| /** |
| * JSONSchema Validator - Validates JavaScript objects using JSON Schemas |
| * (http://www.json.com/json-schema-proposal/) |
| * |
| * Copyright (c) 2007 Kris Zyp SitePen (www.sitepen.com) |
| * Licensed under the MIT (MIT-LICENSE.txt) license. |
| To use the validator call the validate function with an instance object and an optional schema object. |
| If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating), |
| that schema will be used to validate and the schema parameter is not necessary (if both exist, |
| both validations will occur). |
| The validate method will return an array of validation errors. If there are no errors, then an |
| empty list will be returned. A validation error will have two properties: |
| "property" which indicates which property had the error |
| "message" which indicates what the error was |
| */ |
| (function (root, factory) { |
| if (typeof define === 'function' && define.amd) { |
| // AMD. Register as an anonymous module. |
| define([], function () { |
| return factory(); |
| }); |
| } else if (typeof module === 'object' && module.exports) { |
| // Node. Does not work with strict CommonJS, but |
| // only CommonJS-like environments that support module.exports, |
| // like Node. |
| module.exports = factory(); |
| } else { |
| // Browser globals |
| root.jsonSchema = factory(); |
| } |
| }(this, function () {// setup primitive classes to be JSON Schema types |
| var exports = validate |
| exports.Integer = {type:"integer"}; |
| var primitiveConstructors = { |
| String: String, |
| Boolean: Boolean, |
| Number: Number, |
| Object: Object, |
| Array: Array, |
| Date: Date |
| } |
| exports.validate = validate; |
| function validate(/*Any*/instance,/*Object*/schema) { |
| // Summary: |
| // To use the validator call JSONSchema.validate with an instance object and an optional schema object. |
| // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating), |
| // that schema will be used to validate and the schema parameter is not necessary (if both exist, |
| // both validations will occur). |
| // The validate method will return an object with two properties: |
| // valid: A boolean indicating if the instance is valid by the schema |
| // errors: An array of validation errors. If there are no errors, then an |
| // empty list will be returned. A validation error will have two properties: |
| // property: which indicates which property had the error |
| // message: which indicates what the error was |
| // |
| return validate(instance, schema, {changing: false});//, coerce: false, existingOnly: false}); |
| }; |
| exports.checkPropertyChange = function(/*Any*/value,/*Object*/schema, /*String*/property) { |
| // Summary: |
| // The checkPropertyChange method will check to see if an value can legally be in property with the given schema |
| // This is slightly different than the validate method in that it will fail if the schema is readonly and it will |
| // not check for self-validation, it is assumed that the passed in value is already internally valid. |
| // The checkPropertyChange method will return the same object type as validate, see JSONSchema.validate for |
| // information. |
| // |
| return validate(value, schema, {changing: property || "property"}); |
| }; |
| var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*Object*/options) { |
| |
| if (!options) options = {}; |
| var _changing = options.changing; |
| |
| function getType(schema){ |
| return schema.type || (primitiveConstructors[schema.name] == schema && schema.name.toLowerCase()); |
| } |
| var errors = []; |
| // validate a value against a property definition |
| function checkProp(value, schema, path,i){ |
| |
| var l; |
| path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i; |
| function addError(message){ |
| errors.push({property:path,message:message}); |
| } |
| |
| if((typeof schema != 'object' || schema instanceof Array) && (path || typeof schema != 'function') && !(schema && getType(schema))){ |
| if(typeof schema == 'function'){ |
| if(!(value instanceof schema)){ |
| addError("is not an instance of the class/constructor " + schema.name); |
| } |
| }else if(schema){ |
| addError("Invalid schema/property definition " + schema); |
| } |
| return null; |
| } |
| if(_changing && schema.readonly){ |
| addError("is a readonly field, it can not be changed"); |
| } |
| if(schema['extends']){ // if it extends another schema, it must pass that schema as well |
| checkProp(value,schema['extends'],path,i); |
| } |
| // validate a value against a type definition |
| function checkType(type,value){ |
| if(type){ |
| if(typeof type == 'string' && type != 'any' && |
| (type == 'null' ? value !== null : typeof value != type) && |
| !(value instanceof Array && type == 'array') && |
| !(value instanceof Date && type == 'date') && |
| !(type == 'integer' && value%1===0)){ |
| return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}]; |
| } |
| if(type instanceof Array){ |
| var unionErrors=[]; |
| for(var j = 0; j < type.length; j++){ // a union type |
| if(!(unionErrors=checkType(type[j],value)).length){ |
| break; |
| } |
| } |
| if(unionErrors.length){ |
| return unionErrors; |
| } |
| }else if(typeof type == 'object'){ |
| var priorErrors = errors; |
| errors = []; |
| checkProp(value,type,path); |
| var theseErrors = errors; |
| errors = priorErrors; |
| return theseErrors; |
| } |
| } |
| return []; |
| } |
| if(value === undefined){ |
| if(schema.required){ |
| addError("is missing and it is required"); |
| } |
| }else{ |
| errors = errors.concat(checkType(getType(schema),value)); |
| if(schema.disallow && !checkType(schema.disallow,value).length){ |
| addError(" disallowed value was matched"); |
| } |
| if(value !== null){ |
| if(value instanceof Array){ |
| if(schema.items){ |
| var itemsIsArray = schema.items instanceof Array; |
| var propDef = schema.items; |
| for (i = 0, l = value.length; i < l; i += 1) { |
| if (itemsIsArray) |
| propDef = schema.items[i]; |
| if (options.coerce) |
| value[i] = options.coerce(value[i], propDef); |
| errors.concat(checkProp(value[i],propDef,path,i)); |
| } |
| } |
| if(schema.minItems && value.length < schema.minItems){ |
| addError("There must be a minimum of " + schema.minItems + " in the array"); |
| } |
| if(schema.maxItems && value.length > schema.maxItems){ |
| addError("There must be a maximum of " + schema.maxItems + " in the array"); |
| } |
| }else if(schema.properties || schema.additionalProperties){ |
| errors.concat(checkObj(value, schema.properties, path, schema.additionalProperties)); |
| } |
| if(schema.pattern && typeof value == 'string' && !value.match(schema.pattern)){ |
| addError("does not match the regex pattern " + schema.pattern); |
| } |
| if(schema.maxLength && typeof value == 'string' && value.length > schema.maxLength){ |
| addError("may only be " + schema.maxLength + " characters long"); |
| } |
| if(schema.minLength && typeof value == 'string' && value.length < schema.minLength){ |
| addError("must be at least " + schema.minLength + " characters long"); |
| } |
| if(typeof schema.minimum !== undefined && typeof value == typeof schema.minimum && |
| schema.minimum > value){ |
| addError("must have a minimum value of " + schema.minimum); |
| } |
| if(typeof schema.maximum !== undefined && typeof value == typeof schema.maximum && |
| schema.maximum < value){ |
| addError("must have a maximum value of " + schema.maximum); |
| } |
| if(schema['enum']){ |
| var enumer = schema['enum']; |
| l = enumer.length; |
| var found; |
| for(var j = 0; j < l; j++){ |
| if(enumer[j]===value){ |
| found=1; |
| break; |
| } |
| } |
| if(!found){ |
| addError("does not have a value in the enumeration " + enumer.join(", ")); |
| } |
| } |
| if(typeof schema.maxDecimal == 'number' && |
| (value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){ |
| addError("may only have " + schema.maxDecimal + " digits of decimal places"); |
| } |
| } |
| } |
| return null; |
| } |
| // validate an object against a schema |
| function checkObj(instance,objTypeDef,path,additionalProp){ |
| |
| if(typeof objTypeDef =='object'){ |
| if(typeof instance != 'object' || instance instanceof Array){ |
| errors.push({property:path,message:"an object is required"}); |
| } |
| |
| for(var i in objTypeDef){ |
| if(objTypeDef.hasOwnProperty(i)){ |
| var value = instance[i]; |
| // skip _not_ specified properties |
| if (value === undefined && options.existingOnly) continue; |
| var propDef = objTypeDef[i]; |
| // set default |
| if(value === undefined && propDef["default"]){ |
| value = instance[i] = propDef["default"]; |
| } |
| if(options.coerce && i in instance){ |
| value = instance[i] = options.coerce(value, propDef); |
| } |
| checkProp(value,propDef,path,i); |
| } |
| } |
| } |
| for(i in instance){ |
| if(instance.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && objTypeDef && !objTypeDef[i] && additionalProp===false){ |
| if (options.filter) { |
| delete instance[i]; |
| continue; |
| } else { |
| errors.push({property:path,message:(typeof value) + "The property " + i + |
| " is not defined in the schema and the schema does not allow additional properties"}); |
| } |
| } |
| var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires; |
| if(requires && !(requires in instance)){ |
| errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"}); |
| } |
| value = instance[i]; |
| if(additionalProp && (!(objTypeDef && typeof objTypeDef == 'object') || !(i in objTypeDef))){ |
| if(options.coerce){ |
| value = instance[i] = options.coerce(value, additionalProp); |
| } |
| checkProp(value,additionalProp,path,i); |
| } |
| if(!_changing && value && value.$schema){ |
| errors = errors.concat(checkProp(value,value.$schema,path,i)); |
| } |
| } |
| return errors; |
| } |
| if(schema){ |
| checkProp(instance,schema,'',_changing || ''); |
| } |
| if(!_changing && instance && instance.$schema){ |
| checkProp(instance,instance.$schema,'',''); |
| } |
| return {valid:!errors.length,errors:errors}; |
| }; |
| exports.mustBeValid = function(result){ |
| // summary: |
| // This checks to ensure that the result is valid and will throw an appropriate error message if it is not |
| // result: the result returned from checkPropertyChange or validate |
| if(!result.valid){ |
| throw new TypeError(result.errors.map(function(error){return "for property " + error.property + ': ' + error.message;}).join(", \n")); |
| } |
| } |
| |
| return exports; |
| })); |