diff options
Diffstat (limited to 'common/jsonschema/attribute.js')
-rw-r--r-- | common/jsonschema/attribute.js | 1002 |
1 files changed, 1002 insertions, 0 deletions
diff --git a/common/jsonschema/attribute.js b/common/jsonschema/attribute.js new file mode 100644 index 0000000..5f10b7c --- /dev/null +++ b/common/jsonschema/attribute.js @@ -0,0 +1,1002 @@ +/* SPDX-License-Identifier: MIT + * + * jsonschema is licensed under MIT license. + * + * Copyright (C) 2012-2015 Tom de Grunt <tom@degrunt.nl> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#IMPORT common/jsonschema/helpers.js + +/** @type ValidatorResult */ +var ValidatorResult = helpers.ValidatorResult; +/** @type SchemaError */ +var SchemaError = helpers.SchemaError; + +var attribute = {}; + +const ignoreProperties = { + // informative properties + 'id': true, + 'default': true, + 'description': true, + 'title': true, + // arguments to other properties + 'additionalItems': true, + 'then': true, + 'else': true, + // special-handled properties + '$schema': true, + '$ref': true, + 'extends': true, +}; +#EXPORT ignoreProperties + +/** + * @name validators + */ +const validators = {}; + +/** + * Validates whether the instance if of a certain type + * @param instance + * @param schema + * @param options + * @param ctx + * @return {ValidatorResult|null} + */ +validators.type = function validateType (instance, schema, options, ctx) { + // Ignore undefined instances + if (instance === undefined) { + return null; + } + var result = new ValidatorResult(instance, schema, options, ctx); + var types = Array.isArray(schema.type) ? schema.type : [schema.type]; + if (!types.some(this.testType.bind(this, instance, schema, options, ctx))) { + var list = types.map(function (v) { + if(!v) return; + var id = v.$id || v.id; + return id ? ('<' + id + '>') : (v+''); + }); + result.addError({ + name: 'type', + argument: list, + message: "is not of a type(s) " + list, + }); + } + return result; +}; + +function testSchemaNoThrow(instance, options, ctx, callback, schema){ + var throwError = options.throwError; + var throwAll = options.throwAll; + options.throwError = false; + options.throwAll = false; + var res = this.validateSchema(instance, schema, options, ctx); + options.throwError = throwError; + options.throwAll = throwAll; + + if (!res.valid && callback instanceof Function) { + callback(res); + } + return res.valid; +} + +/** + * Validates whether the instance matches some of the given schemas + * @param instance + * @param schema + * @param options + * @param ctx + * @return {ValidatorResult|null} + */ +validators.anyOf = function validateAnyOf (instance, schema, options, ctx) { + // Ignore undefined instances + if (instance === undefined) { + return null; + } + var result = new ValidatorResult(instance, schema, options, ctx); + var inner = new ValidatorResult(instance, schema, options, ctx); + if (!Array.isArray(schema.anyOf)){ + throw new SchemaError("anyOf must be an array"); + } + if (!schema.anyOf.some( + testSchemaNoThrow.bind( + this, instance, options, ctx, function(res){inner.importErrors(res);} + ))) { + var list = schema.anyOf.map(function (v, i) { + var id = v.$id || v.id; + if(id) return '<' + id + '>'; + return(v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']'; + }); + if (options.nestedErrors) { + result.importErrors(inner); + } + result.addError({ + name: 'anyOf', + argument: list, + message: "is not any of " + list.join(','), + }); + } + return result; +}; + +/** + * Validates whether the instance matches every given schema + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null} + */ +validators.allOf = function validateAllOf (instance, schema, options, ctx) { + // Ignore undefined instances + if (instance === undefined) { + return null; + } + if (!Array.isArray(schema.allOf)){ + throw new SchemaError("allOf must be an array"); + } + var result = new ValidatorResult(instance, schema, options, ctx); + var self = this; + schema.allOf.forEach(function(v, i){ + var valid = self.validateSchema(instance, v, options, ctx); + if(!valid.valid){ + var id = v.$id || v.id; + var msg = id || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']'; + result.addError({ + name: 'allOf', + argument: { id: msg, length: valid.errors.length, valid: valid }, + message: 'does not match allOf schema ' + msg + ' with ' + valid.errors.length + ' error[s]:', + }); + result.importErrors(valid); + } + }); + return result; +}; + +/** + * Validates whether the instance matches exactly one of the given schemas + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null} + */ +validators.oneOf = function validateOneOf (instance, schema, options, ctx) { + // Ignore undefined instances + if (instance === undefined) { + return null; + } + if (!Array.isArray(schema.oneOf)){ + throw new SchemaError("oneOf must be an array"); + } + var result = new ValidatorResult(instance, schema, options, ctx); + var inner = new ValidatorResult(instance, schema, options, ctx); + var count = schema.oneOf.filter( + testSchemaNoThrow.bind( + this, instance, options, ctx, function(res) {inner.importErrors(res);} + ) ).length; + var list = schema.oneOf.map(function (v, i) { + var id = v.$id || v.id; + return id || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']'; + }); + if (count!==1) { + if (options.nestedErrors) { + result.importErrors(inner); + } + result.addError({ + name: 'oneOf', + argument: list, + message: "is not exactly one from " + list.join(','), + }); + } + return result; +}; + +/** + * Validates "then" or "else" depending on the result of validating "if" + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null} + */ +validators.if = function validateIf (instance, schema, options, ctx) { + // Ignore undefined instances + if (instance === undefined) return null; + if (!helpers.isSchema(schema.if)) throw new Error('Expected "if" keyword to be a schema'); + var ifValid = testSchemaNoThrow.call(this, instance, options, ctx, null, schema.if); + var result = new ValidatorResult(instance, schema, options, ctx); + var res; + if(ifValid){ + if (schema.then === undefined) return; + if (!helpers.isSchema(schema.then)) throw new Error('Expected "then" keyword to be a schema'); + res = this.validateSchema(instance, schema.then, options, ctx.makeChild(schema.then)); + result.importErrors(res); + }else{ + if (schema.else === undefined) return; + if (!helpers.isSchema(schema.else)) throw new Error('Expected "else" keyword to be a schema'); + res = this.validateSchema(instance, schema.else, options, ctx.makeChild(schema.else)); + result.importErrors(res); + } + return result; +}; + +function getEnumerableProperty(object, key){ + // Determine if `key` shows up in `for(var key in object)` + // First test Object.hasOwnProperty.call as an optimization: that guarantees it does + if(Object.hasOwnProperty.call(object, key)) return object[key]; + // Test `key in object` as an optimization; false means it won't + if(!(key in object)) return; + while( (object = Object.getPrototypeOf(object)) ){ + if(Object.propertyIsEnumerable.call(object, key)) return object[key]; + } +} + +/** + * Validates propertyNames + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null|ValidatorResult} + */ +validators.propertyNames = function validatePropertyNames (instance, schema, options, ctx) { + if(!this.types.object(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var subschema = schema.propertyNames!==undefined ? schema.propertyNames : {}; + if(!helpers.isSchema(subschema)) throw new SchemaError('Expected "propertyNames" to be a schema (object or boolean)'); + + for (var property in instance) { + if(getEnumerableProperty(instance, property) !== undefined){ + var res = this.validateSchema(property, subschema, options, ctx.makeChild(subschema)); + result.importErrors(res); + } + } + + return result; +}; + +/** + * Validates properties + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null|ValidatorResult} + */ +validators.properties = function validateProperties (instance, schema, options, ctx) { + if(!this.types.object(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var properties = schema.properties || {}; + for (var property in properties) { + var subschema = properties[property]; + if(subschema===undefined){ + continue; + }else if(subschema===null){ + throw new SchemaError('Unexpected null, expected schema in "properties"'); + } + if (typeof options.preValidateProperty == 'function') { + options.preValidateProperty(instance, property, subschema, options, ctx); + } + var prop = getEnumerableProperty(instance, property); + var res = this.validateSchema(prop, subschema, options, ctx.makeChild(subschema, property)); + if(res.instance !== result.instance[property]) result.instance[property] = res.instance; + result.importErrors(res); + } + return result; +}; + +/** + * Test a specific property within in instance against the additionalProperties schema attribute + * This ignores properties with definitions in the properties schema attribute, but no other attributes. + * If too many more types of property-existence tests pop up they may need their own class of tests (like `type` has) + * @private + * @return {boolean} + */ +function testAdditionalProperty (instance, schema, options, ctx, property, result) { + if(!this.types.object(instance)) return; + if (schema.properties && schema.properties[property] !== undefined) { + return; + } + if (schema.additionalProperties === false) { + result.addError({ + name: 'additionalProperties', + argument: property, + message: "is not allowed to have the additional property " + JSON.stringify(property), + }); + } else { + var additionalProperties = schema.additionalProperties || {}; + + if (typeof options.preValidateProperty == 'function') { + options.preValidateProperty(instance, property, additionalProperties, options, ctx); + } + + var res = this.validateSchema(instance[property], additionalProperties, options, ctx.makeChild(additionalProperties, property)); + if(res.instance !== result.instance[property]) result.instance[property] = res.instance; + result.importErrors(res); + } +} + +/** + * Validates patternProperties + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null|ValidatorResult} + */ +validators.patternProperties = function validatePatternProperties (instance, schema, options, ctx) { + if(!this.types.object(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var patternProperties = schema.patternProperties || {}; + + for (var property in instance) { + var test = true; + for (var pattern in patternProperties) { + var subschema = patternProperties[pattern]; + if(subschema===undefined){ + continue; + }else if(subschema===null){ + throw new SchemaError('Unexpected null, expected schema in "patternProperties"'); + } + try { + var regexp = new RegExp(pattern, 'u'); + } catch(_e) { + // In the event the stricter handling causes an error, fall back on the forgiving handling + // DEPRECATED + regexp = new RegExp(pattern); + } + if (!regexp.test(property)) { + continue; + } + test = false; + + if (typeof options.preValidateProperty == 'function') { + options.preValidateProperty(instance, property, subschema, options, ctx); + } + + var res = this.validateSchema(instance[property], subschema, options, ctx.makeChild(subschema, property)); + if(res.instance !== result.instance[property]) result.instance[property] = res.instance; + result.importErrors(res); + } + if (test) { + testAdditionalProperty.call(this, instance, schema, options, ctx, property, result); + } + } + + return result; +}; + +/** + * Validates additionalProperties + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null|ValidatorResult} + */ +validators.additionalProperties = function validateAdditionalProperties (instance, schema, options, ctx) { + if(!this.types.object(instance)) return; + // if patternProperties is defined then we'll test when that one is called instead + if (schema.patternProperties) { + return null; + } + var result = new ValidatorResult(instance, schema, options, ctx); + for (var property in instance) { + testAdditionalProperty.call(this, instance, schema, options, ctx, property, result); + } + return result; +}; + +/** + * Validates whether the instance value is at least of a certain length, when the instance value is a string. + * @param instance + * @param schema + * @return {String|null} + */ +validators.minProperties = function validateMinProperties (instance, schema, options, ctx) { + if (!this.types.object(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var keys = Object.keys(instance); + if (!(keys.length >= schema.minProperties)) { + result.addError({ + name: 'minProperties', + argument: schema.minProperties, + message: "does not meet minimum property length of " + schema.minProperties, + }); + } + return result; +}; + +/** + * Validates whether the instance value is at most of a certain length, when the instance value is a string. + * @param instance + * @param schema + * @return {String|null} + */ +validators.maxProperties = function validateMaxProperties (instance, schema, options, ctx) { + if (!this.types.object(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var keys = Object.keys(instance); + if (!(keys.length <= schema.maxProperties)) { + result.addError({ + name: 'maxProperties', + argument: schema.maxProperties, + message: "does not meet maximum property length of " + schema.maxProperties, + }); + } + return result; +}; + +/** + * Validates items when instance is an array + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null|ValidatorResult} + */ +validators.items = function validateItems (instance, schema, options, ctx) { + var self = this; + if (!this.types.array(instance)) return; + if (schema.items===undefined) return; + var result = new ValidatorResult(instance, schema, options, ctx); + instance.every(function (value, i) { + if(Array.isArray(schema.items)){ + var items = schema.items[i]===undefined ? schema.additionalItems : schema.items[i]; + }else{ + var items = schema.items; + } + if (items === undefined) { + return true; + } + if (items === false) { + result.addError({ + name: 'items', + message: "additionalItems not permitted", + }); + return false; + } + var res = self.validateSchema(value, items, options, ctx.makeChild(items, i)); + if(res.instance !== result.instance[i]) result.instance[i] = res.instance; + result.importErrors(res); + return true; + }); + return result; +}; + +/** + * Validates the "contains" keyword + * @param instance + * @param schema + * @param options + * @param ctx + * @return {String|null|ValidatorResult} + */ +validators.contains = function validateContains (instance, schema, options, ctx) { + var self = this; + if (!this.types.array(instance)) return; + if (schema.contains===undefined) return; + if (!helpers.isSchema(schema.contains)) throw new Error('Expected "contains" keyword to be a schema'); + var result = new ValidatorResult(instance, schema, options, ctx); + var count = instance.some(function (value, i) { + var res = self.validateSchema(value, schema.contains, options, ctx.makeChild(schema.contains, i)); + return res.errors.length===0; + }); + if(count===false){ + result.addError({ + name: 'contains', + argument: schema.contains, + message: "must contain an item matching given schema", + }); + } + return result; +}; + +/** + * Validates minimum and exclusiveMinimum when the type of the instance value is a number. + * @param instance + * @param schema + * @return {String|null} + */ +validators.minimum = function validateMinimum (instance, schema, options, ctx) { + if (!this.types.number(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + if (schema.exclusiveMinimum && schema.exclusiveMinimum === true) { + if(!(instance > schema.minimum)){ + result.addError({ + name: 'minimum', + argument: schema.minimum, + message: "must be greater than " + schema.minimum, + }); + } + } else { + if(!(instance >= schema.minimum)){ + result.addError({ + name: 'minimum', + argument: schema.minimum, + message: "must be greater than or equal to " + schema.minimum, + }); + } + } + return result; +}; + +/** + * Validates maximum and exclusiveMaximum when the type of the instance value is a number. + * @param instance + * @param schema + * @return {String|null} + */ +validators.maximum = function validateMaximum (instance, schema, options, ctx) { + if (!this.types.number(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + if (schema.exclusiveMaximum && schema.exclusiveMaximum === true) { + if(!(instance < schema.maximum)){ + result.addError({ + name: 'maximum', + argument: schema.maximum, + message: "must be less than " + schema.maximum, + }); + } + } else { + if(!(instance <= schema.maximum)){ + result.addError({ + name: 'maximum', + argument: schema.maximum, + message: "must be less than or equal to " + schema.maximum, + }); + } + } + return result; +}; + +/** + * Validates the number form of exclusiveMinimum when the type of the instance value is a number. + * @param instance + * @param schema + * @return {String|null} + */ +validators.exclusiveMinimum = function validateExclusiveMinimum (instance, schema, options, ctx) { + // Support the boolean form of exclusiveMinimum, which is handled by the "minimum" keyword. + if(typeof schema.exclusiveMinimum === 'boolean') return; + if (!this.types.number(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var valid = instance > schema.exclusiveMinimum; + if (!valid) { + result.addError({ + name: 'exclusiveMinimum', + argument: schema.exclusiveMinimum, + message: "must be strictly greater than " + schema.exclusiveMinimum, + }); + } + return result; +}; + +/** + * Validates the number form of exclusiveMaximum when the type of the instance value is a number. + * @param instance + * @param schema + * @return {String|null} + */ +validators.exclusiveMaximum = function validateExclusiveMaximum (instance, schema, options, ctx) { + // Support the boolean form of exclusiveMaximum, which is handled by the "maximum" keyword. + if(typeof schema.exclusiveMaximum === 'boolean') return; + if (!this.types.number(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var valid = instance < schema.exclusiveMaximum; + if (!valid) { + result.addError({ + name: 'exclusiveMaximum', + argument: schema.exclusiveMaximum, + message: "must be strictly less than " + schema.exclusiveMaximum, + }); + } + return result; +}; + +/** + * Perform validation for multipleOf and divisibleBy, which are essentially the same. + * @param instance + * @param schema + * @param validationType + * @param errorMessage + * @returns {String|null} + */ +var validateMultipleOfOrDivisbleBy = function validateMultipleOfOrDivisbleBy (instance, schema, options, ctx, validationType, errorMessage) { + if (!this.types.number(instance)) return; + + var validationArgument = schema[validationType]; + if (validationArgument == 0) { + throw new SchemaError(validationType + " cannot be zero"); + } + + var result = new ValidatorResult(instance, schema, options, ctx); + + var instanceDecimals = helpers.getDecimalPlaces(instance); + var divisorDecimals = helpers.getDecimalPlaces(validationArgument); + + var maxDecimals = Math.max(instanceDecimals , divisorDecimals); + var multiplier = Math.pow(10, maxDecimals); + + if (Math.round(instance * multiplier) % Math.round(validationArgument * multiplier) !== 0) { + result.addError({ + name: validationType, + argument: validationArgument, + message: errorMessage + JSON.stringify(validationArgument), + }); + } + + return result; +}; + +/** + * Validates divisibleBy when the type of the instance value is a number. + * @param instance + * @param schema + * @return {String|null} + */ +validators.multipleOf = function validateMultipleOf (instance, schema, options, ctx) { + return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "multipleOf", "is not a multiple of (divisible by) "); +}; + +/** + * Validates multipleOf when the type of the instance value is a number. + * @param instance + * @param schema + * @return {String|null} + */ +validators.divisibleBy = function validateDivisibleBy (instance, schema, options, ctx) { + return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "divisibleBy", "is not divisible by (multiple of) "); +}; + +/** + * Validates whether the instance value is present. + * @param instance + * @param schema + * @return {String|null} + */ +validators.required = function validateRequired (instance, schema, options, ctx) { + var result = new ValidatorResult(instance, schema, options, ctx); + if (instance === undefined && schema.required === true) { + // A boolean form is implemented for reverse-compatibility with schemas written against older drafts + result.addError({ + name: 'required', + message: "is required", + }); + } else if (this.types.object(instance) && Array.isArray(schema.required)) { + schema.required.forEach(function(n){ + if(getEnumerableProperty(instance, n)===undefined){ + result.addError({ + name: 'required', + argument: n, + message: "requires property " + JSON.stringify(n), + }); + } + }); + } + return result; +}; + +/** + * Validates whether the instance value matches the regular expression, when the instance value is a string. + * @param instance + * @param schema + * @return {String|null} + */ +validators.pattern = function validatePattern (instance, schema, options, ctx) { + if (!this.types.string(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var pattern = schema.pattern; + try { + var regexp = new RegExp(pattern, 'u'); + } catch(_e) { + // In the event the stricter handling causes an error, fall back on the forgiving handling + // DEPRECATED + regexp = new RegExp(pattern); + } + if (!instance.match(regexp)) { + result.addError({ + name: 'pattern', + argument: schema.pattern, + message: "does not match pattern " + JSON.stringify(schema.pattern.toString()), + }); + } + return result; +}; + +/** + * Validates whether the instance value is of a certain defined format or a custom + * format. + * The following formats are supported for string types: + * - date-time + * - date + * - time + * - ip-address + * - ipv6 + * - uri + * - color + * - host-name + * - alpha + * - alpha-numeric + * - utc-millisec + * @param instance + * @param schema + * @param [options] + * @param [ctx] + * @return {String|null} + */ +validators.format = function validateFormat (instance, schema, options, ctx) { + if (instance===undefined) return; + var result = new ValidatorResult(instance, schema, options, ctx); + if (!result.disableFormat && !helpers.isFormat(instance, schema.format, this)) { + result.addError({ + name: 'format', + argument: schema.format, + message: "does not conform to the " + JSON.stringify(schema.format) + " format", + }); + } + return result; +}; + +/** + * Validates whether the instance value is at least of a certain length, when the instance value is a string. + * @param instance + * @param schema + * @return {String|null} + */ +validators.minLength = function validateMinLength (instance, schema, options, ctx) { + if (!this.types.string(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + var hsp = instance.match(/[\uDC00-\uDFFF]/g); + var length = instance.length - (hsp ? hsp.length : 0); + if (!(length >= schema.minLength)) { + result.addError({ + name: 'minLength', + argument: schema.minLength, + message: "does not meet minimum length of " + schema.minLength, + }); + } + return result; +}; + +/** + * Validates whether the instance value is at most of a certain length, when the instance value is a string. + * @param instance + * @param schema + * @return {String|null} + */ +validators.maxLength = function validateMaxLength (instance, schema, options, ctx) { + if (!this.types.string(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + // TODO if this was already computed in "minLength", use that value instead of re-computing + var hsp = instance.match(/[\uDC00-\uDFFF]/g); + var length = instance.length - (hsp ? hsp.length : 0); + if (!(length <= schema.maxLength)) { + result.addError({ + name: 'maxLength', + argument: schema.maxLength, + message: "does not meet maximum length of " + schema.maxLength, + }); + } + return result; +}; + +/** + * Validates whether instance contains at least a minimum number of items, when the instance is an Array. + * @param instance + * @param schema + * @return {String|null} + */ +validators.minItems = function validateMinItems (instance, schema, options, ctx) { + if (!this.types.array(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + if (!(instance.length >= schema.minItems)) { + result.addError({ + name: 'minItems', + argument: schema.minItems, + message: "does not meet minimum length of " + schema.minItems, + }); + } + return result; +}; + +/** + * Validates whether instance contains no more than a maximum number of items, when the instance is an Array. + * @param instance + * @param schema + * @return {String|null} + */ +validators.maxItems = function validateMaxItems (instance, schema, options, ctx) { + if (!this.types.array(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + if (!(instance.length <= schema.maxItems)) { + result.addError({ + name: 'maxItems', + argument: schema.maxItems, + message: "does not meet maximum length of " + schema.maxItems, + }); + } + return result; +}; + +/** + * Deep compares arrays for duplicates + * @param v + * @param i + * @param a + * @private + * @return {boolean} + */ +function testArrays (v, i, a) { + var j, len = a.length; + for (j = i + 1, len; j < len; j++) { + if (helpers.deepCompareStrict(v, a[j])) { + return false; + } + } + return true; +} + +/** + * Validates whether there are no duplicates, when the instance is an Array. + * @param instance + * @return {String|null} + */ +validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) { + if (schema.uniqueItems!==true) return; + if (!this.types.array(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + if (!instance.every(testArrays)) { + result.addError({ + name: 'uniqueItems', + message: "contains duplicate item", + }); + } + return result; +}; + +/** + * Validate for the presence of dependency properties, if the instance is an object. + * @param instance + * @param schema + * @param options + * @param ctx + * @return {null|ValidatorResult} + */ +validators.dependencies = function validateDependencies (instance, schema, options, ctx) { + if (!this.types.object(instance)) return; + var result = new ValidatorResult(instance, schema, options, ctx); + for (var property in schema.dependencies) { + if (instance[property] === undefined) { + continue; + } + var dep = schema.dependencies[property]; + var childContext = ctx.makeChild(dep, property); + if (typeof dep == 'string') { + dep = [dep]; + } + if (Array.isArray(dep)) { + dep.forEach(function (prop) { + if (instance[prop] === undefined) { + result.addError({ + // FIXME there's two different "dependencies" errors here with slightly different outputs + // Can we make these the same? Or should we create different error types? + name: 'dependencies', + argument: childContext.propertyPath, + message: "property " + prop + " not found, required by " + childContext.propertyPath, + }); + } + }); + } else { + var res = this.validateSchema(instance, dep, options, childContext); + if(result.instance !== res.instance) result.instance = res.instance; + if (res && res.errors.length) { + result.addError({ + name: 'dependencies', + argument: childContext.propertyPath, + message: "does not meet dependency required by " + childContext.propertyPath, + }); + result.importErrors(res); + } + } + } + return result; +}; + +/** + * Validates whether the instance value is one of the enumerated values. + * + * @param instance + * @param schema + * @return {ValidatorResult|null} + */ +validators['enum'] = function validateEnum (instance, schema, options, ctx) { + if (instance === undefined) { + return null; + } + if (!Array.isArray(schema['enum'])) { + throw new SchemaError("enum expects an array", schema); + } + var result = new ValidatorResult(instance, schema, options, ctx); + if (!schema['enum'].some(helpers.deepCompareStrict.bind(null, instance))) { + result.addError({ + name: 'enum', + argument: schema['enum'], + message: "is not one of enum values: " + schema['enum'].map(String).join(','), + }); + } + return result; +}; + +/** + * Validates whether the instance exactly matches a given value + * + * @param instance + * @param schema + * @return {ValidatorResult|null} + */ +validators['const'] = function validateEnum (instance, schema, options, ctx) { + if (instance === undefined) { + return null; + } + var result = new ValidatorResult(instance, schema, options, ctx); + if (!helpers.deepCompareStrict(schema['const'], instance)) { + result.addError({ + name: 'const', + argument: schema['const'], + message: "does not exactly match expected constant: " + schema['const'], + }); + } + return result; +}; + +/** + * Validates whether the instance if of a prohibited type. + * @param instance + * @param schema + * @param options + * @param ctx + * @return {null|ValidatorResult} + */ +validators.not = validators.disallow = function validateNot (instance, schema, options, ctx) { + var self = this; + if(instance===undefined) return null; + var result = new ValidatorResult(instance, schema, options, ctx); + var notTypes = schema.not || schema.disallow; + if(!notTypes) return null; + if(!Array.isArray(notTypes)) notTypes=[notTypes]; + notTypes.forEach(function (type) { + if (self.testType(instance, schema, options, ctx, type)) { + var id = type && (type.$id || type.id); + var schemaId = id || type; + result.addError({ + name: 'not', + argument: schemaId, + message: "is of prohibited type " + schemaId, + }); + } + }); + return result; +}; + +#EXPORT validators |