Update jsonschema version

Fixes bug in `uniqueItems` property.
This commit is contained in:
Andrew Eisenberg 2023-05-02 14:26:17 -07:00
parent ece3cbc8ec
commit ef88842204
11 changed files with 602 additions and 180 deletions

View file

@ -16,13 +16,13 @@ attribute.ignoreProperties = {
'description': true,
'title': true,
// arguments to other properties
'exclusiveMinimum': true,
'exclusiveMaximum': true,
'additionalItems': true,
'then': true,
'else': true,
// special-handled properties
'$schema': true,
'$ref': true,
'extends': true
'extends': true,
};
/**
@ -47,7 +47,9 @@ validators.type = function validateType (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) {
return v.id && ('<' + v.id + '>') || (v+'');
if(!v) return;
var id = v.$id || v.id;
return id ? ('<' + id + '>') : (v+'');
});
result.addError({
name: 'type',
@ -60,9 +62,12 @@ validators.type = function validateType (instance, schema, options, ctx) {
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);
@ -91,9 +96,11 @@ validators.anyOf = function validateAnyOf (instance, schema, options, ctx) {
if (!schema.anyOf.some(
testSchemaNoThrow.bind(
this, instance, options, ctx, function(res){inner.importErrors(res);}
))) {
))) {
var list = schema.anyOf.map(function (v, i) {
return (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+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);
@ -128,7 +135,8 @@ validators.allOf = function validateAllOf (instance, schema, options, ctx) {
schema.allOf.forEach(function(v, i){
var valid = self.validateSchema(instance, v, options, ctx);
if(!valid.valid){
var msg = (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
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 },
@ -161,9 +169,10 @@ validators.oneOf = function validateOneOf (instance, schema, options, ctx) {
var count = schema.oneOf.filter(
testSchemaNoThrow.bind(
this, instance, options, ctx, function(res) {inner.importErrors(res);}
) ).length;
) ).length;
var list = schema.oneOf.map(function (v, i) {
return (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+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) {
@ -178,6 +187,70 @@ validators.oneOf = function validateOneOf (instance, schema, options, ctx) {
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
@ -191,12 +264,17 @@ validators.properties = function validateProperties (instance, schema, options,
var result = new ValidatorResult(instance, schema, options, ctx);
var properties = schema.properties || {};
for (var property in properties) {
if (typeof options.preValidateProperty == 'function') {
options.preValidateProperty(instance, property, properties[property], options, ctx);
var subschema = properties[property];
if(subschema===undefined){
continue;
}else if(subschema===null){
throw new SchemaError('Unexpected null, expected schema in "properties"');
}
var prop = Object.hasOwnProperty.call(instance, property) ? instance[property] : undefined;
var res = this.validateSchema(prop, properties[property], options, ctx.makeChild(properties[property], property));
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);
}
@ -206,7 +284,7 @@ validators.properties = function validateProperties (instance, schema, options,
/**
* 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)
* 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}
*/
@ -219,7 +297,7 @@ function testAdditionalProperty (instance, schema, options, ctx, property, resul
result.addError({
name: 'additionalProperties',
argument: property,
message: "additionalProperty " + JSON.stringify(property) + " exists in instance when not allowed",
message: "is not allowed to have the additional property " + JSON.stringify(property),
});
} else {
var additionalProperties = schema.additionalProperties || {};
@ -250,17 +328,29 @@ validators.patternProperties = function validatePatternProperties (instance, sch
for (var property in instance) {
var test = true;
for (var pattern in patternProperties) {
var expr = new RegExp(pattern);
if (!expr.test(property)) {
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, patternProperties[pattern], options, ctx);
options.preValidateProperty(instance, property, subschema, options, ctx);
}
var res = this.validateSchema(instance[property], patternProperties[pattern], options, ctx.makeChild(patternProperties[pattern], property));
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);
}
@ -308,7 +398,7 @@ validators.minProperties = function validateMinProperties (instance, schema, opt
name: 'minProperties',
argument: schema.minProperties,
message: "does not meet minimum property length of " + schema.minProperties,
})
});
}
return result;
};
@ -344,10 +434,14 @@ validators.maxProperties = function validateMaxProperties (instance, schema, opt
validators.items = function validateItems (instance, schema, options, ctx) {
var self = this;
if (!this.types.array(instance)) return;
if (!schema.items) return;
if (schema.items===undefined) return;
var result = new ValidatorResult(instance, schema, options, ctx);
instance.every(function (value, i) {
var items = Array.isArray(schema.items) ? (schema.items[i] || schema.additionalItems) : schema.items;
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;
}
@ -366,6 +460,34 @@ validators.items = function validateItems (instance, schema, options, ctx) {
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
@ -375,18 +497,22 @@ validators.items = function validateItems (instance, schema, options, ctx) {
validators.minimum = function validateMinimum (instance, schema, options, ctx) {
if (!this.types.number(instance)) return;
var result = new ValidatorResult(instance, schema, options, ctx);
var valid = true;
if (schema.exclusiveMinimum && schema.exclusiveMinimum === true) {
valid = instance > schema.minimum;
if(!(instance > schema.minimum)){
result.addError({
name: 'minimum',
argument: schema.minimum,
message: "must be greater than " + schema.minimum,
});
}
} else {
valid = instance >= schema.minimum;
}
if (!valid) {
result.addError({
name: 'minimum',
argument: schema.minimum,
message: "must have a minimum value of " + schema.minimum,
});
if(!(instance >= schema.minimum)){
result.addError({
name: 'minimum',
argument: schema.minimum,
message: "must be greater than or equal to " + schema.minimum,
});
}
}
return result;
};
@ -400,17 +526,65 @@ validators.minimum = function validateMinimum (instance, schema, options, ctx) {
validators.maximum = function validateMaximum (instance, schema, options, ctx) {
if (!this.types.number(instance)) return;
var result = new ValidatorResult(instance, schema, options, ctx);
var valid;
if (schema.exclusiveMaximum && schema.exclusiveMaximum === true) {
valid = instance < schema.maximum;
if(!(instance < schema.maximum)){
result.addError({
name: 'maximum',
argument: schema.maximum,
message: "must be less than " + schema.maximum,
});
}
} else {
valid = instance <= schema.maximum;
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: 'maximum',
argument: schema.maximum,
message: "must have a maximum value of " + schema.maximum,
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;
@ -444,7 +618,7 @@ var validateMultipleOfOrDivisbleBy = function validateMultipleOfOrDivisbleBy (in
result.addError({
name: validationType,
argument: validationArgument,
message: errorMessage + JSON.stringify(validationArgument)
message: errorMessage + JSON.stringify(validationArgument),
});
}
@ -458,7 +632,7 @@ var validateMultipleOfOrDivisbleBy = function validateMultipleOfOrDivisbleBy (in
* @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) ");
return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "multipleOf", "is not a multiple of (divisible by) ");
};
/**
@ -480,14 +654,14 @@ validators.divisibleBy = function validateDivisibleBy (instance, schema, options
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-compatability with schemas written against older drafts
// A boolean form is implemented for reverse-compatibility with schemas written against older drafts
result.addError({
name: 'required',
message: "is required"
message: "is required",
});
} else if (this.types.object(instance) && Array.isArray(schema.required)) {
schema.required.forEach(function(n){
if(instance[n]===undefined){
if(getEnumerableProperty(instance, n)===undefined){
result.addError({
name: 'required',
argument: n,
@ -508,7 +682,15 @@ validators.required = function validateRequired (instance, schema, options, ctx)
validators.pattern = function validatePattern (instance, schema, options, ctx) {
if (!this.types.string(instance)) return;
var result = new ValidatorResult(instance, schema, options, ctx);
if (!instance.match(schema.pattern)) {
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,
@ -633,32 +815,6 @@ validators.maxItems = function validateMaxItems (instance, schema, options, ctx)
return result;
};
/**
* 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) {
if (!this.types.array(instance)) return;
var result = new ValidatorResult(instance, schema, options, ctx);
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({
name: 'uniqueItems',
message: "contains duplicate item",
});
}
return result;
};
/**
* Deep compares arrays for duplicates
* @param v
@ -683,6 +839,7 @@ function testArrays (v, i, a) {
* @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)) {
@ -806,7 +963,8 @@ validators.not = validators.disallow = function validateNot (instance, schema, o
if(!Array.isArray(notTypes)) notTypes=[notTypes];
notTypes.forEach(function (type) {
if (self.testType(instance, schema, options, ctx, type)) {
var schemaId = type && type.id && ('<' + type.id + '>') || type;
var id = type && (type.$id || type.id);
var schemaId = id || type;
result.addError({
name: 'not',
argument: schemaId,

View file

@ -2,21 +2,23 @@
var uri = require('url');
var ValidationError = exports.ValidationError = function ValidationError (message, instance, schema, propertyPath, name, argument) {
if (propertyPath) {
this.property = propertyPath;
var ValidationError = exports.ValidationError = function ValidationError (message, instance, schema, path, name, argument) {
if(Array.isArray(path)){
this.path = path;
this.property = path.reduce(function(sum, item){
return sum + makeSuffix(item);
}, 'instance');
}else if(path !== undefined){
this.property = path;
}
if (message) {
this.message = message;
}
if (schema) {
if (schema.id) {
this.schema = schema.id;
} else {
this.schema = schema;
}
var id = schema.$id || schema.id;
this.schema = id || schema;
}
if (instance) {
if (instance !== undefined) {
this.instance = instance;
}
this.name = name;
@ -31,27 +33,33 @@ ValidationError.prototype.toString = function toString() {
var ValidatorResult = exports.ValidatorResult = function ValidatorResult(instance, schema, options, ctx) {
this.instance = instance;
this.schema = schema;
this.options = options;
this.path = ctx.path;
this.propertyPath = ctx.propertyPath;
this.errors = [];
this.throwError = options && options.throwError;
this.throwFirst = options && options.throwFirst;
this.throwAll = options && options.throwAll;
this.disableFormat = options && options.disableFormat === true;
};
ValidatorResult.prototype.addError = function addError(detail) {
var err;
if (typeof detail == 'string') {
err = new ValidationError(detail, this.instance, this.schema, this.propertyPath);
err = new ValidationError(detail, this.instance, this.schema, this.path);
} else {
if (!detail) throw new Error('Missing error detail');
if (!detail.message) throw new Error('Missing error message');
if (!detail.name) throw new Error('Missing validator type');
err = new ValidationError(detail.message, this.instance, this.schema, this.propertyPath, detail.name, detail.argument);
err = new ValidationError(detail.message, this.instance, this.schema, this.path, detail.name, detail.argument);
}
if (this.throwError) {
this.errors.push(err);
if (this.throwFirst) {
throw new ValidatorResultError(this);
}else if(this.throwError){
throw err;
}
this.errors.push(err);
return err;
};
@ -59,7 +67,7 @@ ValidatorResult.prototype.importErrors = function importErrors(res) {
if (typeof res == 'string' || (res && res.validatorType)) {
this.addError(res);
} else if (res && res.errors) {
Array.prototype.push.apply(this.errors, res.errors);
this.errors = this.errors.concat(res.errors);
}
};
@ -74,6 +82,20 @@ Object.defineProperty(ValidatorResult.prototype, "valid", { get: function() {
return !this.errors.length;
} });
module.exports.ValidatorResultError = ValidatorResultError;
function ValidatorResultError(result) {
if(Error.captureStackTrace){
Error.captureStackTrace(this, ValidatorResultError);
}
this.instance = result.instance;
this.schema = result.schema;
this.options = result.options;
this.errors = result.errors;
}
ValidatorResultError.prototype = new Error();
ValidatorResultError.prototype.constructor = ValidatorResultError;
ValidatorResultError.prototype.name = "Validation Error";
/**
* Describes a problem with a Schema which prevents validation of an instance
* @name SchemaError
@ -86,14 +108,22 @@ var SchemaError = exports.SchemaError = function SchemaError (msg, schema) {
Error.captureStackTrace(this, SchemaError);
};
SchemaError.prototype = Object.create(Error.prototype,
{ constructor: {value: SchemaError, enumerable: false}
, name: {value: 'SchemaError', enumerable: false}
{
constructor: {value: SchemaError, enumerable: false},
name: {value: 'SchemaError', enumerable: false},
});
var SchemaContext = exports.SchemaContext = function SchemaContext (schema, options, propertyPath, base, schemas) {
var SchemaContext = exports.SchemaContext = function SchemaContext (schema, options, path, base, schemas) {
this.schema = schema;
this.options = options;
this.propertyPath = propertyPath;
if(Array.isArray(path)){
this.path = path;
this.propertyPath = path.reduce(function(sum, item){
return sum + makeSuffix(item);
}, 'instance');
}else{
this.propertyPath = path;
}
this.base = base;
this.schemas = schemas;
};
@ -103,36 +133,60 @@ SchemaContext.prototype.resolve = function resolve (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]){
var path = (propertyName===undefined) ? this.path : this.path.concat([propertyName]);
var id = schema.$id || schema.id;
var base = uri.resolve(this.base, id||'');
var ctx = new SchemaContext(schema, this.options, path, base, Object.create(this.schemas));
if(id && !ctx.schemas[base]){
ctx.schemas[base] = schema;
}
return ctx;
}
};
var FORMAT_REGEXPS = exports.FORMAT_REGEXPS = {
// 7.3.1. Dates, Times, and Duration
'date-time': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])[tT ](2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])(\.\d+)?([zZ]|[+-]([0-5][0-9]):(60|[0-5][0-9]))$/,
'date': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])$/,
'time': /^(2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])$/,
'duration': /P(T\d+(H(\d+M(\d+S)?)?|M(\d+S)?|S)|\d+(D|M(\d+D)?|Y(\d+M(\d+D)?)?)(T\d+(H(\d+M(\d+S)?)?|M(\d+S)?|S))?|\d+W)/i,
// 7.3.2. Email Addresses
// TODO: fix the email production
'email': /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/,
'ip-address': /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
'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': /^[a-zA-Z][a-zA-Z0-9+-.]*:[^\s]*$/,
'idn-email': /^("(?:[!#-\[\]-\u{10FFFF}]|\\[\t -\u{10FFFF}])*"|[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*)@([!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*|\[[!-Z\^-\u{10FFFF}]*\])$/u,
'color': /^(#?([0-9A-Fa-f]{3}){1,2}\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*\)))$/,
// 7.3.3. Hostnames
// 7.3.4. IP Addresses
'ip-address': /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
// FIXME whitespace is invalid
'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*$/,
// 7.3.5. Resource Identifiers
// TODO: A more accurate regular expression for "uri" goes:
// [A-Za-z][+\-.0-9A-Za-z]*:((/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?)?#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])|/?%[0-9A-Fa-f]{2}|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*(#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?|/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)?)?
'uri': /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]*$/,
'uri-reference': /^(((([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|([A-Za-z][+\-.0-9A-Za-z]*:?)?)|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?))#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(([A-Za-z][+\-.0-9A-Za-z]*)?%[0-9A-Fa-f]{2}|[!$&-.0-9;=@_~]|[A-Za-z][+\-.0-9A-Za-z]*[!$&-*,;=@_~])(%[0-9A-Fa-f]{2}|[!$&-.0-9;=@-Z_a-z~])*((([/?](%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?#|[/?])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?|([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)?|[A-Za-z][+\-.0-9A-Za-z]*:?)?$/,
'iri': /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]*$/,
'iri-reference': /^(((([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~-\u{10FFFF}]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|([A-Za-z][+\-.0-9A-Za-z]*:?)?)|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|(\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?))#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|(([A-Za-z][+\-.0-9A-Za-z]*)?%[0-9A-Fa-f]{2}|[!$&-.0-9;=@_~-\u{10FFFF}]|[A-Za-z][+\-.0-9A-Za-z]*[!$&-*,;=@_~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-.0-9;=@-Z_a-z~-\u{10FFFF}])*((([/?](%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*)?#|[/?])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*)?|([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~-\u{10FFFF}]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)?|[A-Za-z][+\-.0-9A-Za-z]*:?)?$/u,
'uuid': /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
// 7.3.6. uri-template
'uri-template': /(%[0-9a-f]{2}|[!#$&(-;=?@\[\]_a-z~]|\{[!#&+,./;=?@|]?(%[0-9a-f]{2}|[0-9_a-z])(\.?(%[0-9a-f]{2}|[0-9_a-z]))*(:[1-9]\d{0,3}|\*)?(,(%[0-9a-f]{2}|[0-9_a-z])(\.?(%[0-9a-f]{2}|[0-9_a-z]))*(:[1-9]\d{0,3}|\*)?)*\})*/iu,
// 7.3.7. JSON Pointers
'json-pointer': /^(\/([\x00-\x2e0-@\[-}\x7f]|~[01])*)*$/iu,
'relative-json-pointer': /^\d+(#|(\/([\x00-\x2e0-@\[-}\x7f]|~[01])*)*)$/iu,
// hostname regex from: http://stackoverflow.com/a/1420225/5628
'hostname': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
'host-name': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
'alpha': /^[a-zA-Z]+$/,
'alphanumeric': /^[a-zA-Z0-9]+$/,
'utc-millisec': function (input) {
return (typeof input === 'string') && parseFloat(input) === parseInt(input, 10) && !isNaN(input);
},
// 7.3.8. regex
'regex': function (input) {
var result = true;
try {
@ -142,8 +196,15 @@ var FORMAT_REGEXPS = exports.FORMAT_REGEXPS = {
}
return result;
},
'style': /\s*(.+?):\s*([^;]+);?/,
'phone': /^\+(?:[0-9] ?){6,14}[0-9]$/
// Other definitions
// "style" was removed from JSON Schema in draft-4 and is deprecated
'style': /[\r\n\t ]*[^\r\n\t ][^:]*:[\r\n\t ]*[^\r\n\t ;]*[\r\n\t ]*;?/,
// "color" was removed from JSON Schema in draft-4 and is deprecated
'color': /^(#?([0-9A-Fa-f]{3}){1,2}\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*\)))$/,
'phone': /^\+(?:[0-9] ?){6,14}[0-9]$/,
'alpha': /^[a-zA-Z]+$/,
'alphanumeric': /^[a-zA-Z0-9]+$/,
};
FORMAT_REGEXPS.regexp = FORMAT_REGEXPS.regex;
@ -212,10 +273,10 @@ exports.deepCompareStrict = function deepCompareStrict (a, b) {
function deepMerger (target, dst, e, i) {
if (typeof e === 'object') {
dst[i] = deepMerge(target[i], e)
dst[i] = deepMerge(target[i], e);
} else {
if (target.indexOf(e) === -1) {
dst.push(e)
dst.push(e);
}
}
}
@ -232,7 +293,7 @@ function copyistWithDeepMerge (target, src, dst, key) {
if (!target[key]) {
dst[key] = src[key];
} else {
dst[key] = deepMerge(target[key], src[key])
dst[key] = deepMerge(target[key], src[key]);
}
}
}
@ -253,7 +314,7 @@ function deepMerge (target, src) {
}
return dst;
};
}
module.exports.deepMerge = deepMerge;
@ -284,9 +345,9 @@ function pathEncoder (v) {
* @return {String}
*/
exports.encodePath = function encodePointer(a){
// ~ must be encoded explicitly because hacks
// the slash is encoded by encodeURIComponent
return a.map(pathEncoder).join('');
// ~ must be encoded explicitly because hacks
// the slash is encoded by encodeURIComponent
return a.map(pathEncoder).join('');
};
@ -323,3 +384,7 @@ exports.getDecimalPlaces = function getDecimalPlaces(number) {
return decimalPlaces;
};
exports.isSchema = function isSchema(val){
return (typeof val === 'object' && val) || (typeof val === 'boolean');
};

View file

@ -29,6 +29,7 @@ export declare class ValidatorResult {
export declare class ValidationError {
constructor(message?: string, instance?: any, schema?: Schema, propertyPath?: any, name?: string, argument?: any);
path: (string|number)[];
property: string;
message: string;
schema: string|Schema;
@ -48,6 +49,7 @@ export declare class SchemaError extends Error{
export declare function validate(instance: any, schema: any, options?: Options): ValidatorResult
export interface Schema {
$id?: string
id?: string
$schema?: string
$ref?: string
@ -55,9 +57,9 @@ export interface Schema {
description?: string
multipleOf?: number
maximum?: number
exclusiveMaximum?: boolean
exclusiveMaximum?: number | boolean
minimum?: number
exclusiveMinimum?: boolean
exclusiveMinimum?: number | boolean
maxLength?: number
minLength?: number
pattern?: string | RegExp
@ -82,6 +84,7 @@ export interface Schema {
dependencies?: {
[name: string]: Schema | string[]
}
const?: any
'enum'?: any[]
type?: string | string[]
format?: string
@ -89,27 +92,39 @@ export interface Schema {
anyOf?: Schema[]
oneOf?: Schema[]
not?: Schema
if?: Schema
then?: Schema
else?: Schema
}
export interface Options {
skipAttributes?: string[];
allowUnknownAttributes?: boolean;
preValidateProperty?: PreValidatePropertyFunction;
rewrite?: RewriteFunction;
propertyName?: string;
base?: string;
throwError?: boolean;
required?: boolean;
throwFirst?: boolean;
throwAll?: boolean;
nestedErrors?: boolean;
}
export interface RewriteFunction {
(instance: any, schema: Schema, options: Options, ctx: SchemaContext): any;
}
export interface PreValidatePropertyFunction {
(instance: any, key: string, schema: Schema, options: Options, ctx: SchemaContext): any;
}
export interface SchemaContext {
schema: Schema;
options: Options;
propertyPath: string;
base: string;
schemas: {[base: string]: Schema};
makeChild: (schema: Schema, key: string) => SchemaContext;
}
export interface CustomFormat {

View file

@ -3,6 +3,7 @@
var Validator = module.exports.Validator = require('./validator');
module.exports.ValidatorResult = require('./helpers').ValidatorResult;
module.exports.ValidatorResultError = require('./helpers').ValidatorResultError;
module.exports.ValidationError = require('./helpers').ValidationError;
module.exports.SchemaError = require('./helpers').SchemaError;
module.exports.SchemaScanResult = require('./scan').SchemaScanResult;

View file

@ -1,3 +1,4 @@
"use strict";
var urilib = require('url');
var helpers = require('./helpers');
@ -23,13 +24,14 @@ module.exports.scan = function scan(base, schema){
ref[resolvedUri] = ref[resolvedUri] ? ref[resolvedUri]+1 : 0;
return;
}
var ourBase = schema.id ? urilib.resolve(baseuri, schema.id) : baseuri;
var id = schema.$id || schema.id;
var ourBase = id ? urilib.resolve(baseuri, id) : baseuri;
if (ourBase) {
// If there's no fragment, append an empty one
if(ourBase.indexOf('#')<0) ourBase += '#';
if(found[ourBase]){
if(!helpers.deepCompareStrict(found[ourBase], schema)){
throw new Error('Schema <'+schema+'> already exists with different definition');
throw new Error('Schema <'+ourBase+'> already exists with different definition');
}
return found[ourBase];
}
@ -68,7 +70,6 @@ module.exports.scan = function scan(base, schema){
var found = {};
var ref = {};
var schemaUri = base;
scanSchema(base, schema);
return new SchemaScanResult(found, ref);
}
};

View file

@ -6,6 +6,7 @@ var attribute = require('./attribute');
var helpers = require('./helpers');
var scanSchema = require('./scan').scan;
var ValidatorResult = helpers.ValidatorResult;
var ValidatorResultError = helpers.ValidatorResultError;
var SchemaError = helpers.SchemaError;
var SchemaContext = helpers.SchemaContext;
//var anonymousBase = 'vnd.jsonschema:///';
@ -49,13 +50,15 @@ Validator.prototype.addSchema = function addSchema (schema, base) {
return null;
}
var scan = scanSchema(base||anonymousBase, schema);
var ourUri = base || schema.id;
var ourUri = base || schema.$id || schema.id;
for(var uri in scan.id){
this.schemas[uri] = scan.id[uri];
}
for(var uri in scan.ref){
// If this schema is already defined, it will be filtered out by the next step
this.unresolvedRefs.push(uri);
}
// Remove newly defined schemas from unresolvedRefs
this.unresolvedRefs = this.unresolvedRefs.filter(function(uri){
return typeof self.schemas[uri]==='undefined';
});
@ -103,14 +106,18 @@ Validator.prototype.getSchema = function getSchema (urn) {
* @return {Array}
*/
Validator.prototype.validate = function validate (instance, schema, options, ctx) {
if((typeof schema !== 'boolean' && typeof schema !== 'object') || schema === null){
throw new SchemaError('Expected `schema` to be an object or boolean');
}
if (!options) {
options = {};
}
var propertyName = options.propertyName || 'instance';
// This section indexes subschemas in the provided schema, so they don't need to be added with Validator#addSchema
// 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||anonymousBase, schema.id||'');
var id = schema.$id || schema.id;
var base = urilib.resolve(options.base||anonymousBase, id||'');
if(!ctx){
ctx = new SchemaContext(schema, options, propertyName, base, Object.create(this.schemas));
ctx = new SchemaContext(schema, options, [], base, Object.create(this.schemas));
if (!ctx.schemas[base]) {
ctx.schemas[base] = schema;
}
@ -120,14 +127,18 @@ Validator.prototype.validate = function validate (instance, schema, options, ctx
ctx.schemas[n] = sch;
}
}
if (schema) {
var result = this.validateSchema(instance, schema, options, ctx);
if (!result) {
throw new Error('Result undefined');
}
if(options.required && instance===undefined){
var result = new ValidatorResult(instance, schema, options, ctx);
result.addError('is required, but is undefined');
return result;
}
throw new SchemaError('no schema specified', schema);
var result = this.validateSchema(instance, schema, options, ctx);
if (!result) {
throw new Error('Result undefined');
}else if(options.throwAll && result.errors.length){
throw new ValidatorResultError(result);
}
return result;
};
/**
@ -152,7 +163,7 @@ function shouldResolve(schema) {
Validator.prototype.validateSchema = function validateSchema (instance, schema, options, ctx) {
var result = new ValidatorResult(instance, schema, options, ctx);
// Support for the true/false schemas
// Support for the true/false schemas
if(typeof schema==='boolean') {
if(schema===true){
// `true` is always valid
@ -180,10 +191,10 @@ Validator.prototype.validateSchema = function validateSchema (instance, schema,
}
// If passed a string argument, load that schema URI
var switchSchema;
if (switchSchema = shouldResolve(schema)) {
var switchSchema = shouldResolve(schema);
if (switchSchema) {
var resolved = this.resolve(schema, switchSchema, ctx);
var subctx = new SchemaContext(resolved.subschema, options, ctx.propertyPath, resolved.switchSchema, ctx.schemas);
var subctx = new SchemaContext(resolved.subschema, options, ctx.path, resolved.switchSchema, ctx.schemas);
return this.validateSchema(instance, resolved.subschema, options, subctx);
}
@ -220,7 +231,7 @@ Validator.prototype.validateSchema = function validateSchema (instance, schema,
*/
Validator.prototype.schemaTraverser = function schemaTraverser (schemaobj, s) {
schemaobj.schema = helpers.deepMerge(schemaobj.schema, this.superResolve(s, schemaobj.ctx));
}
};
/**
* @private
@ -229,12 +240,12 @@ Validator.prototype.schemaTraverser = function schemaTraverser (schemaobj, s) {
* @returns Object schema or resolved schema
*/
Validator.prototype.superResolve = function superResolve (schema, ctx) {
var ref;
if(ref = shouldResolve(schema)) {
var ref = shouldResolve(schema);
if(ref) {
return this.resolve(schema, ref, ctx).subschema;
}
return schema;
}
};
/**
* @private
@ -275,6 +286,11 @@ Validator.prototype.resolve = function resolve (schema, switchSchema, ctx) {
* @return {boolean}
*/
Validator.prototype.testType = function validateType (instance, schema, options, ctx, type) {
if(type===undefined){
return;
}else if(type===null){
throw new SchemaError('Unexpected null in "type" keyword');
}
if (typeof this.types[type] == 'function') {
return this.types[type].call(this, instance);
}