jsonschema
Advanced tools
Comparing version 0.1.5 to 0.2.0
'use strict'; | ||
var helpers = require('./helpers'); | ||
var equal = require('deep-equal'); | ||
var Attribute = function Attribute(validator, name, schema, instance, propertyName, options) { | ||
this.validator = validator; | ||
this.name = name; | ||
this.schema = schema; | ||
this.value = schema[name]; | ||
this.propertyName = propertyName; | ||
this.instance = instance; | ||
this.options = options; | ||
var attribute = {}; | ||
return this; | ||
}; | ||
attribute.informativeProperties = ['id', 'default', 'description', 'title']; | ||
attribute.argumentProperties = ['exclusiveMinimum', 'exclusiveMaximum','items','additionalItems','properties','additionalProperties','patternProperties']; | ||
attribute.ignoreProperties = [].concat(attribute.informativeProperties, attribute.argumentProperties); | ||
Attribute.prototype.createError = function (message) { | ||
return helpers.createError(this.schema, this.name, this.propertyName, message); | ||
}; | ||
var validators = attribute.validators = {}; | ||
Attribute.prototype.validate = function () { | ||
// Ignored attributes, mostly due to that they are handled differently. | ||
if (this.name === "exclusiveMinimum" || this.name === "exclusiveMaximum" || this.name === "default" || this.name === "description" || this.name === "title") { | ||
return null; | ||
function testType(instance, options, propertyName, type){ | ||
switch (type) { | ||
case 'string': return (typeof instance=='string'); | ||
case 'number': return (typeof instance=='number'); | ||
case 'integer': return (typeof instance=='number') && instance%1===0; | ||
case 'boolean': return (typeof instance=='boolean'); | ||
case 'object': | ||
return (instance && (typeof instance)=='object' && Object.getPrototypeOf(instance)===Object.prototype); | ||
case 'array': return (instance instanceof Array); | ||
case 'null': return (instance===null); | ||
case 'date': return (instance instanceof Date); | ||
case 'any': return true; | ||
} | ||
var mName = 'validate' + helpers.capitalize(this.name); | ||
if (this[mName]) { | ||
return this[mName](); | ||
} else { | ||
console.log("WARNING: unsupported attribute:", this.name); | ||
if(type && typeof type=='object'){ | ||
var errs = this.validateSchema(instance, type, options, propertyName); | ||
return !(errs&&errs.length); | ||
} | ||
}; | ||
} | ||
Attribute.prototype.validateType = function () { | ||
var valid = false; | ||
switch (this.value) { | ||
case 'string': | ||
valid = helpers.isString(this.instance); | ||
break; | ||
case 'number': | ||
valid = helpers.isNumber(this.instance); | ||
break; | ||
case 'integer': | ||
valid = helpers.isInteger(this.instance); | ||
break; | ||
case 'boolean': | ||
valid = helpers.isBoolean(this.instance); | ||
break; | ||
case 'null': | ||
valid = helpers.isNull(this.instance); | ||
break; | ||
case 'date': | ||
valid = helpers.isDate(this.instance); | ||
break; | ||
case 'any': | ||
valid = true; | ||
break; | ||
default: | ||
break; | ||
} | ||
validators.type = function validateType(instance, schema, options, propertyName) { | ||
var types = (schema.type instanceof Array)?schema.type:[schema.type]; | ||
// Ignore undefined instances | ||
if (helpers.isDefined(this.instance) && !valid) { | ||
return this.createError("is not " + this.value); | ||
if (instance===undefined) return; | ||
if(!types.some(testType.bind(this, instance, options, propertyName))) { | ||
return "is not " + schema.type; | ||
} | ||
@@ -69,14 +43,12 @@ }; | ||
// Only applicable for numbers | ||
Attribute.prototype.validateMinimum = function () { | ||
validators.minimum = function validateMinimum(instance, schema) { | ||
if (typeof instance!='number') return; | ||
var valid = true; | ||
if (helpers.isNumber(this.instance)) { | ||
if (this.schema.exclusiveMinimum && this.schema.exclusiveMinimum === true) { | ||
valid = this.instance > this.value; | ||
} else { | ||
valid = this.instance >= this.value; | ||
} | ||
if (schema.exclusiveMinimum && schema.exclusiveMinimum === true) { | ||
valid = instance > schema.minimum; | ||
} else { | ||
valid = instance >= schema.minimum; | ||
} | ||
if (!valid) { | ||
return this.createError("is not " + this.value); | ||
return "is not " + schema.minimum; | ||
} | ||
@@ -86,32 +58,26 @@ }; | ||
// Only applicable for numbers | ||
Attribute.prototype.validateMaximum = function () { | ||
var valid = true; | ||
if (helpers.isNumber(this.instance)) { | ||
if (this.schema.exclusiveMaximum && this.schema.exclusiveMaximum === true) { | ||
valid = this.instance < this.value; | ||
} else { | ||
valid = this.instance <= this.value; | ||
} | ||
validators.maximum = function validateMaximum(instance, schema) { | ||
if (typeof instance!='number') return; | ||
var valid; | ||
if (schema.exclusiveMaximum && schema.exclusiveMaximum === true) { | ||
valid = instance < schema.maximum; | ||
} else { | ||
valid = instance <= schema.maximum; | ||
} | ||
if (!valid) { | ||
return this.createError("is not " + this.value); | ||
return "is not " + schema.maximum; | ||
} | ||
}; | ||
Attribute.prototype.validateRequired = function () { | ||
var valid = true; | ||
if (!helpers.isDefined(this.instance) && this.value === true) { | ||
return this.createError("is required"); | ||
// Only applicable for numbers | ||
validators.divisibleBy = function validateDivisibleBy(instance, schema) { | ||
if (typeof instance!='number') return; | ||
if (instance % schema.divisibleBy) { | ||
return "is not " + schema.maximum; | ||
} | ||
}; | ||
// Only applicable for numbers | ||
Attribute.prototype.validate$ref = function () { | ||
var valid = true; | ||
if (helpers.isDefined(this.value) && this.validator.schemas[this.value]) { | ||
return this.validator.validateSchema(this.instance, this.validator.schemas[this.value], this.options); | ||
} else { | ||
return this.createError("no such schema " + this.value); | ||
validators.required = function validateRequired(instance, schema) { | ||
if (instance===undefined && schema.required===true) { | ||
return "is required"; | ||
} | ||
@@ -121,18 +87,14 @@ }; | ||
// Only applicable for strings, ignored otherwise | ||
Attribute.prototype.validatePattern = function () { | ||
var valid = true; | ||
if (helpers.isString(this.instance)) { | ||
valid = (this.instance.match(this.value) !== null); | ||
validators.pattern = function validatePattern(instance, schema) { | ||
if (typeof instance!='string') return; | ||
if(!instance.match(schema.pattern)) { | ||
return "does not match pattern" + schema.pattern; | ||
} | ||
if (!valid) { | ||
return this.createError("does not match pattern" + this.value); | ||
} | ||
}; | ||
// Only applicable for strings, ignored otherwise | ||
Attribute.prototype.validateFormat = function () { | ||
var valid = true; | ||
valid = !helpers.isDefined(this.instance) || (helpers.isString(this.instance) && helpers.isFormat(this.instance, this.value)); | ||
if (!valid) { | ||
return this.createError("\"" + this.instance + "\" does not conform to format " + this.value); | ||
validators.format = function validateFormat(instance, schema) { | ||
if(instance===undefined) return; | ||
if (!helpers.isFormat(instance, schema.format)) { | ||
return "\"" + instance + "\" does not conform to format " + schema.format; | ||
} | ||
@@ -142,20 +104,22 @@ }; | ||
// Only applicable for strings, ignored otherwise | ||
Attribute.prototype.validateMinLength = function () { | ||
var valid = true; | ||
if (helpers.isString(this.instance)) { | ||
valid = this.instance.length >= this.value; | ||
validators.minLength = function validateMinLength(instance, schema) { | ||
if (!(typeof instance=='string')) return; | ||
if(!(instance.length >= schema.minLength)){ | ||
return "does not meet minimum length of " + schema.minLength; | ||
} | ||
if (!valid) { | ||
return this.createError("does not meet minimum length of " + this.value); | ||
} | ||
}; | ||
// Only applicable for strings, ignored otherwise | ||
Attribute.prototype.validateMaxLength = function () { | ||
var valid = true; | ||
if (helpers.isString(this.instance)) { | ||
valid = this.instance.length <= this.value; | ||
validators.maxLength = function validateMaxLength(instance, schema) { | ||
if (!(typeof instance=='string')) return; | ||
if(!(instance.length <= schema.maxLength)){ | ||
return "does not meet maximum length of " + schema.maxLength; | ||
} | ||
if (!valid) { | ||
return this.createError("does not meet maximum length of " + this.value); | ||
}; | ||
// Only applicable for arrays, ignored otherwise | ||
validators.minItems = function validateMinItems(instance, schema) { | ||
if (!(instance instanceof Array)) return; | ||
if(!(instance.length >= schema.minItems)){ | ||
return "does not meet minimum length of " + schema.minItems; | ||
} | ||
@@ -165,40 +129,43 @@ }; | ||
// Only applicable for arrays, ignored otherwise | ||
Attribute.prototype.validateMinItems = function () { | ||
var valid = true; | ||
if (helpers.isArray(this.instance)) { | ||
valid = this.instance.length >= this.value; | ||
validators.maxItems = function validateMaxItems(instance, schema) { | ||
if (!(instance instanceof Array)) return; | ||
if(!(instance.length <= schema.maxItems)){ | ||
return "does not meet maximum length of " + schema.maxItems; | ||
} | ||
if (!valid) { | ||
return this.createError("does not meet minimum length of " + this.value); | ||
} | ||
}; | ||
// Only applicable for arrays, ignored otherwise | ||
Attribute.prototype.validateMaxItems = function () { | ||
var valid = true; | ||
if (helpers.isArray(this.instance)) { | ||
valid = this.instance.length <= this.value; | ||
validators.uniqueItems = function validateUniqueItems(instance, schema) { | ||
if (!(instance instanceof Array)) return; | ||
function testArrays(v, i, a){ | ||
for(var j=i+1; j<a.length; j++) if(helpers.deepCompareStrict(v,a[j])) return false; | ||
return true; | ||
}; | ||
if(!instance.every(testArrays)){ | ||
return "contains duplicate item"; | ||
} | ||
if (!valid) { | ||
return this.createError("does not meet maximum length of " + this.value); | ||
} | ||
}; | ||
Attribute.prototype.validateEnum = function () { | ||
var valid = false; | ||
var i; | ||
if (helpers.isArray(this.value)) { | ||
for (i = 0; i <= this.value.length; i++) { | ||
valid = equal(this.instance, this.value[i]); | ||
// stop as soon as something is valid | ||
if (valid) { | ||
break; | ||
validators.dependencies = function validateDependencies(instance, schema, options, propertyName) { | ||
if (!instance || typeof instance!='object') return; | ||
for(var property in schema.dependencies){ | ||
if(instance[property]===undefined) continue; | ||
var dep = schema.dependencies[property]; | ||
var propPath = propertyName+helpers.makeSuffix(property); | ||
if(typeof dep=='string'){ | ||
dep=[dep]; | ||
} | ||
if(dep instanceof Array){ | ||
for(var i=0; i<dep.length; i++){ | ||
if(instance[dep[i]] === undefined){ | ||
return " property "+dep[i]+" not found, required by "+propPath; | ||
} | ||
} | ||
return; | ||
}else{ | ||
var errs = this.validateSchema(instance, dep, options, propPath); | ||
if(errs&&errs.length){ | ||
return "does not meet dependency required by "+propPath; | ||
} | ||
} | ||
if (!valid) { | ||
return this.createError("is not one of enum values: " + this.value); | ||
} | ||
} else { | ||
return this.createError("enum expects an array"); | ||
} | ||
@@ -208,2 +175,19 @@ }; | ||
module.exports = Attribute; | ||
validators.enum = function validateEnum(instance, schema) { | ||
if (!(schema.enum instanceof Array)) { | ||
return "enum expects an array"; | ||
} | ||
if(!schema.enum.some(helpers.deepCompareStrict.bind(null, instance))){ | ||
return "is not one of enum values: " + schema.enum; | ||
} | ||
}; | ||
validators.disallow = function validateDisallow(instance, schema, options, propertyName) { | ||
var types = (schema.disallow instanceof Array)?schema.disallow:[schema.disallow]; | ||
if (types.some(testType.bind(this, instance, options, propertyName))) { | ||
return "is of prohibited type " + schema.type; | ||
} | ||
}; | ||
module.exports = attribute; |
'use strict'; | ||
var createError = function (schema, validator, propertyName, message, instance) { | ||
var createError = exports.createError = function createError(schema, validator, propertyName, message, instance) { | ||
var result = {}; | ||
@@ -19,51 +19,3 @@ if (validator) result.validator = validator; | ||
var capitalize = function (input) { | ||
return input.charAt(0).toUpperCase() + input.slice(1); | ||
}; | ||
var isString = function (input) { | ||
return typeof input === 'string'; | ||
}; | ||
var isNull = function (input) { | ||
return input === null; | ||
}; | ||
var isFunction = function (input) { | ||
return typeof input === 'function'; | ||
}; | ||
var isArray = function (input) { | ||
return typeof input === 'object' && (input instanceof Array); | ||
}; | ||
var isBoolean = function (input) { | ||
return typeof input === 'boolean'; | ||
}; | ||
var isDefined = function (input) { | ||
return typeof input !== 'undefined'; | ||
}; | ||
var isNumber = function (input) { | ||
return typeof input === 'number'; | ||
}; | ||
var isInteger = function (input) { | ||
return isNumber(input) && input % 1 === 0; | ||
}; | ||
var isObject = function (input) { | ||
return typeof input === 'object'; | ||
}; | ||
var isDate = function (input) { | ||
return typeof input === 'object' && input instanceof Date; | ||
}; | ||
var isRegExp = function (input) { | ||
return typeof input === 'object' && input instanceof RegExp; | ||
}; | ||
var FORMAT_REGEXPS = { | ||
var FORMAT_REGEXPS = exports.FORMAT_REGEXPS = { | ||
'date-time': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, | ||
@@ -76,3 +28,3 @@ 'date': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/, | ||
'ipv6': /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/, | ||
'uri': /^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|cat|coop|int|pro|tel|xxx|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2})?)|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/, | ||
'uri': /^[a-zA-Z][a-zA-Z0-9+-.]*:[^\s]*$/, | ||
@@ -85,3 +37,3 @@ 'color': /(#?([0-9A-Fa-f]{3,6})\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\))/, | ||
'alphanumeric': /^[a-zA-Z0-9]+$/, | ||
'utc-millisec': function (input) { return isString(input) && parseFloat(input) == parseInt(input, 10) && !isNaN(input); }, | ||
'utc-millisec': function (input) { return (typeof input=='string') && parseFloat(input) == parseInt(input, 10) && !isNaN(input); }, | ||
'regex': function (input) { return true; }, // TODO: Needs implementation | ||
@@ -95,8 +47,8 @@ 'style': function (input) { return true; }, // TODO: Needs implementation | ||
var isFormat = function (input, format) { | ||
if (isDefined(FORMAT_REGEXPS[format]) === true) { | ||
if (isRegExp(FORMAT_REGEXPS[format])) { | ||
return input.match(FORMAT_REGEXPS[format]) !== null; | ||
var isFormat = exports.isFormat = function isFormat(input, format) { | ||
if (FORMAT_REGEXPS[format] !== undefined) { | ||
if (FORMAT_REGEXPS[format] instanceof RegExp) { | ||
return FORMAT_REGEXPS[format].test(input); | ||
} | ||
if (isFunction(FORMAT_REGEXPS[format])) { | ||
if (typeof FORMAT_REGEXPS[format]=='function') { | ||
return FORMAT_REGEXPS[format](input); | ||
@@ -108,17 +60,31 @@ } | ||
module.exports = { | ||
createError: createError, | ||
capitalize: capitalize, | ||
isString: isString, | ||
isNull: isNull, | ||
isFunction: isFunction, | ||
isArray: isArray, | ||
isBoolean: isBoolean, | ||
isDefined: isDefined, | ||
isNumber: isNumber, | ||
isInteger: isInteger, | ||
isObject: isObject, | ||
isDate: isDate, | ||
isRegExp: isRegExp, | ||
isFormat: isFormat | ||
}; | ||
exports.makeSuffix = function makeSuffix(key){ | ||
key = key.toString(); | ||
// This function could be capable of outputting valid a ECMAScript string, but the | ||
// resulting code for testing which form to use would be tens of thousands of characters long | ||
// That means this will use the name form for some illegal forms | ||
if(!key.match(/[.\s\[\]]/) && !key.match(/^[\d]/)) { | ||
return '.'+key; | ||
}else if(key.match(/^\d+$/)){ | ||
return '['+key+']'; | ||
}else{ | ||
return '['+JSON.stringify(key)+']'; | ||
} | ||
} | ||
exports.deepCompareStrict = function deepCompareStrict(a, b){ | ||
if(typeof a!=typeof b) return false; | ||
if(a instanceof Array){ | ||
if(!(b instanceof Array)) return false; | ||
if(a.length!=b.length) return false; | ||
return a.every(function(v, i){return deepCompareStrict(a[i], b[i]);}); | ||
} | ||
if(typeof a=='object'){ | ||
if(!a || !b) return a===b; | ||
var akeys = Object.keys(a); | ||
var bkeys = Object.keys(b); | ||
if(akeys.length!=bkeys.length) return false; | ||
return akeys.every(function(v){return deepCompareStrict(a[v], b[v]);}); | ||
} | ||
return a===b; | ||
} |
'use strict'; | ||
var Attribute = require('./attribute'); | ||
var attribute = require('./attribute'); | ||
var helpers = require('./helpers'); | ||
@@ -8,202 +8,135 @@ | ||
this.schemas = {}; | ||
this.errors = []; | ||
return this; | ||
}; | ||
Validator.prototype.addSchema = function (schema, urn) { | ||
var ourUrn = urn; | ||
if (!schema) { | ||
return; | ||
} | ||
if (!urn) { | ||
ourUrn = schema.id; | ||
} | ||
if (ourUrn) { | ||
this.schemas[ourUrn] = schema; | ||
} | ||
Validator.prototype.addSchema = function addSchema(schema, urn) { | ||
if (!schema) return; | ||
var ourUrn = urn||schema.id; | ||
if (ourUrn) this.schemas[ourUrn] = schema; | ||
return this.schemas[ourUrn]; | ||
}; | ||
Validator.prototype.setSchemas = function (schemas) { | ||
Validator.prototype.setSchemas = function setSchemas(schemas) { | ||
this.schemas = schemas; | ||
}; | ||
Validator.prototype.addError = function (error) { | ||
if (error) { | ||
this.errors.push(error); | ||
Validator.prototype.validate = function validate(instance, schema, options, propertyName) { | ||
if(!propertyName) propertyName = 'instance'; | ||
if (schema) { | ||
return this.validateSchema(instance, schema, options, propertyName); | ||
} | ||
throw new Error('no schema specified'); | ||
}; | ||
Validator.prototype.validate = function (instance, schema, options) { | ||
if (helpers.isDefined(schema)) { | ||
if (typeof schema.type === 'object' && (schema.type instanceof Array)) { | ||
this.validateUnionType(instance, schema, options); | ||
Validator.prototype.validateSchema = function validateSchema(instance, schema, options, propertyName) { | ||
if(!schema){ | ||
return [helpers.createError(schema, undefined, undefined, "is undefined")]; | ||
} | ||
var switchSchema = (typeof schema=='string')?schema:schema.$ref; | ||
if(switchSchema) { | ||
if (this.schemas[switchSchema]) { | ||
return this.validateSchema(instance, this.schemas[switchSchema], options, propertyName); | ||
} else { | ||
this.validateSchema(instance, schema, options); | ||
return [helpers.createError(schema, '$ref', propertyName, "no such schema " + switchSchema)]; | ||
} | ||
} | ||
return this.errors; | ||
}; | ||
Validator.prototype.validateSchema = function (instance, schema, options) { | ||
if (helpers.isDefined(schema.type)) { | ||
switch (schema.type) { | ||
case 'string': | ||
case 'number': | ||
case 'integer': | ||
case 'boolean': | ||
case 'null': | ||
case 'date': | ||
case 'any': | ||
return this.validateProperty(instance, schema, options); | ||
case 'object': | ||
return this.validateObject(instance, schema, options); | ||
case 'array': | ||
return this.validateArray(instance, schema, options); | ||
} | ||
var errs = this.validateProperty(instance, schema, options, propertyName); | ||
if(instance instanceof Array){ | ||
return errs.concat(this.validateArray(instance, schema, options, propertyName)); | ||
}else if(instance && (typeof instance)=='object' && Object.getPrototypeOf(instance)===Object.prototype){ | ||
return errs.concat(this.validateObject(instance, schema, options, propertyName)); | ||
}else{ | ||
return errs; | ||
} | ||
if (schema.$ref) { | ||
return this.validateProperty(instance, schema, options); | ||
} | ||
}; | ||
Validator.prototype.validateProperty = function (instance, schema, options) { | ||
var a; | ||
var valid; | ||
if (!helpers.isDefined(options)) { | ||
options = {addError: true, skipAttributes: []}; | ||
} else { | ||
if (!helpers.isDefined(options.addError) || helpers.isNull(options.addError)) { | ||
options.addError = true; | ||
} | ||
if (!helpers.isDefined(options.skipAttributes) || helpers.isNull(options.skipAttributes)) { | ||
options.skipAttributes = []; | ||
} | ||
} | ||
var key, i; | ||
// Validates each schema property against the instance | ||
Validator.prototype.validateProperty = function validateProperty(instance, schema, options, propertyName) { | ||
var self=this; | ||
var errors = []; | ||
var skipAttributes = options&&options.skipAttributes || []; | ||
var keys = Object.keys(schema); | ||
var keysLength = keys.length; | ||
for (i = 0; i < keysLength; i++) { | ||
key = keys[i]; | ||
if (options.skipAttributes.indexOf(key) === -1) { | ||
a = new Attribute(this, key, schema, instance, options.propertyName, options); | ||
valid = a.validate(); | ||
keys.forEach(function(key){ | ||
if (skipAttributes.indexOf(key)<0 && attribute.ignoreProperties.indexOf(key)<0) { | ||
var validatorErr; | ||
var validator = attribute.validators[key]; | ||
if (validator) { | ||
validatorErr = validator.call(self, instance, schema, options, propertyName); | ||
} else { | ||
throw new Error("Unsupported attribute: "+key); | ||
validatorErr = "WARNING: unsupported attribute: "+key; | ||
} | ||
if (options.addError === true) { | ||
this.addError(valid); | ||
if (validatorErr) { | ||
errors.push(helpers.createError(schema, key, propertyName, validatorErr)); | ||
} | ||
} | ||
} | ||
return valid; | ||
}); | ||
return errors; | ||
}; | ||
Validator.prototype.validateObject = function (instance, schema, options) { | ||
var a; | ||
var prop; | ||
Validator.prototype.validateObject = function (instance, schema, options, propertyName) { | ||
var errors = []; | ||
// Only validate undefined's if they are required | ||
if (!helpers.isDefined(instance)) { | ||
if (helpers.isDefined(schema) && schema.required === true) { | ||
this.addError(helpers.createError(schema, undefined, options.propertyName, "is required", instance)); | ||
if (instance===undefined) { | ||
if (schema && schema.required===true) { | ||
errors.push(helpers.createError(schema, undefined, propertyName, "is required", instance)); | ||
} | ||
return; | ||
return errors; | ||
} | ||
if (typeof instance !== 'object') { | ||
if (!helpers.isDefined(options) || (helpers.isDefined(options) && options.addError === true)) { | ||
this.addError(helpers.createError(schema, undefined, undefined, "not an object", instance)); | ||
} | ||
} else { | ||
var property, i; | ||
if (helpers.isDefined(schema.properties)) { | ||
this.validateAdditionalProperties(instance, schema, options); | ||
var keys = Object.keys(schema.properties); | ||
var keysLength = keys.length; | ||
for (i = 0; i < keysLength; i++) { | ||
property = keys[i]; | ||
if (helpers.isDefined(instance) && !helpers.isNull(instance) && helpers.isDefined(instance[property])) { | ||
prop = instance[property]; | ||
} else { | ||
prop = undefined; | ||
} | ||
this.validate(prop, schema.properties[property], {'propertyName': property, 'addError': true}); | ||
} | ||
} | ||
if (typeof instance!=='object' || instance&&Object.getPrototypeOf(instance)!==Object.prototype) { | ||
errors.push(helpers.createError(schema, undefined, undefined, "not an object", instance)); | ||
return errors; | ||
} | ||
}; | ||
Validator.prototype.validateAdditionalProperties = function (instance, schema, options) { | ||
if (!helpers.isDefined(schema.additionalProperties) || schema.additionalProperties === true) { | ||
return; | ||
} | ||
var properties = schema.properties || {}; | ||
var property, i; | ||
var keys = Object.keys(instance); | ||
var keysLength = keys.length; | ||
for (i = 0; i < keysLength; i++) { | ||
property = keys[i]; | ||
if (!helpers.isDefined(schema.properties[property])) { | ||
// true is the same as the default, an empty schema, which needs no validation | ||
if (schema.additionalProperties!==undefined && schema.additionalProperties!==true) { | ||
for(var property in instance){ | ||
if (properties[property]!==undefined) continue; | ||
if (schema.additionalProperties === false) { | ||
this.addError("Property " + property + " does not exist in the schema"); | ||
errors.push("Property " + property + " does not exist in the schema"); | ||
} else { | ||
this.validate(instance[property], schema.additionalProperties, options); | ||
var errs = this.validateSchema(instance[property], schema.additionalProperties, options, propertyName+helpers.makeSuffix(property)); | ||
if(errs&&errs.length) errors=errors.concat(errs); | ||
} | ||
} | ||
} | ||
}; | ||
// FIXME: This will fail if the schema doesn't have 'items' defined | ||
Validator.prototype.validateArray = function (instance, schema, options) { | ||
var a, i, len; | ||
// Don't validate undefined's | ||
if (!helpers.isDefined(instance)) { | ||
return; | ||
} | ||
if (!(instance instanceof Array)) { | ||
this.addError("not an array"); | ||
} else { | ||
if (!options) { | ||
options = {skipAttributes: ['items', 'type']}; | ||
} else { | ||
if (options.skipAttributes === undefined || options.skipAttributes === null) { | ||
options.skipAttributes = ['items', 'type']; | ||
} | ||
var patternProperties = schema.patternProperties || {}; | ||
for(var pattern in patternProperties){ | ||
var expr = new RegExp(pattern); | ||
for(var property in instance){ | ||
if(!expr.test(property)) continue; | ||
var errs = this.validateSchema(instance[property], schema.additionalProperties, options, propertyName+helpers.makeSuffix(property)); | ||
if(errs&&errs.length) errors=errors.concat(errs); | ||
} | ||
this.validateProperty(instance, schema, options); | ||
options.skipAttributes = []; | ||
for (i = 0, len = instance.length; i < len; i++) { | ||
if (options && options.propertyName) { | ||
options.propertyName = options.propertyName.concat("[", i, "]"); | ||
} | ||
this.validate(instance[i], schema.items, options); | ||
} | ||
} | ||
for(var property in properties){ | ||
var prop = (instance||undefined) && instance[property]; | ||
var errs = this.validate(prop, properties[property], options, propertyName+helpers.makeSuffix(property)); | ||
if(errs&&errs.length) errors=errors.concat(errs); | ||
} | ||
return errors; | ||
}; | ||
Validator.prototype.validateUnionType = function (instance, schema, options) { | ||
var i, len; | ||
var numErrors = this.errors.length; | ||
var startingNumErrors = numErrors; | ||
var valid = false; | ||
for (i = 0, len = schema.type.length; i < len; i++) { | ||
if (typeof schema.type[i] === 'string') { | ||
this.validateSchema(instance, {'type': schema.type[i]}); | ||
} else { | ||
this.validateSchema(instance, schema.type[i]); | ||
Validator.prototype.validateArray = function (instance, schema, options, propertyName) { | ||
var self=this; | ||
if (instance===undefined || !schema.items) return []; | ||
var errors = []; | ||
instance.every(function(value, i){ | ||
var items = (schema.items instanceof Array)?(schema.items[i]||schema.additionalItems||false):schema.items; | ||
if(items===false){ | ||
errors.push("additionalItems not permitted"); | ||
return false; | ||
} | ||
if (this.errors.length === numErrors) { | ||
valid = true; | ||
break; | ||
} else { | ||
numErrors = this.errors.length - startingNumErrors; | ||
} | ||
} | ||
if (valid) { | ||
this.errors = this.errors.slice(0, startingNumErrors); | ||
} else { | ||
this.addError("not any of union type"); | ||
} | ||
var errs = self.validateSchema(value, items, options, propertyName+"["+i+"]"); | ||
if(errs&&errs.length) errors=errors.concat(errs); | ||
return true; | ||
}); | ||
return errors; | ||
}; | ||
module.exports = Validator; |
{ | ||
"author": "Tom de Grunt <tom@degrunt.nl>", | ||
"name": "jsonschema", | ||
"version": "0.1.5", | ||
"dependencies": { | ||
"version": "0.2.0", | ||
"dependencies": {}, | ||
"main": "./lib", | ||
"devDependencies": { | ||
"mocha": "~1.3", | ||
"should": "~0.6.3", | ||
"deep-equal": "0.0.0" | ||
"should": "~0.6.3" | ||
}, | ||
"main": "./lib", | ||
"devDependencies": {}, | ||
"optionalDependencies": {}, | ||
@@ -13,0 +12,0 @@ "engines": { |
@@ -28,5 +28,5 @@ [![Build Status](https://secure.travis-ci.org/tdegrunt/jsonschema.png)](http://travis-ci.org/tdegrunt/jsonschema) | ||
| items | ✔ | ✔ | | ||
| additionalItems | ✔ | | | ||
| additionalItems | ✔ | ✔ | | ||
| required | ✔ | ✔ | | ||
| dependencies | ✔ | | | ||
| dependencies | ✔ | ✔ | | ||
| minimum | ✔ | ✔ | | ||
@@ -38,3 +38,3 @@ | maximum | ✔ | ✔ | | ||
| maxItems | ✔ | ✔ | | ||
| uniqueItems | ✔ | | | ||
| uniqueItems | ✔ | ✔ | | ||
| pattern | ✔ | ✔ | | ||
@@ -48,8 +48,8 @@ | minLength | ✔ | ✔ | | ||
| format | ✔ | ✔ | | ||
| divisibleBy | ✔ | | | ||
| disallow | ✔ | | | ||
| divisibleBy | ✔ | ✔ | | ||
| disallow | ✔ | ✔ | | ||
| extends | ✔ | | | ||
| id | ✔ | ✔ | | ||
| $ref | ✔ | ✔ | | ||
| $schema | ✔ | | | ||
| $schema | ✔ | | | ||
@@ -113,2 +113,2 @@ ### Types | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. | ||
SOFTWARE. |
@@ -73,2 +73,28 @@ 'use strict'; | ||
}); | ||
}); | ||
describe('uniqueItems', function () { | ||
beforeEach(function () { | ||
this.validator = new Validator(); | ||
}); | ||
it('should validate if array has no duplicate items', function () { | ||
return this.validator.validate([1], {'type': 'array', 'uniqueItems': true}).should.be.empty; | ||
}); | ||
it('should validate if array has no duplicate objects', function () { | ||
return this.validator.validate([1, 2, "1", "2", {a:1}, {a:1, b:1}], {'type': 'array', 'uniqueItems': true}).should.be.empty; | ||
}); | ||
it('should not validate if array has duplicate numbers', function () { | ||
return this.validator.validate([1, 2, 4, 1, 3, 5], {'type': 'array', 'uniqueItems': true}).should.not.be.empty; | ||
}); | ||
it('should not validate if array has duplicate objects', function () { | ||
return this.validator.validate([{a:1}, {a:1}], {'type': 'array', 'uniqueItems': true}).should.not.be.empty; | ||
}); | ||
it('should validate if not an Array', function () { | ||
return this.validator.validate(null, {'type': 'any', 'uniqueItems': true}).should.be.empty; | ||
}); | ||
}); | ||
}); |
@@ -161,2 +161,20 @@ 'use strict'; | ||
describe('dividibleBy', function () { | ||
beforeEach(function () { | ||
this.validator = new Validator(); | ||
}); | ||
it('should validate if 0 is even', function () { | ||
return this.validator.validate(2, {'type': 'number', 'divisibleBy': 2}).should.be.empty; | ||
}); | ||
it('should validate if -2 is even', function () { | ||
return this.validator.validate(-2, {'type': 'number', 'divisibleBy': 2}).should.be.empty; | ||
}); | ||
it('should not validate 1 is even', function () { | ||
return this.validator.validate(1, {'type': 'number', 'divisibleBy': 2}).should.not.be.empty; | ||
}); | ||
}); | ||
describe('pattern', function () { | ||
@@ -232,3 +250,2 @@ beforeEach(function () { | ||
describe('description', function () { | ||
beforeEach(function () { | ||
@@ -242,2 +259,16 @@ this.validator = new Validator(); | ||
}); | ||
}); | ||
describe('disallow', function () { | ||
beforeEach(function () { | ||
this.validator = new Validator(); | ||
}); | ||
it('should prohibit specified types', function () { | ||
this.validator.validate(1, {'type': 'any', 'disallow':'array'}).should.be.empty; | ||
}); | ||
it('should not prohibit unprohibited types', function () { | ||
this.validator.validate(1, {'type':'any', 'disallow':'array'}).should.be.empty; | ||
}); | ||
}); | ||
}); |
@@ -114,5 +114,9 @@ 'use strict'; | ||
it('should not validate file:///Users/tdegrunt', function () { | ||
this.validator.validate("file:///Users/tdegrunt", {'type': 'string', 'format': 'uri'}).should.not.be.empty; | ||
it('should not validate relative URIs', function () { | ||
this.validator.validate("tdegrunt", {'type': 'string', 'format': 'uri'}).should.not.be.empty; | ||
}); | ||
it('should not validate with whitespace', function () { | ||
this.validator.validate("The dog jumped", {'type': 'string', 'format': 'uri'}).should.not.be.empty; | ||
}); | ||
}); | ||
@@ -217,2 +221,2 @@ | ||
}); | ||
}); | ||
}); |
@@ -51,6 +51,6 @@ 'use strict'; | ||
result[0].should.have.property('message', 'is not string'); | ||
result[0].should.have.property('property', 'lines[0]'); | ||
result[0].should.have.property('property', 'instance.lines[0]'); | ||
}); | ||
}); | ||
}); | ||
}); |
73218
0
27
111
2
1292
- Removeddeep-equal@0.0.0
- Removedmocha@~1.3
- Removedshould@~0.6.3
- Removedcommander@0.6.1(transitive)
- Removeddebug@4.3.7(transitive)
- Removeddeep-equal@0.0.0(transitive)
- Removeddiff@1.0.2(transitive)
- Removedgrowl@1.5.1(transitive)
- Removedjade@0.26.3(transitive)
- Removedmkdirp@0.3.0(transitive)
- Removedmocha@1.3.2(transitive)
- Removedms@2.1.3(transitive)
- Removedshould@0.6.3(transitive)