jsonschema
Advanced tools
Comparing version 0.3.2 to 0.4.0
@@ -5,6 +5,11 @@ 'use strict'; | ||
/** @type ValidatorResult */ | ||
var ValidatorResult = helpers.ValidatorResult; | ||
/** @type SchemaError */ | ||
var SchemaError = helpers.SchemaError; | ||
var attribute = {}; | ||
attribute.ignoreProperties = { | ||
// informativeProperties | ||
// informative properties | ||
'id': true, | ||
@@ -14,10 +19,9 @@ 'default': true, | ||
'title': true, | ||
// argumentProperties | ||
// arguments to other properties | ||
'exclusiveMinimum': true, | ||
'exclusiveMaximum': true, | ||
'items': true, | ||
'additionalItems': true, | ||
'properties': true, | ||
'additionalProperties': true, | ||
'patternProperties': true, | ||
// special-handled properties | ||
'$schema': true, | ||
'$ref': true, | ||
'extends': true | ||
@@ -32,49 +36,60 @@ }; | ||
/** | ||
* Tests whether the instance if of a certain type. | ||
* @private | ||
* Validates whether the instance if of a certain type | ||
* @param instance | ||
* @param schema | ||
* @param options | ||
* @param propertyName | ||
* @param type | ||
* @return {boolean} | ||
* @param ctx | ||
* @return {String|null} | ||
*/ | ||
var testType = function (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'); | ||
// TODO: fix this - see #15 | ||
case 'object': | ||
return (instance && (typeof instance) === 'object' && !(instance instanceof Array) && !(instance instanceof Date)); | ||
case 'array': | ||
return (instance instanceof Array); | ||
case 'null': | ||
return (instance === null); | ||
case 'date': | ||
return (instance instanceof Date); | ||
case 'any': | ||
return true; | ||
validators.type = function validateType (instance, schema, options, ctx) { | ||
// Ignore undefined instances | ||
if (instance === undefined) { | ||
return null; | ||
} | ||
if (type && typeof type === 'object') { | ||
var errs = this.validateSchema(instance, type, options, propertyName); | ||
return !(errs && errs.length); | ||
var types = (schema.type instanceof Array) ? schema.type : [schema.type]; | ||
if (!types.some(this.testType.bind(this, instance, schema, options, ctx))) { | ||
return "is not of a type(s) " + types.map(function (v) { | ||
return v.id && ('<' + v.id + '>') || v.toString(); | ||
}); | ||
} | ||
return null; | ||
}; | ||
return false; | ||
function testSchema(instance, options, ctx, schema){ | ||
return this.validateSchema(instance, schema, options, ctx).valid; | ||
} | ||
/** | ||
* Validates whether the instance matches some of the given schemas | ||
* @param instance | ||
* @param schema | ||
* @param options | ||
* @param ctx | ||
* @return {String|null} | ||
*/ | ||
validators.anyOf = function validateAnyOf (instance, schema, options, ctx) { | ||
// Ignore undefined instances | ||
if (instance === undefined) { | ||
return null; | ||
} | ||
if (!(schema.anyOf instanceof Array)){ | ||
throw new SchemaError("anyOf must be an array"); | ||
} | ||
if (!schema.anyOf.some(testSchema.bind(this, instance, options, ctx))) { | ||
return "is not any of " + schema.anyOf.map(function (v) { | ||
return v.id && ('<' + v.id + '>') || v.toString(); | ||
}); | ||
} | ||
return null; | ||
}; | ||
/** | ||
* Validates whether the instance if of a certain type | ||
* Validates whether the instance matches every given schema | ||
* @param instance | ||
* @param schema | ||
* @param options | ||
* @param propertyName | ||
* @param ctx | ||
* @return {String|null} | ||
*/ | ||
validators.type = function validateType(instance, schema, options, propertyName) { | ||
validators.allOf = function validateAllOf (instance, schema, options, ctx) { | ||
// Ignore undefined instances | ||
@@ -84,6 +99,10 @@ if (instance === undefined) { | ||
} | ||
var types = (schema.type instanceof Array) ? schema.type : [schema.type]; | ||
if (!types.some(testType.bind(this, instance, options, propertyName))) { | ||
return "is not " + schema.type; | ||
if (!(schema.allOf instanceof Array)){ | ||
throw new SchemaError("allOf must be an array"); | ||
} | ||
if (!schema.allOf.every(testSchema.bind(this, instance, options, ctx))) { | ||
return "is not all from " + schema.allOf.map(function (v) { | ||
return v.id && ('<' + v.id + '>') || v.toString(); | ||
}); | ||
} | ||
return null; | ||
@@ -93,2 +112,159 @@ }; | ||
/** | ||
* 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 (!(schema.oneOf instanceof Array)){ | ||
throw new SchemaError("oneOf must be an array"); | ||
} | ||
var count = schema.oneOf.filter(testSchema.bind(this, instance, options, ctx)).length; | ||
if (count!==1) { | ||
return "is not exactly one from " + schema.oneOf.map(function (v) { | ||
return v.id && ('<' + v.id + '>') || v.toString(); | ||
}); | ||
} | ||
return null; | ||
}; | ||
/** | ||
* Validates properties | ||
* @param instance | ||
* @param schema | ||
* @param options | ||
* @param ctx | ||
* @return {String|null|ValidatorResult} | ||
*/ | ||
validators.properties = function validateProperties (instance, schema, options, ctx) { | ||
if(instance === undefined) return; | ||
var result = new ValidatorResult(instance, schema, options, ctx); | ||
var properties = schema.properties || {}; | ||
for (var property in properties) { | ||
var prop = (instance || undefined) && instance[property]; | ||
var res = this.validateSchema(prop, properties[property], options, ctx.makeChild(properties[property], 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-existance 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 (schema.properties && schema.properties[property] !== undefined) { | ||
return; | ||
} | ||
if (schema.additionalProperties === false) { | ||
result.addError("Property " + property + " does not exist in the schema"); | ||
} else { | ||
var additionalProperties = schema.additionalProperties || {}; | ||
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(instance === undefined) return; | ||
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 expr = new RegExp(pattern); | ||
if (!expr.test(property)) { | ||
continue; | ||
} | ||
test = false; | ||
var res = this.validateSchema(instance[property], patternProperties[pattern], options, ctx.makeChild(patternProperties[pattern], 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(instance === undefined) return; | ||
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 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) { | ||
if (!(instance instanceof Array)) { | ||
return null; | ||
} | ||
var self = this; | ||
var result = new ValidatorResult(instance, schema, options, ctx); | ||
if (instance === undefined || !schema.items) { | ||
return result; | ||
} | ||
instance.every(function (value, i) { | ||
var items = (schema.items instanceof Array) ? (schema.items[i] || schema.additionalItems) : schema.items; | ||
if (items === undefined) { | ||
return true; | ||
} | ||
if (items === false) { | ||
result.addError("additionalItems not permitted"); | ||
return false; | ||
} | ||
var res = self.validateSchema(value, items, options, ctx.makeChild(items, i)); | ||
result.instance[i] = res.instance; | ||
result.importErrors(res); | ||
return true; | ||
}); | ||
return result; | ||
}; | ||
/** | ||
* Validates minimum and exclusiveMinimum when the type of the instance value is a number. | ||
@@ -99,3 +275,3 @@ * @param instance | ||
*/ | ||
validators.minimum = function validateMinimum(instance, schema) { | ||
validators.minimum = function validateMinimum (instance, schema) { | ||
if (typeof instance !== 'number') { | ||
@@ -122,3 +298,3 @@ return null; | ||
*/ | ||
validators.maximum = function validateMaximum(instance, schema) { | ||
validators.maximum = function validateMaximum (instance, schema) { | ||
if (typeof instance !== 'number') { | ||
@@ -141,2 +317,4 @@ return null; | ||
* Validates divisibleBy when the type of the instance value is a number. | ||
* Of course, this is susceptible to floating point error since it compares the floating points | ||
* and not the JSON byte sequences to arbitrary precision. | ||
* @param instance | ||
@@ -146,3 +324,3 @@ * @param schema | ||
*/ | ||
validators.divisibleBy = function validateDivisibleBy(instance, schema) { | ||
validators.divisibleBy = function validateDivisibleBy (instance, schema) { | ||
if (typeof instance !== 'number') { | ||
@@ -153,3 +331,3 @@ return null; | ||
if (schema.divisibleBy == 0) { | ||
return "divisibleBy can't be zero"; | ||
throw new SchemaError("divisibleBy cannot be zero"); | ||
} | ||
@@ -169,3 +347,3 @@ | ||
*/ | ||
validators.required = function validateRequired(instance, schema) { | ||
validators.required = function validateRequired (instance, schema) { | ||
if (instance === undefined && schema.required === true) { | ||
@@ -181,5 +359,5 @@ return "is required"; | ||
* @param schema | ||
* @return {*} | ||
* @return {String|null} | ||
*/ | ||
validators.pattern = function validatePattern(instance, schema) { | ||
validators.pattern = function validatePattern (instance, schema) { | ||
if (typeof instance !== 'string') { | ||
@@ -210,5 +388,7 @@ return null; | ||
* @param schema | ||
* @param [options] | ||
* @param [ctx] | ||
* @return {String|null} | ||
*/ | ||
validators.format = function validateFormat(instance, schema) { | ||
validators.format = function validateFormat (instance, schema, options, ctx) { | ||
if (instance === undefined) { | ||
@@ -218,3 +398,3 @@ return null; | ||
if (!helpers.isFormat(instance, schema.format)) { | ||
return "\"" + instance + "\" does not conform to format " + schema.format; | ||
return "does not conform to the '" + schema.format + "' format"; | ||
} | ||
@@ -230,3 +410,3 @@ return null; | ||
*/ | ||
validators.minLength = function validateMinLength(instance, schema) { | ||
validators.minLength = function validateMinLength (instance, schema) { | ||
if (!(typeof instance === 'string')) { | ||
@@ -247,3 +427,3 @@ return null; | ||
*/ | ||
validators.maxLength = function validateMaxLength(instance, schema) { | ||
validators.maxLength = function validateMaxLength (instance, schema) { | ||
if (!(typeof instance === 'string')) { | ||
@@ -264,3 +444,3 @@ return null; | ||
*/ | ||
validators.minItems = function validateMinItems(instance, schema) { | ||
validators.minItems = function validateMinItems (instance, schema) { | ||
if (!(instance instanceof Array)) { | ||
@@ -281,3 +461,3 @@ return null; | ||
*/ | ||
validators.maxItems = function validateMaxItems(instance, schema) { | ||
validators.maxItems = function validateMaxItems (instance, schema) { | ||
if (!(instance instanceof Array)) { | ||
@@ -293,2 +473,27 @@ return null; | ||
/** | ||
* Validates that every item in an instance array is unique, when instance is an array | ||
* @param instance | ||
* @param schema | ||
* @param options | ||
* @param ctx | ||
* @return {String|null|ValidatorResult} | ||
*/ | ||
validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) { | ||
var result = new ValidatorResult(instance, schema, options, ctx); | ||
if (!(instance instanceof Array)) { | ||
return result; | ||
} | ||
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)) { | ||
result.addError("contains duplicate item"); | ||
} | ||
return result; | ||
}; | ||
/** | ||
* Deep compares arrays for duplicates | ||
@@ -301,3 +506,3 @@ * @param v | ||
*/ | ||
function testArrays(v, i, a) { | ||
function testArrays (v, i, a) { | ||
var j, len = a.length; | ||
@@ -317,3 +522,3 @@ for (j = i + 1, len; j < len; j++) { | ||
*/ | ||
validators.uniqueItems = function validateUniqueItems(instance) { | ||
validators.uniqueItems = function validateUniqueItems (instance) { | ||
if (!(instance instanceof Array)) { | ||
@@ -334,36 +539,35 @@ return null; | ||
* @param options | ||
* @param propertyName | ||
* @return {String|null} | ||
* @param ctx | ||
* @return {String|null|ValidatorResult} | ||
*/ | ||
validators.dependencies = function validateDependencies(instance, schema, options, propertyName) { | ||
if (!instance || typeof instance !== 'object') { | ||
validators.dependencies = function validateDependencies (instance, schema, options, ctx) { | ||
var result = new ValidatorResult(instance, schema, options, ctx); | ||
if (!instance || typeof instance != 'object') { | ||
return null; | ||
} | ||
var property; | ||
for (property in schema.dependencies) { | ||
if (schema.dependencies.hasOwnProperty(property)) { | ||
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) { | ||
var i, len = dep.length; | ||
for (i = 0, len; i < len; i++) { | ||
if (instance[dep[i]] === undefined) { | ||
return " property " + dep[i] + " not found, required by " + propPath; | ||
} | ||
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 (dep instanceof Array) { | ||
dep.forEach(function (prop) { | ||
if (instance[prop] === undefined) { | ||
result.addError("property " + prop + " not found, required by " + childContext.propertyPath); | ||
} | ||
} else { | ||
var errs = this.validateSchema(instance, dep, options, propPath); | ||
if (errs && errs.length) { | ||
return "does not meet dependency required by " + propPath; | ||
} | ||
}); | ||
} else { | ||
var res = this.validateSchema(instance, dep, options, childContext); | ||
result.instance[property] = res.instance; | ||
if (res && res.errors.length) { | ||
result.addError("does not meet dependency required by " + childContext.propertyPath); | ||
result.importErrors(res); | ||
} | ||
} | ||
} | ||
return null; | ||
return result; | ||
}; | ||
@@ -378,6 +582,9 @@ | ||
*/ | ||
validators.enum = function validateEnum(instance, schema) { | ||
validators.enum = function validateEnum (instance, schema) { | ||
if (!(schema.enum instanceof Array)) { | ||
return "enum expects an array"; | ||
throw new SchemaError("enum expects an array", schema); | ||
} | ||
if (instance === undefined) { | ||
instance = schema.default; | ||
} | ||
if (!schema.enum.some(helpers.deepCompareStrict.bind(null, instance))) { | ||
@@ -394,14 +601,18 @@ return "is not one of enum values: " + schema.enum; | ||
* @param options | ||
* @param propertyName | ||
* @return {String|null} | ||
* @param ctx | ||
* @return {String|null|ValidatorResult} | ||
*/ | ||
validators.disallow = function validateDisallow(instance, schema, options, propertyName) { | ||
validators.not = validators.disallow = function validateNot (instance, schema, options, ctx) { | ||
var self = this; | ||
var result = new ValidatorResult(instance, schema, options, ctx); | ||
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; | ||
} | ||
return null; | ||
types.forEach(function (type) { | ||
if (self.testType(instance, schema, options, ctx, type)) { | ||
var schemaId = type && type.id && ('<' + type.id + '>') || type.toString(); | ||
result.addError("is of prohibited type " + schemaId); | ||
} | ||
}); | ||
return result; | ||
}; | ||
module.exports = attribute; |
'use strict'; | ||
exports.createError = function createError(schema, validator, propertyName, message, instance) { | ||
var result = {}; | ||
if (validator) { | ||
result.validator = validator; | ||
var uri = require('url'); | ||
var ValidationError = exports.ValidationError = function ValidationError (message, instance, schema, propertyPath) { | ||
if (propertyPath) { | ||
this.property = propertyPath; | ||
} | ||
if (propertyName) { | ||
result.property = propertyName; | ||
} | ||
if (message) { | ||
result.message = message; | ||
this.message = message; | ||
} | ||
if (schema) { | ||
if (schema.id) { | ||
result.schema = schema.id; | ||
this.schema = schema.id; | ||
} else { | ||
result.schema = schema; | ||
this.schema = schema; | ||
} | ||
} | ||
if (instance) { | ||
result.instance = instance; | ||
this.instance = instance; | ||
} | ||
return result; | ||
this.stack = this.toString(); | ||
}; | ||
ValidationError.prototype.toString = function toString() { | ||
return this.property + ' ' + this.message; | ||
}; | ||
var ValidatorResult = exports.ValidatorResult = function ValidatorResult(instance, schema, options, ctx) { | ||
this.instance = instance; | ||
this.schema = schema; | ||
this.propertyPath = ctx.propertyPath; | ||
this.errors = []; | ||
this.throwError = options && options.throwError; | ||
}; | ||
ValidatorResult.prototype.addError = function addError(message) { | ||
var err = new ValidationError(message, this.instance, this.schema, this.propertyPath); | ||
if (this.throwError) { | ||
throw err; | ||
} | ||
this.errors.push(err); | ||
return err; | ||
}; | ||
ValidatorResult.prototype.importErrors = function importErrors(res) { | ||
if (typeof res == 'string') { | ||
this.addError(res); | ||
} else if (res && res.errors) { | ||
var errs = this.errors; | ||
res.errors.forEach(function (v) { | ||
errs.push(v) | ||
}); | ||
} | ||
}; | ||
ValidatorResult.prototype.toString = function toString(res) { | ||
return this.errors.map(function(v,i){ return i+': '+v.toString()+'\n'; }).join(''); | ||
}; | ||
Object.defineProperty(ValidatorResult.prototype, "valid", { get: function() { | ||
return !this.errors.length; | ||
} }); | ||
/** | ||
* Describes a problem with a Schema which prevents validation of an instance | ||
* @name SchemaError | ||
* @constructor | ||
*/ | ||
var SchemaError = exports.SchemaError = function SchemaError (msg, schema) { | ||
this.message = msg; | ||
this.schema = schema; | ||
Error.call(this, msg); | ||
Error.captureStackTrace(this, SchemaError); | ||
}; | ||
SchemaError.prototype = Object.create(Error.prototype, | ||
{ constructor: {value: SchemaError, enumerable: false} | ||
, name: {value: 'SchemaError', enumerable: false} | ||
}); | ||
var SchemaContext = exports.SchemaContext = function SchemaContext (schema, options, propertyPath, base, schemas) { | ||
this.schema = schema; | ||
this.options = options; | ||
this.propertyPath = propertyPath; | ||
this.base = base; | ||
this.schemas = schemas; | ||
}; | ||
SchemaContext.prototype.resolve = function resolve (target) { | ||
return uri.resolve(this.base, target); | ||
}; | ||
SchemaContext.prototype.makeChild = function makeChild(schema, propertyName){ | ||
var propertyPath = (propertyName===undefined) ? this.propertyPath : this.propertyPath+makeSuffix(propertyName); | ||
var base = uri.resolve(this.base, schema.id||''); | ||
var ctx = new SchemaContext(schema, this.options, propertyPath, base, Object.create(this.schemas)); | ||
if(schema.id && !ctx.schemas[base]){ | ||
ctx.schemas[base] = schema; | ||
} | ||
return ctx; | ||
} | ||
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$/, | ||
'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$/, | ||
'date': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/, | ||
@@ -48,3 +124,7 @@ 'time': /^\d{2}:\d{2}:\d{2}$/, | ||
var result = true; | ||
try { new RegExp(input); } catch(e) { result = false; } | ||
try { | ||
new RegExp(input); | ||
} catch (e) { | ||
result = false; | ||
} | ||
return result; | ||
@@ -57,5 +137,6 @@ }, | ||
FORMAT_REGEXPS.regexp = FORMAT_REGEXPS.regex; | ||
FORMAT_REGEXPS.pattern = FORMAT_REGEXPS.regex; | ||
FORMAT_REGEXPS.ipv4 = FORMAT_REGEXPS['ip-address']; | ||
exports.isFormat = function isFormat(input, format) { | ||
exports.isFormat = function isFormat (input, format) { | ||
if (FORMAT_REGEXPS[format] !== undefined) { | ||
@@ -72,3 +153,3 @@ if (FORMAT_REGEXPS[format] instanceof RegExp) { | ||
exports.makeSuffix = function makeSuffix(key) { | ||
var makeSuffix = exports.makeSuffix = function makeSuffix (key) { | ||
key = key.toString(); | ||
@@ -87,3 +168,3 @@ // This function could be capable of outputting valid a ECMAScript string, but the | ||
exports.deepCompareStrict = function deepCompareStrict(a, b) { | ||
exports.deepCompareStrict = function deepCompareStrict (a, b) { | ||
if (typeof a !== typeof b) { | ||
@@ -107,8 +188,8 @@ return false; | ||
} | ||
var akeys = Object.keys(a); | ||
var bkeys = Object.keys(b); | ||
if (akeys.length !== bkeys.length) { | ||
var aKeys = Object.keys(a); | ||
var bKeys = Object.keys(b); | ||
if (aKeys.length !== bKeys.length) { | ||
return false; | ||
} | ||
return akeys.every(function (v) { | ||
return aKeys.every(function (v) { | ||
return deepCompareStrict(a[v], b[v]); | ||
@@ -121,9 +202,9 @@ }); | ||
module.exports.deepMerge = function deepMerge (target, src) { | ||
var array = Array.isArray(src) | ||
var dst = array && [] || {} | ||
var array = Array.isArray(src); | ||
var dst = array && [] || {}; | ||
if (array) { | ||
target = target || [] | ||
dst = dst.concat(target) | ||
src.forEach(function(e, i) { | ||
target = target || []; | ||
dst = dst.concat(target); | ||
src.forEach(function (e, i) { | ||
if (typeof e === 'object') { | ||
@@ -136,16 +217,16 @@ dst[i] = deepMerge(target[i], e) | ||
} | ||
}) | ||
}); | ||
} else { | ||
if (target && typeof target === 'object') { | ||
Object.keys(target).forEach(function (key) { | ||
dst[key] = target[key] | ||
}) | ||
dst[key] = target[key]; | ||
}); | ||
} | ||
Object.keys(src).forEach(function (key) { | ||
if (typeof src[key] !== 'object' || !src[key]) { | ||
dst[key] = src[key] | ||
dst[key] = src[key]; | ||
} | ||
else { | ||
if (!target[key]) { | ||
dst[key] = src[key] | ||
dst[key] = src[key]; | ||
} else { | ||
@@ -155,21 +236,35 @@ dst[key] = deepMerge(target[key], src[key]) | ||
} | ||
}) | ||
}); | ||
} | ||
return dst | ||
} | ||
return dst; | ||
}; | ||
exports.objectGetPath = function(o, s) { | ||
s = s.replace(/\[(\w+)\]/g, '/$1'); | ||
s = s.replace(/^\//, ''); | ||
var a = s.split('/'); | ||
while (a.length) { | ||
var n = a.shift(); | ||
if (n in o) { | ||
o = o[n]; | ||
} else { | ||
return; | ||
} | ||
/** | ||
* Validates instance against the provided schema | ||
* Implements URI+JSON Pointer encoding, e.g. "%7e"="~0"=>"~", "~1"="%2f"=>"/" | ||
* @param o | ||
* @param s The path to walk o along | ||
* @return any | ||
*/ | ||
exports.objectGetPath = function objectGetPath(o, s) { | ||
var parts = s.split('/').slice(1); | ||
var k; | ||
while (typeof (k=parts.shift()) == 'string') { | ||
var n = decodeURIComponent(k.replace(/~0/,'~').replace(/~1/g,'/')); | ||
if (!(n in o)) return; | ||
o = o[n]; | ||
} | ||
return o; | ||
}; | ||
/** | ||
* Accept an Array of property names and return a JSON Pointer URI fragment | ||
* @param Array a | ||
* @return {String} | ||
*/ | ||
exports.encodePath = function encodePointer(a){ | ||
// ~ must be encoded explicitly because hacks | ||
// the slash is encoded by encodeURIComponent | ||
return a.map(function(v){ return '/'+encodeURIComponent(v).replace(/~/g,'%7E'); }).join(''); | ||
} |
@@ -5,7 +5,9 @@ 'use strict'; | ||
module.exports.Environment = require('./environment'); | ||
module.exports.ValidatorResult = require('./helpers').ValidatorResult; | ||
module.exports.ValidationError = require('./helpers').ValidationError; | ||
module.exports.SchemaError = require('./helpers').SchemaError; | ||
module.exports.validate = function (instance, schema) { | ||
module.exports.validate = function (instance, schema, options) { | ||
var v = new Validator(); | ||
return v.validate(instance, schema); | ||
}; | ||
return v.validate(instance, schema, options); | ||
}; |
'use strict'; | ||
var urilib = require('url'); | ||
var attribute = require('./attribute'); | ||
var helpers = require('./helpers'); | ||
var ValidatorResult = helpers.ValidatorResult; | ||
var SchemaError = helpers.SchemaError; | ||
var SchemaContext = helpers.SchemaContext; | ||
@@ -11,7 +16,17 @@ /** | ||
*/ | ||
var Validator = function Validator() { | ||
var Validator = function Validator () { | ||
this.schemas = {}; | ||
return this; | ||
this.unresolvedRefs = []; | ||
// Use Object.create to make this extensible without Validator instances stepping on each other's toes. | ||
this.types = Object.create(types); | ||
this.attributes = Object.create(attribute.validators); | ||
}; | ||
// Hint at the presence of a property | ||
Validator.prototype.schemas = null; | ||
Validator.prototype.types = null; | ||
Validator.prototype.attributes = null; | ||
Validator.prototype.unresolvedRefs = null; | ||
/** | ||
@@ -23,13 +38,64 @@ * Adds a schema with a certain urn to the Validator instance. | ||
*/ | ||
Validator.prototype.addSchema = function addSchema(schema, urn) { | ||
Validator.prototype.addSchema = function addSchema (schema, uri) { | ||
if (!schema) { | ||
return null; | ||
} | ||
var ourUrn = urn || schema.id; | ||
if (ourUrn) { | ||
this.schemas[ourUrn] = schema; | ||
var ourUri = uri || schema.id; | ||
this.addSubSchema(ourUri, schema); | ||
if (ourUri) { | ||
this.schemas[ourUri] = schema; | ||
} | ||
return this.schemas[ourUrn]; | ||
return this.schemas[ourUri]; | ||
}; | ||
Validator.prototype.addSubSchema = function addSubSchema(baseuri, schema) { | ||
if(!schema || typeof schema!='object') return; | ||
// Mark all referenced schemas so we can tell later which schemas are referred to, but never defined | ||
if(schema.$ref){ | ||
var resolvedUri = urilib.resolve(baseuri, schema.$ref); | ||
this.schemas[resolvedUri] = null; | ||
this.unresolvedRefs.push(resolvedUri); | ||
return; | ||
} | ||
var ourUri = schema.id && urilib.resolve(baseuri, schema.id); | ||
var ourBase = ourUri || baseuri; | ||
if (ourUri) { | ||
if(this.schemas[ourUri]){ | ||
if(!helpers.deepCompareStrict(this.schemas[ourUri], schema)){ | ||
throw new Error('Schema <'+schema+'> already exists with different definition'); | ||
} | ||
return this.schemas[ourUri]; | ||
} | ||
this.schemas[ourUri] = schema; | ||
} | ||
this.addSubSchemaArray(ourBase, ((schema.items instanceof Array)?schema.items:[schema.items])); | ||
this.addSubSchema(ourBase, schema.additionalItems); | ||
this.addSubSchemaObject(ourBase, schema.properties); | ||
this.addSubSchema(ourBase, schema.additionalProperties); | ||
this.addSubSchemaObject(ourBase, schema.definitions); | ||
this.addSubSchemaObject(ourBase, schema.patternProperties); | ||
this.addSubSchemaObject(ourBase, schema.dependencies); | ||
this.addSubSchemaArray(ourBase, schema.allOf); | ||
this.addSubSchemaArray(ourBase, schema.anyOf); | ||
this.addSubSchemaArray(ourBase, schema.oneOf); | ||
this.addSubSchema(ourBase, schema.not); | ||
return this.schemas[ourUri]; | ||
}; | ||
Validator.prototype.addSubSchemaArray = function addSubSchemaArray(baseuri, schemas) { | ||
if(!(schemas instanceof Array)) return; | ||
for(var i=0; i<schemas.length; i++){ | ||
this.addSubSchema(baseuri, schemas[i]); | ||
} | ||
} | ||
Validator.prototype.addSubSchemaObject = function addSubSchemaArray(baseuri, schemas) { | ||
if(!schemas || typeof schemas!='object') return; | ||
for(var p in schemas){ | ||
this.addSubSchema(baseuri, schemas[p]); | ||
} | ||
} | ||
/** | ||
@@ -39,3 +105,3 @@ * Sets all the schemas of the Validator instance. | ||
*/ | ||
Validator.prototype.setSchemas = function setSchemas(schemas) { | ||
Validator.prototype.setSchemas = function setSchemas (schemas) { | ||
this.schemas = schemas; | ||
@@ -48,3 +114,3 @@ }; | ||
*/ | ||
Validator.prototype.getSchema = function getSchema(urn) { | ||
Validator.prototype.getSchema = function getSchema (urn) { | ||
return this.schemas[urn]; | ||
@@ -58,16 +124,26 @@ }; | ||
* @param [options] | ||
* @param [propertyName] | ||
* @param [ctx] | ||
* @return {Array} | ||
*/ | ||
Validator.prototype.validate = function validate(instance, schema, options, propertyName) { | ||
if (!propertyName) { | ||
propertyName = 'instance'; | ||
Validator.prototype.validate = function validate (instance, schema, options, ctx) { | ||
if (!options) { | ||
options = {}; | ||
} | ||
var propertyName = options.propertyName || 'instance'; | ||
// This will work so long as the function at uri.resolve() will resolve a relative URI to a relative URI | ||
var base = urilib.resolve(options.base||'/', schema.id||''); | ||
if(!ctx){ | ||
ctx = new SchemaContext(schema, options, propertyName, base, Object.create(this.schemas)); | ||
if (!ctx.schemas[base]) { | ||
ctx.schemas[base] = schema; | ||
} | ||
} | ||
if (schema) { | ||
if (!this.schemas['#']) { | ||
this.addSchema(schema, '#'); | ||
var result = this.validateSchema(instance, schema, options, ctx); | ||
if (!result) { | ||
throw new Error('Result undefined'); | ||
} | ||
return this.validateSchema(instance, schema, options, propertyName); | ||
return result; | ||
} | ||
throw new Error('no schema specified'); | ||
throw new SchemaError('no schema specified', schema); | ||
}; | ||
@@ -80,193 +156,159 @@ | ||
* @param options | ||
* @param propertyName | ||
* @param ctx | ||
* @private | ||
* @return {Array} | ||
* @return {ValidatorResult} | ||
*/ | ||
Validator.prototype.validateSchema = function validateSchema(instance, schema, options, propertyName) { | ||
Validator.prototype.validateSchema = function validateSchema (instance, schema, options, ctx) { | ||
var self = this; | ||
var result = new ValidatorResult(instance, schema, options, ctx); | ||
if (!schema) { | ||
return [helpers.createError(schema, undefined, undefined, "is undefined")]; | ||
throw new Error("schema is undefined"); | ||
} | ||
/** | ||
* @param Object schema | ||
* @return mixed schema uri or false | ||
*/ | ||
function shouldResolve(schema) { | ||
var ref = (typeof schema === 'string') ? schema : schema.$ref; | ||
if (typeof ref=='string') return ref; | ||
return false; | ||
} | ||
/** | ||
* @param Object schema | ||
* @param SchemaContext ctx | ||
* @returns Object schema or resolved schema | ||
*/ | ||
function resolve(schema, ctx) { | ||
var ref; | ||
if(ref = shouldResolve(schema)) { | ||
return self.resolve(schema, ref, ctx).subschema; | ||
} | ||
return schema; | ||
} | ||
if (schema.extends) { | ||
if(schema.extends instanceof Array) { | ||
schema.extends.forEach(function(s) { | ||
schema = helpers.deepMerge(schema, s); | ||
if (schema.extends instanceof Array) { | ||
schema.extends.forEach(function (s) { | ||
schema = helpers.deepMerge(schema, resolve(s, ctx)); | ||
}); | ||
} else { | ||
schema = helpers.deepMerge(schema, schema.extends); | ||
schema = helpers.deepMerge(schema, resolve(schema.extends, ctx)); | ||
} | ||
} | ||
var switchSchema = (typeof schema === 'string') ? schema : schema.$ref; | ||
if (switchSchema) { | ||
if (this.schemas[switchSchema]) { | ||
return this.validateSchema(instance, this.schemas[switchSchema], options, propertyName); | ||
} | ||
if (switchSchema.substr(0,2) == '#/') { | ||
schema = helpers.objectGetPath(this.schemas['#'], switchSchema.substr(1)); | ||
return this.validateSchema(instance, schema, options, propertyName); | ||
} | ||
return [helpers.createError(schema, '$ref', propertyName, "no such schema " + switchSchema)]; | ||
var switchSchema; | ||
if (switchSchema = shouldResolve(schema)) { | ||
var resolved = this.resolve(schema, switchSchema, ctx); | ||
var subctx = new SchemaContext(resolved.subschema, options, ctx.propertyPath, resolved.switchSchema, ctx.schemas); | ||
return this.validateSchema(instance, resolved.subschema, options, subctx); | ||
} | ||
var errs = this.validateProperty(instance, schema, options, propertyName); | ||
if (instance instanceof Array) { | ||
return errs.concat(this.validateArray(instance, schema, options, propertyName)); | ||
} | ||
// TODO: fix this - see #15 | ||
if (instance && (typeof instance) === 'object' && !(instance instanceof Array) && !(instance instanceof Date)) { | ||
return errs.concat(this.validateObject(instance, schema, options, propertyName)); | ||
} | ||
return errs; | ||
}; | ||
/** | ||
* Validates each schema property against the instance | ||
* @param instance | ||
* @param schema | ||
* @param options | ||
* @param propertyName | ||
* @private | ||
* @return {Array} | ||
*/ | ||
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 key; | ||
for (var i = 0, len = keys.length; i < len; i++ ) { | ||
key = keys[i]; | ||
var skipAttributes = options && options.skipAttributes || []; | ||
// Validate each schema attribute against the instance | ||
for (var key in schema) { | ||
if (!attribute.ignoreProperties[key] && skipAttributes.indexOf(key) < 0) { | ||
var validatorErr; | ||
var validator = attribute.validators[key]; | ||
var validator = self.attributes[key]; | ||
if (validator) { | ||
validatorErr = validator.call(self, instance, schema, options, propertyName); | ||
} else { | ||
validatorErr = "WARNING: unsupported attribute: " + key; | ||
validatorErr = validator.call(self, instance, schema, options, ctx); | ||
} else if (options.allowUnknownAttributes === false) { | ||
// This represents an error with the schema itself, not an invalid instance | ||
throw new SchemaError("Unsupported attribute: " + key, schema); | ||
} | ||
if (validatorErr) { | ||
errors.push(helpers.createError(schema, key, propertyName, validatorErr)); | ||
result.importErrors(validatorErr); | ||
} | ||
} | ||
} | ||
return errors; | ||
if (typeof options.rewrite == 'function') { | ||
var value = options.rewrite.call(this, instance, schema, options, ctx); | ||
result.instance = value; | ||
} | ||
return result; | ||
}; | ||
/** | ||
* Validates an object against the schema | ||
* @param instance | ||
* @param schema | ||
* @param options | ||
* @param propertyName | ||
* @private | ||
* @return {Array} | ||
*/ | ||
Validator.prototype.validateObject = function (instance, schema, options, propertyName) { | ||
var errors = []; | ||
// Only validate undefined's if they are required | ||
if (instance === undefined) { | ||
if (schema && schema.required === true) { | ||
errors.push(helpers.createError(schema, undefined, propertyName, "is required", instance)); | ||
} | ||
return errors; | ||
* @private | ||
* @param Object schema | ||
* @param Object switchSchema | ||
* @param SchemaContext ctx | ||
* @return Object resolved schemas {subschema:String, switchSchema: String} | ||
* @thorws SchemaError | ||
*/ | ||
Validator.prototype.resolve = function resolve (schema, switchSchema, ctx) { | ||
switchSchema = ctx.resolve(switchSchema); | ||
// First see if the schema exists under the provided URI | ||
if (ctx.schemas[switchSchema]) { | ||
return {subschema: ctx.schemas[switchSchema], switchSchema: switchSchema}; | ||
} | ||
// TODO: fix this - see #15 | ||
if (!(instance && (typeof instance) === 'object' && !(instance instanceof Array) && !(instance instanceof Date))) { | ||
errors.push(helpers.createError(schema, undefined, undefined, "not an object", instance)); | ||
return errors; | ||
// Else try walking the property pointer | ||
var parsed = urilib.parse(switchSchema); | ||
var fragment = parsed && parsed.hash; | ||
var document = fragment && fragment.length && switchSchema.substr(0, switchSchema.length - fragment.length); | ||
if (!document || !ctx.schemas[document]) { | ||
throw new SchemaError("no such schema <" + switchSchema + ">", schema); | ||
} | ||
var properties = schema.properties || {}; | ||
var property; | ||
var errs; | ||
var lookedAtPatternProperties = {}; | ||
if (schema.patternProperties) { | ||
var patternProperties = schema.patternProperties || {}; | ||
var pattern; | ||
for (pattern in patternProperties) { | ||
if (patternProperties.hasOwnProperty(pattern)) { | ||
var expr = new RegExp(pattern); | ||
for (property in instance) { | ||
if (instance.hasOwnProperty(property)) { | ||
if (!expr.test(property)) { | ||
continue; | ||
} | ||
lookedAtPatternProperties[property] = true; | ||
errs = this.validateSchema(instance[property], schema.patternProperties[pattern], options, propertyName + helpers.makeSuffix(property)); | ||
if (errs && errs.length) { | ||
errors = errors.concat(errs); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
var subschema = helpers.objectGetPath(ctx.schemas[document], fragment.substr(1)); | ||
if(subschema===undefined){ | ||
throw new SchemaError("no such schema " + fragment + " located in <" + document + ">", schema); | ||
} | ||
// true is the same as the default, an empty schema, which needs no validation | ||
if (schema.additionalProperties !== undefined && schema.additionalProperties !== true) { | ||
for (property in instance) { | ||
if (instance.hasOwnProperty(property)) { | ||
if (properties[property] !== undefined || lookedAtPatternProperties[property]) { | ||
continue; | ||
} | ||
if (schema.additionalProperties === false) { | ||
errors.push("Property " + property + " does not exist in the schema"); | ||
} else { | ||
errs = this.validateSchema(instance[property], schema.additionalProperties, options, propertyName + helpers.makeSuffix(property)); | ||
if (errs && errs.length) { | ||
errors = errors.concat(errs); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
for (property in properties) { | ||
if (properties.hasOwnProperty(property)) { | ||
var prop = (instance || undefined) && instance[property]; | ||
errs = this.validate(prop, properties[property], options, propertyName + helpers.makeSuffix(property)); | ||
if (errs && errs.length) { | ||
errors = errors.concat(errs); | ||
} | ||
} | ||
} | ||
return errors; | ||
}; | ||
return {subschema: subschema, switchSchema: switchSchema}; | ||
} | ||
/** | ||
* Validates an array against the schema | ||
* Tests whether the instance if of a certain type. | ||
* @private | ||
* @param instance | ||
* @param schema | ||
* @param options | ||
* @param propertyName | ||
* @private | ||
* @return {Array} | ||
* @param ctx | ||
* @param type | ||
* @return {boolean} | ||
*/ | ||
Validator.prototype.validateArray = function (instance, schema, options, propertyName) { | ||
var self = this; | ||
if (instance === undefined || !schema.items) { | ||
return []; | ||
Validator.prototype.testType = function validateType (instance, schema, options, ctx, type) { | ||
if (typeof this.types[type] == 'function') { | ||
return this.types[type].call(this, instance); | ||
} | ||
var errors = []; | ||
instance.every(function (value, i) { | ||
var items = (schema.items instanceof Array) ? (schema.items[i] || schema.additionalItems) : schema.items; | ||
if (items === false) { | ||
errors.push("additionalItems not permitted"); | ||
return false; | ||
} | ||
if (items) { | ||
var errs = self.validateSchema(value, items, options, propertyName + "[" + i + "]"); | ||
if (errs && errs.length) { | ||
errors = errors.concat(errs); | ||
} | ||
return true; | ||
} | ||
}); | ||
return errors; | ||
if (type && typeof type == 'object') { | ||
var res = this.validateSchema(instance, type, options, ctx); | ||
return res === undefined || !(res && res.errors.length); | ||
} | ||
// Undefined or properties not on the list are acceptable, same as not being defined | ||
return true; | ||
}; | ||
var types = Validator.prototype.types = {}; | ||
types.string = function testString (instance) { | ||
return typeof instance == 'string'; | ||
}; | ||
types.number = function testNumber (instance) { | ||
return typeof instance == 'number'; | ||
}; | ||
types.integer = function testInteger (instance) { | ||
return (typeof instance == 'number') && instance % 1 === 0; | ||
}; | ||
types.boolean = function testBoolean (instance) { | ||
return typeof instance == 'boolean'; | ||
}; | ||
types.number = function testNumber (instance) { | ||
return typeof instance == 'number'; | ||
}; | ||
types.array = function testArray (instance) { | ||
return instance instanceof Array; | ||
}; | ||
types.null = function testNull (instance) { | ||
return instance === null; | ||
}; | ||
types.date = function testDate (instance) { | ||
return instance instanceof Date; | ||
}; | ||
types.any = function testAny (instance) { | ||
return true; | ||
}; | ||
types.object = function testObject (instance) { | ||
// TODO: fix this - see #15 | ||
return instance && (typeof instance) === 'object' && !(instance instanceof Array) && !(instance instanceof Date); | ||
}; | ||
module.exports = Validator; |
{ | ||
"author": "Tom de Grunt <tom@degrunt.nl>", | ||
"name": "jsonschema", | ||
"version": "0.3.2", | ||
"version": "0.4.0", | ||
"dependencies": { | ||
@@ -9,4 +9,4 @@ }, | ||
"devDependencies": { | ||
"mocha": "~1.3", | ||
"chai": "~1.4.0" | ||
"mocha": "~1.8.2", | ||
"chai": "~1.5.0" | ||
}, | ||
@@ -13,0 +13,0 @@ "optionalDependencies": {}, |
216
README.md
[![Build Status](https://secure.travis-ci.org/tdegrunt/jsonschema.png)](http://travis-ci.org/tdegrunt/jsonschema) | ||
# jsonschema | ||
Simple and fast [JSON schema](http://tools.ietf.org/html/draft-zyp-json-schema-03) validator. | ||
The latest IETF published draft is v3. This library is v3 compatible, but has some v4 additions. | ||
We aim to fully support v4 once it is is published. | ||
## Usage | ||
### Simple | ||
Simple object validation using JSON schemas. | ||
```javascript | ||
var v = new Validator(); | ||
var instance = 4; | ||
var schema = {"type": "number"}; | ||
console.log(v.validate(instance, schema)); | ||
var Validator = require('jsonschema').Validator; | ||
var v = new Validator(); | ||
var instance = 4; | ||
var schema = {"type": "number"}; | ||
console.log(v.validate(instance, schema)); | ||
``` | ||
### Even simpler | ||
```javascript | ||
var validate = require('jsonschema').validate; | ||
console.log(validate(4, {"type": "number"})); | ||
``` | ||
### Complex example, with split schemas and references | ||
```javascript | ||
var Validator = require('jsonschema').Validator; | ||
var v = new Validator(); | ||
// Address, to be embedded on Person | ||
var addressSchema = { | ||
"id": "/SimpleAddress", | ||
"type": "object", | ||
"properties": { | ||
"lines": { | ||
"type": "array", | ||
"items": {"type": "string"} | ||
}, | ||
"zip": {"type": "string"}, | ||
"city": {"type": "string"}, | ||
"country": {"type": "string", "required": true} | ||
} | ||
}; | ||
// Person | ||
var schema = { | ||
"id": "/SimplePerson", | ||
"type": "object", | ||
"properties": { | ||
"name": {"type": "string"}, | ||
"address": {"$ref": "/SimpleAddress"}, | ||
"votes": {"type": "integer", "minimum": 1} | ||
} | ||
}; | ||
var p = { | ||
"name": "Barack Obama", | ||
"address": { | ||
"lines": [ "1600 Pennsylvania Avenue Northwest" ], | ||
"zip": "DC 20500", | ||
"city": "Washington", | ||
"country": "USA" | ||
}, | ||
"votes": "lots" | ||
}; | ||
v.addSchema(addressSchema, '/SimpleAddress'); | ||
console.log(v.validate(p, schema)); | ||
``` | ||
## Features | ||
### Definitions | ||
All schema definitions are supported, $schema is ignored. | ||
Any non ticked off definition types are ignored. | ||
### Types | ||
All types are supported | ||
| Value | JSON Schema Draft | jsonschema | Comments | | ||
|:------|:-----------------:|:----------:|:---------| | ||
| type | ✔ | ✔ | | ||
| properties | ✔ | ✔ | | ||
| patternProperties | ✔ | ✔ | | ||
| additionalProperties | ✔ | ✔ | | ||
| items | ✔ | ✔ | | ||
| additionalItems | ✔ | ✔ | | ||
| required | ✔ | ✔ | | ||
| dependencies | ✔ | ✔ | | ||
| minimum | ✔ | ✔ | | ||
| maximum | ✔ | ✔ | | ||
| exclusiveMinimum | ✔ | ✔ | | ||
| exclusiveMaximum | ✔ | ✔ | | ||
| minItems | ✔ | ✔ | | ||
| maxItems | ✔ | ✔ | | ||
| uniqueItems | ✔ | ✔ | | ||
| pattern | ✔ | ✔ | | ||
| minLength | ✔ | ✔ | | ||
| maxLength | ✔ | ✔ | | ||
| enum | ✔ | ✔ | | ||
| default | ✔ | ✔ | informational only | ||
| title | ✔ | ✔ | informational only | ||
| description | ✔ | ✔ | informational only | ||
| format | ✔ | ✔ | | ||
| divisibleBy | ✔ | ✔ | | ||
| disallow | ✔ | ✔ | | ||
| extends | ✔ | ✔ | | ||
| id | ✔ | ✔ | informational only | ||
| $ref | ✔ | ✔ | | ||
| $schema | ✔ | | ignored | ||
### String Formats | ||
All formats are supported, phone numbers are expected to follow the [http://en.wikipedia.org/wiki/E.123](E.123) standard. | ||
### Types | ||
### Custom properties | ||
Specify your own JSON Schema properties with the validator.attributes property: | ||
| Value | JSON Schema Draft | jsonschema | Comments | | ||
|:------|:-----------------:|:----------:|:---------| | ||
| `string` | ✔ | ✔ | | ||
| `number` | ✔ | ✔ | | ||
| `integer` | ✔ | ✔ | | ||
| `boolean` | ✔ | ✔ | | ||
| `object` | ✔ | ✔ | | ||
| `array` | ✔ | ✔ | | ||
| `null` | ✔ | ✔ | | ||
| `date` | | ✔ | | ||
| `any` | ✔ | ✔ | | ||
| Union Types | ✔ | ✔ | | ||
```javascript | ||
validator.attributes.contains = function validateContains(instance, schema, options, ctx) { | ||
if(typeof instance!='string') return; | ||
if(typeof schema.contains!='string') throw new jsonschema.SchemaError('"contains" expects a string', schema); | ||
if(instance.indexOf()<0){ | ||
return 'does not contain the string '+JSON.stringify(schema.contains); | ||
} | ||
} | ||
var result = validator.validate({x:0, y:10}, {type:"object", radius:{x:10, y:10, radius:5}}); | ||
### String Formats | ||
``` | ||
| Value | JSON Schema Draft | jsonschema | Comments | | ||
|:------|:-----------------:|:----------:|:---------| | ||
| `date-time` | ✔ | ✔ | | ||
| `date` | ✔ | ✔ | | ||
| `time` | ✔ | ✔ | | ||
| `utc-millisec` | ✔ | ✔ | Any number (integer or float) is allowed | ||
| `regex` | ✔ | ✔ | We test for valid regular expression | ||
| `color` | ✔ | ✔ | | ||
| `style` | ✔ | ✔ | | ||
| `phone` | ✔ | ✔ | Should follow [http://en.wikipedia.org/wiki/E.123](E.123) standard. | ||
| `uri` | ✔ | ✔ | | ||
| `email` | ✔ | ✔ | | ||
| `ip-address` | ✔ | ✔ | | ||
| `ipv6` | ✔ | ✔ | | ||
| `host-name` | ✔ | ✔ | | ||
| `alpha` | | ✔ | | ||
| `alphanumeric` | | ✔ | | ||
The instance passes validation if the function returns nothing. A single validation error is produced | ||
if the fuction returns a string. Any number of errors (maybe none at all) may be returned by passing a | ||
`ValidatorResult` object, which may be used like so: | ||
```javascript | ||
var result = new ValidatorResult(instance, schema, options, ctx); | ||
while(someErrorCondition()){ | ||
result.addError('fails some validation test'); | ||
} | ||
return result; | ||
``` | ||
### Dereferencing schemas | ||
Sometimes you may want to download schemas from remote sources, like a database, or over HTTP. When importing a schema, | ||
unknown references are inserted into the `validator.unresolvedRefs` Array. Asynchronously shift elements off this array and import | ||
them: | ||
```javascript | ||
var Validator = require('jsonschema').Validator; | ||
var v = new Validator(); | ||
v.addSchema(initialSchema); | ||
function importNextSchema(){ | ||
var nextSchema = v.unresolvedRefs.shift(); | ||
if(!nextSchema){ done(); return; } | ||
databaseGet(nextSchema, function(schema){ | ||
v.addSchema(schema); | ||
importNextSchema(); | ||
}); | ||
} | ||
importNextSchema(); | ||
``` | ||
## Tests | ||
Uses [https://github.com/Julian/JSON-Schema-Test-Suite](JSON Schema Test Suite) as well as our own. | ||
@@ -98,22 +142,22 @@ You'll need to update and init the git submodules: | ||
jsonschema is licensed under MIT license. | ||
jsonschema is licensed under MIT license. | ||
Copyright (C) 2012 Tom de Grunt <tom@degrunt.nl> | ||
Copyright (C) 2012-2013 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: | ||
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 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. | ||
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. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
41734
1093
163
7
1