UNPKG

pimatic

Version:

A home automation server and framework for the Raspberry PI running on node.js

139 lines (119 loc) 4.29 kB
// // Validation suite for JSON objects against a JSON Schema Draft v4 // schema. Each test returns an array (possibly empty) of errors. // 'use strict'; var core = require('./core.js') , Errors = require('../../errors.js') , uri = require('../../uri.js') ; // ****************************************************************** // Categories of properties we can validate. // ****************************************************************** var PROPS_TO_VALIDATE = { general: ['enum', 'allOf', 'anyOf', 'oneOf', 'not'], array: ['items', 'additionalItems', 'maxItems', 'minItems', 'uniqueItems'], number: ['multipleOf', 'maximum', 'exclusiveMaximum', 'minimum', 'exclusiveMinimum'], object: ['maxProperties', 'minProperties', 'required', 'additionalProperties', 'properties', 'patternProperties', 'dependencies'], string: ['maxLength', 'minLength', 'pattern', 'format'] }; var VALIDATOR_FUNCTIONS = { type: require('./keywords/type.js') }; // ****************************************************************** // Return a set of tests to apply to the instance. // ****************************************************************** function getApplicableTests(config) { var result = []; var len, i, key; // general tests that apply to all types for (i = 0, len = PROPS_TO_VALIDATE.general.length; i !== len; ++i) { key = PROPS_TO_VALIDATE.general[i]; if (Object.prototype.hasOwnProperty.call(config.schema, key)) { result.push(key); } } // type-specific tests var apparentType = core.apparentType(config.inst); if (apparentType === 'integer') { apparentType = 'number'; } var props = PROPS_TO_VALIDATE[apparentType] || []; for (i = 0, len = props.length; i !== len; ++i) { key = props[i]; if (Object.prototype.hasOwnProperty.call(config.schema, key)) { result.push(key); } } // for objects: the properties, patternProperties, and // additionalProperties validations are inseparable if (result.indexOf('properties') !== -1 || result.indexOf('patternProperties') !== -1 || result.indexOf('additionalProperties') !== -1) { result.push('_propertiesImpl'); } return result; } // ****************************************************************** // Run all applicable tests. // // config values: // // inst: instance to validate // schema: schema to validate against // resolutionScope: resolutionScope, // instanceContext: current position within the overall instance // schemaRegistry: a SchemaRegistry // customFormatHandlers: custom handlers for the "format" keyword // // ****************************************************************** function run(config) { var errors = []; var desc; var maxRefDepth = 10; // avoid infinite loops var depth = 0; while (depth < maxRefDepth && config.schema && Object.prototype.hasOwnProperty.call(config.schema, '$ref')) { var ref = uri.resolve(config.resolutionScope, decodeURI(config.schema.$ref)); config.schema = config.schemaRegistry.get(ref); if (!config.schema) { desc = 'schema not available: ' + ref; errors.push(new Errors.ValidationError(undefined, undefined, undefined, undefined, undefined, desc)); return errors; } config.resolutionScope = ref; depth++; if (depth >= maxRefDepth) { desc = 'maximum nested $ref depth of ' + maxRefDepth + ' exceeded; ' + 'possible $ref loop'; errors.push(new Errors.ValidationError(undefined, config.instanceContext, '$ref', undefined, undefined, desc)); return errors; } } // empty schema - bail early if (Object.keys(config.schema).length === 0) { return errors; } // validate the type: if it isn't valid we can bail early errors = errors.concat(VALIDATOR_FUNCTIONS.type(config)); if (errors.length) { return errors; } // test all applicable schema properties var props = getApplicableTests(config); for (var index = 0; index < props.length; ++index) { var prop = props[index]; var fn = VALIDATOR_FUNCTIONS[prop]; if (!fn) { fn = VALIDATOR_FUNCTIONS[prop] = require('./keywords/' + prop + '.js'); } errors = errors.concat(fn(config)); } return errors; } module.exports = run;