2 * JSONSchema Validator - Validates JavaScript objects using JSON Schemas
3 * (http://www.json.com/json-schema-proposal/)
5 * Copyright (c) 2007 Kris Zyp SitePen (www.sitepen.com)
6 * Licensed under the MIT (MIT-LICENSE.txt) license.
7 To use the validator call the validate function with an instance object and an optional schema object.
8 If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
9 that schema will be used to validate and the schema parameter is not necessary (if both exist,
10 both validations will occur).
11 The validate method will return an array of validation errors. If there are no errors, then an
12 empty list will be returned. A validation error will have two properties:
13 "property" which indicates which property had the error
14 "message" which indicates what the error was
16 ({define:typeof define!="undefined"?define:function(deps, factory){module.exports = factory();}}).
17 define([], function(){
18 var exports = validate;
19 // setup primitive classes to be JSON Schema types
20 exports.Integer = {type:"integer"};
21 var primitiveConstructors = {
29 exports.validate = validate;
30 function validate(/*Any*/instance,/*Object*/schema) {
32 // To use the validator call JSONSchema.validate with an instance object and an optional schema object.
33 // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
34 // that schema will be used to validate and the schema parameter is not necessary (if both exist,
35 // both validations will occur).
36 // The validate method will return an object with two properties:
37 // valid: A boolean indicating if the instance is valid by the schema
38 // errors: An array of validation errors. If there are no errors, then an
39 // empty list will be returned. A validation error will have two properties:
40 // property: which indicates which property had the error
41 // message: which indicates what the error was
43 return validate(instance, schema, {changing: false});//, coerce: false, existingOnly: false});
45 exports.checkPropertyChange = function(/*Any*/value,/*Object*/schema, /*String*/property) {
47 // The checkPropertyChange method will check to see if an value can legally be in property with the given schema
48 // This is slightly different than the validate method in that it will fail if the schema is readonly and it will
49 // not check for self-validation, it is assumed that the passed in value is already internally valid.
50 // The checkPropertyChange method will return the same object type as validate, see JSONSchema.validate for
53 return validate(value, schema, {changing: property || "property"});
55 var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*Object*/options) {
57 if (!options) options = {};
58 var _changing = options.changing;
60 function getType(schema){
61 return schema.type || (primitiveConstructors[schema.name] == schema && schema.name.toLowerCase());
64 // validate a value against a property definition
65 function checkProp(value, schema, path,i){
68 path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i;
69 function addError(message){
70 errors.push({property:path,message:message});
73 if((typeof schema != 'object' || schema instanceof Array) && (path || typeof schema != 'function') && !(schema && getType(schema))){
74 if(typeof schema == 'function'){
75 if(!(value instanceof schema)){
76 addError("is not an instance of the class/constructor " + schema.name);
79 addError("Invalid schema/property definition " + schema);
83 if(_changing && schema.readonly){
84 addError("is a readonly field, it can not be changed");
86 if(schema['extends']){ // if it extends another schema, it must pass that schema as well
87 checkProp(value,schema['extends'],path,i);
89 // validate a value against a type definition
90 function checkType(type,value){
92 if(typeof type == 'string' && type != 'any' &&
93 (type == 'null' ? value !== null : typeof value != type) &&
94 !(value instanceof Array && type == 'array') &&
95 !(value instanceof Date && type == 'date') &&
96 !(type == 'integer' && value%1===0)){
97 return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}];
99 if(type instanceof Array){
101 for(var j = 0; j < type.length; j++){ // a union type
102 if(!(unionErrors=checkType(type[j],value)).length){
106 if(unionErrors.length){
109 }else if(typeof type == 'object'){
110 var priorErrors = errors;
112 checkProp(value,type,path);
113 var theseErrors = errors;
114 errors = priorErrors;
120 if(value === undefined){
122 addError("is missing and it is required");
125 errors = errors.concat(checkType(getType(schema),value));
126 if(schema.disallow && !checkType(schema.disallow,value).length){
127 addError(" disallowed value was matched");
130 if(value instanceof Array){
132 var itemsIsArray = schema.items instanceof Array;
133 var propDef = schema.items;
134 for (i = 0, l = value.length; i < l; i += 1) {
136 propDef = schema.items[i];
138 value[i] = options.coerce(value[i], propDef);
139 errors.concat(checkProp(value[i],propDef,path,i));
142 if(schema.minItems && value.length < schema.minItems){
143 addError("There must be a minimum of " + schema.minItems + " in the array");
145 if(schema.maxItems && value.length > schema.maxItems){
146 addError("There must be a maximum of " + schema.maxItems + " in the array");
148 }else if(schema.properties || schema.additionalProperties){
149 errors.concat(checkObj(value, schema.properties, path, schema.additionalProperties));
151 if(schema.pattern && typeof value == 'string' && !value.match(schema.pattern)){
152 addError("does not match the regex pattern " + schema.pattern);
154 if(schema.maxLength && typeof value == 'string' && value.length > schema.maxLength){
155 addError("may only be " + schema.maxLength + " characters long");
157 if(schema.minLength && typeof value == 'string' && value.length < schema.minLength){
158 addError("must be at least " + schema.minLength + " characters long");
160 if(typeof schema.minimum !== undefined && typeof value == typeof schema.minimum &&
161 schema.minimum > value){
162 addError("must have a minimum value of " + schema.minimum);
164 if(typeof schema.maximum !== undefined && typeof value == typeof schema.maximum &&
165 schema.maximum < value){
166 addError("must have a maximum value of " + schema.maximum);
169 var enumer = schema['enum'];
172 for(var j = 0; j < l; j++){
173 if(enumer[j]===value){
179 addError("does not have a value in the enumeration " + enumer.join(", "));
182 if(typeof schema.maxDecimal == 'number' &&
183 (value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){
184 addError("may only have " + schema.maxDecimal + " digits of decimal places");
190 // validate an object against a schema
191 function checkObj(instance,objTypeDef,path,additionalProp){
193 if(typeof objTypeDef =='object'){
194 if(typeof instance != 'object' || instance instanceof Array){
195 errors.push({property:path,message:"an object is required"});
198 for(var i in objTypeDef){
199 if(objTypeDef.hasOwnProperty(i)){
200 var value = instance[i];
201 // skip _not_ specified properties
202 if (value === undefined && options.existingOnly) continue;
203 var propDef = objTypeDef[i];
205 if(value === undefined && propDef["default"]){
206 value = instance[i] = propDef["default"];
208 if(options.coerce && i in instance){
209 value = instance[i] = options.coerce(value, propDef);
211 checkProp(value,propDef,path,i);
216 if(instance.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && objTypeDef && !objTypeDef[i] && additionalProp===false){
217 if (options.filter) {
221 errors.push({property:path,message:(typeof value) + "The property " + i +
222 " is not defined in the schema and the schema does not allow additional properties"});
225 var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires;
226 if(requires && !(requires in instance)){
227 errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"});
230 if(additionalProp && (!(objTypeDef && typeof objTypeDef == 'object') || !(i in objTypeDef))){
232 value = instance[i] = options.coerce(value, additionalProp);
234 checkProp(value,additionalProp,path,i);
236 if(!_changing && value && value.$schema){
237 errors = errors.concat(checkProp(value,value.$schema,path,i));
243 checkProp(instance,schema,'',_changing || '');
245 if(!_changing && instance && instance.$schema){
246 checkProp(instance,instance.$schema,'','');
248 return {valid:!errors.length,errors:errors};
250 exports.mustBeValid = function(result){
252 // This checks to ensure that the result is valid and will throw an appropriate error message if it is not
253 // result: the result returned from checkPropertyChange or validate
255 throw new TypeError(result.errors.map(function(error){return "for property " + error.property + ': ' + error.message;}).join(", \n"));