const JsonSchemaValidator = require('./jsonschema');
const ProcessGraphError = require('./error');
const ProcessUtils = require('@openeo/js-commons/src/processUtils');
const Utils = require('./utils');
/**
* Base Process class
*
* @class
*/
class BaseProcess {
constructor(spec) {
this.spec = spec; // Keep original specification data
// Make properties easily accessible
Object.assign(this, spec);
if (typeof this.id !== 'string') {
throw new Error("Invalid process specified, no id given.");
}
if (!Array.isArray(this.parameters)) {
this.parameters = [];
}
}
toJSON() {
return this.spec;
}
async validate(node) {
// Check for arguments we don't support and throw error
let unsupportedArgs = node.getArgumentNames().filter(name => this.parameters.findIndex(p => p.name === name) === -1);
if (unsupportedArgs.length > 0) {
throw new ProcessGraphError('ProcessArgumentUnsupported', {
process: this.id,
arguments: unsupportedArgs
});
}
// Validate against JSON Schema
for(let key in this.parameters) {
let param = this.parameters[key];
if (!node.hasArgument(param.name)) {
if (!param.optional) {
throw new ProcessGraphError('ProcessArgumentRequired', {
process: this.id,
argument: param.name
});
}
else {
continue;
}
}
let arg = node.getParsedArgument(param.name);
let rawArg = node.getRawArgument(param.name);
await this.validateArgument(arg, rawArg, node, param);
}
}
async validateArgument(arg, rawArg, node, param, path = null) {
if (!path) {
path = param.name;
}
let argType = Utils.getType(arg);
let pg = node.getProcessGraph();
switch(argType) {
case 'parameter':
// Validate callback parameters (no value available yet)
let callbackParam = pg.getCallbackParameter(arg.from_parameter);
if (callbackParam) {
if (!JsonSchemaValidator.isSchemaCompatible(param.schema, callbackParam.schema)) {
throw new ProcessGraphError('ProcessArgumentInvalid', {
process: this.id,
argument: path,
reason: "Schema for parameter '" + arg.from_parameter + "' not compatible with reference"
});
}
return;
}
// Validate all other parameters (value must be available if allowUndefinedParameterRefs is false)
let value = node.getProcessGraphParameterValue(arg.from_parameter);
if (typeof value === 'undefined' && !pg.allowUndefinedParameterRefs) {
throw new ProcessGraphError('ProcessGraphParameterMissing', {
argument: arg.from_parameter,
node_id: node.id,
process_id: node.process_id
});
}
let parameter = pg.getProcessParameter(arg.from_parameter);
if (Utils.isObject(parameter) && parameter.schema) {
if (typeof value !== 'undefined') {
await this.validateArgument(value, rawArg, node, parameter, path);
}
if (!JsonSchemaValidator.isSchemaCompatible(param.schema, parameter.schema)) {
throw new ProcessGraphError('ProcessArgumentInvalid', {
process: this.id,
argument: path,
reason: "Schema for parameter '" + arg.from_parameter + "' not compatible"
});
}
}
// else: Parameter not available, everything is valid
break;
case 'result':
let resultNode = pg.getNode(arg.from_node);
let process = pg.getProcess(resultNode);
if (!JsonSchemaValidator.isSchemaCompatible(param.schema, process.returns.schema)) {
throw new ProcessGraphError('ProcessArgumentInvalid', {
process: this.id,
argument: path,
reason: "Schema for result '" + arg.from_node + "' not compatible"
});
}
break;
case 'array':
case 'object':
let schemas = ProcessUtils.normalizeJsonSchema(param.schema).filter(schema => ['array', 'object'].includes(schema.type));
// Check if it is expected to be a process. If yes, do normal validation. Handles the issue discussed in https://github.com/Open-EO/openeo-js-processgraphs/issues/4
let isProcessGraphSchema = (schemas.length === 1 && schemas[0].subtype === 'process-graph');
if (Utils.containsRef(rawArg) && !isProcessGraphSchema) {
// This tries to at least be compliant to one of the element schemas
// It's better than validating nothing, but it's still not 100% correct
for(var key in arg) {
let elementSchema = schemas.map(schema => ProcessUtils.getElementJsonSchema(schema, key)).filter(schema => Object.keys(schema).length); // jshint ignore:line
if (elementSchema.length > 0) {
let validated = 0;
let lastError = null;
for(let schema of elementSchema) {
try {
// ToDo: Check against JSON schema required property
await this.validateArgument(arg[key], rawArg[key], node, {schema}, path + '/' + key);
validated++;
} catch (error) {
lastError = error;
}
}
if (validated === 0 && lastError) {
throw lastError;
}
}
}
return;
}
else {
// Use default behavior below, so no break; needed
} // jshint ignore:line
default:
let validator = node.getProcessGraph().getJsonSchemaValidator();
// Validate against JSON schema
let errors = await validator.validateValue(arg, param.schema);
if (errors.length > 0) {
throw new ProcessGraphError('ProcessArgumentInvalid', {
process: this.id,
argument: path,
reason: errors
});
}
}
}
/* istanbul ignore next */
async execute(/*node*/) {
throw "execute not implemented yet";
}
/* istanbul ignore next */
test() {
// Run the tests from the examples
throw "test not implemented yet";
}
}
module.exports = BaseProcess;