• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * JSONSchema Validator - Validates JavaScript objects using JSON Schemas
3 *	(http://www.json.com/json-schema-proposal/)
4 *
5 * Copyright (c) 2007 Kris Zyp SitePen (www.sitepen.com)
6 * Licensed under the MIT (MIT-LICENSE.txt) license.
7To use the validator call the validate function with an instance object and an optional schema object.
8If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
9that schema will be used to validate and the schema parameter is not necessary (if both exist,
10both validations will occur).
11The validate method will return an array of validation errors. If there are no errors, then an
12empty 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
15 */
16(function (root, factory) {
17    if (typeof define === 'function' && define.amd) {
18        // AMD. Register as an anonymous module.
19        define([], function () {
20            return factory();
21        });
22    } else if (typeof module === 'object' && module.exports) {
23        // Node. Does not work with strict CommonJS, but
24        // only CommonJS-like environments that support module.exports,
25        // like Node.
26        module.exports = factory();
27    } else {
28        // Browser globals
29        root.jsonSchema = factory();
30    }
31}(this, function () {// setup primitive classes to be JSON Schema types
32var exports = validate
33exports.Integer = {type:"integer"};
34var primitiveConstructors = {
35	String: String,
36	Boolean: Boolean,
37	Number: Number,
38	Object: Object,
39	Array: Array,
40	Date: Date
41}
42exports.validate = validate;
43function validate(/*Any*/instance,/*Object*/schema) {
44		// Summary:
45		//  	To use the validator call JSONSchema.validate with an instance object and an optional schema object.
46		// 		If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
47		// 		that schema will be used to validate and the schema parameter is not necessary (if both exist,
48		// 		both validations will occur).
49		// 		The validate method will return an object with two properties:
50		// 			valid: A boolean indicating if the instance is valid by the schema
51		// 			errors: An array of validation errors. If there are no errors, then an
52		// 					empty list will be returned. A validation error will have two properties:
53		// 						property: which indicates which property had the error
54		// 						message: which indicates what the error was
55		//
56		return validate(instance, schema, {changing: false});//, coerce: false, existingOnly: false});
57	};
58exports.checkPropertyChange = function(/*Any*/value,/*Object*/schema, /*String*/property) {
59		// Summary:
60		// 		The checkPropertyChange method will check to see if an value can legally be in property with the given schema
61		// 		This is slightly different than the validate method in that it will fail if the schema is readonly and it will
62		// 		not check for self-validation, it is assumed that the passed in value is already internally valid.
63		// 		The checkPropertyChange method will return the same object type as validate, see JSONSchema.validate for
64		// 		information.
65		//
66		return validate(value, schema, {changing: property || "property"});
67	};
68var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*Object*/options) {
69
70	if (!options) options = {};
71	var _changing = options.changing;
72
73	function getType(schema){
74		return schema.type || (primitiveConstructors[schema.name] == schema && schema.name.toLowerCase());
75	}
76	var errors = [];
77	// validate a value against a property definition
78	function checkProp(value, schema, path,i){
79
80		var l;
81		path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i;
82		function addError(message){
83			errors.push({property:path,message:message});
84		}
85
86		if((typeof schema != 'object' || schema instanceof Array) && (path || typeof schema != 'function') && !(schema && getType(schema))){
87			if(typeof schema == 'function'){
88				if(!(value instanceof schema)){
89					addError("is not an instance of the class/constructor " + schema.name);
90				}
91			}else if(schema){
92				addError("Invalid schema/property definition " + schema);
93			}
94			return null;
95		}
96		if(_changing && schema.readonly){
97			addError("is a readonly field, it can not be changed");
98		}
99		if(schema['extends']){ // if it extends another schema, it must pass that schema as well
100			checkProp(value,schema['extends'],path,i);
101		}
102		// validate a value against a type definition
103		function checkType(type,value){
104			if(type){
105				if(typeof type == 'string' && type != 'any' &&
106						(type == 'null' ? value !== null : typeof value != type) &&
107						!(value instanceof Array && type == 'array') &&
108						!(value instanceof Date && type == 'date') &&
109						!(type == 'integer' && value%1===0)){
110					return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}];
111				}
112				if(type instanceof Array){
113					var unionErrors=[];
114					for(var j = 0; j < type.length; j++){ // a union type
115						if(!(unionErrors=checkType(type[j],value)).length){
116							break;
117						}
118					}
119					if(unionErrors.length){
120						return unionErrors;
121					}
122				}else if(typeof type == 'object'){
123					var priorErrors = errors;
124					errors = [];
125					checkProp(value,type,path);
126					var theseErrors = errors;
127					errors = priorErrors;
128					return theseErrors;
129				}
130			}
131			return [];
132		}
133		if(value === undefined){
134			if(schema.required){
135				addError("is missing and it is required");
136			}
137		}else{
138			errors = errors.concat(checkType(getType(schema),value));
139			if(schema.disallow && !checkType(schema.disallow,value).length){
140				addError(" disallowed value was matched");
141			}
142			if(value !== null){
143				if(value instanceof Array){
144					if(schema.items){
145						var itemsIsArray = schema.items instanceof Array;
146						var propDef = schema.items;
147						for (i = 0, l = value.length; i < l; i += 1) {
148							if (itemsIsArray)
149								propDef = schema.items[i];
150							if (options.coerce)
151								value[i] = options.coerce(value[i], propDef);
152							errors.concat(checkProp(value[i],propDef,path,i));
153						}
154					}
155					if(schema.minItems && value.length < schema.minItems){
156						addError("There must be a minimum of " + schema.minItems + " in the array");
157					}
158					if(schema.maxItems && value.length > schema.maxItems){
159						addError("There must be a maximum of " + schema.maxItems + " in the array");
160					}
161				}else if(schema.properties || schema.additionalProperties){
162					errors.concat(checkObj(value, schema.properties, path, schema.additionalProperties));
163				}
164				if(schema.pattern && typeof value == 'string' && !value.match(schema.pattern)){
165					addError("does not match the regex pattern " + schema.pattern);
166				}
167				if(schema.maxLength && typeof value == 'string' && value.length > schema.maxLength){
168					addError("may only be " + schema.maxLength + " characters long");
169				}
170				if(schema.minLength && typeof value == 'string' && value.length < schema.minLength){
171					addError("must be at least " + schema.minLength + " characters long");
172				}
173				if(typeof schema.minimum !== undefined && typeof value == typeof schema.minimum &&
174						schema.minimum > value){
175					addError("must have a minimum value of " + schema.minimum);
176				}
177				if(typeof schema.maximum !== undefined && typeof value == typeof schema.maximum &&
178						schema.maximum < value){
179					addError("must have a maximum value of " + schema.maximum);
180				}
181				if(schema['enum']){
182					var enumer = schema['enum'];
183					l = enumer.length;
184					var found;
185					for(var j = 0; j < l; j++){
186						if(enumer[j]===value){
187							found=1;
188							break;
189						}
190					}
191					if(!found){
192						addError("does not have a value in the enumeration " + enumer.join(", "));
193					}
194				}
195				if(typeof schema.maxDecimal == 'number' &&
196					(value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){
197					addError("may only have " + schema.maxDecimal + " digits of decimal places");
198				}
199			}
200		}
201		return null;
202	}
203	// validate an object against a schema
204	function checkObj(instance,objTypeDef,path,additionalProp){
205
206		if(typeof objTypeDef =='object'){
207			if(typeof instance != 'object' || instance instanceof Array){
208				errors.push({property:path,message:"an object is required"});
209			}
210
211			for(var i in objTypeDef){
212				if(objTypeDef.hasOwnProperty(i)){
213					var value = instance[i];
214					// skip _not_ specified properties
215					if (value === undefined && options.existingOnly) continue;
216					var propDef = objTypeDef[i];
217					// set default
218					if(value === undefined && propDef["default"]){
219						value = instance[i] = propDef["default"];
220					}
221					if(options.coerce && i in instance){
222						value = instance[i] = options.coerce(value, propDef);
223					}
224					checkProp(value,propDef,path,i);
225				}
226			}
227		}
228		for(i in instance){
229			if(instance.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && objTypeDef && !objTypeDef[i] && additionalProp===false){
230				if (options.filter) {
231					delete instance[i];
232					continue;
233				} else {
234					errors.push({property:path,message:(typeof value) + "The property " + i +
235						" is not defined in the schema and the schema does not allow additional properties"});
236				}
237			}
238			var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires;
239			if(requires && !(requires in instance)){
240				errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"});
241			}
242			value = instance[i];
243			if(additionalProp && (!(objTypeDef && typeof objTypeDef == 'object') || !(i in objTypeDef))){
244				if(options.coerce){
245					value = instance[i] = options.coerce(value, additionalProp);
246				}
247				checkProp(value,additionalProp,path,i);
248			}
249			if(!_changing && value && value.$schema){
250				errors = errors.concat(checkProp(value,value.$schema,path,i));
251			}
252		}
253		return errors;
254	}
255	if(schema){
256		checkProp(instance,schema,'',_changing || '');
257	}
258	if(!_changing && instance && instance.$schema){
259		checkProp(instance,instance.$schema,'','');
260	}
261	return {valid:!errors.length,errors:errors};
262};
263exports.mustBeValid = function(result){
264	//	summary:
265	//		This checks to ensure that the result is valid and will throw an appropriate error message if it is not
266	// result: the result returned from checkPropertyChange or validate
267	if(!result.valid){
268		throw new TypeError(result.errors.map(function(error){return "for property " + error.property + ': ' + error.message;}).join(", \n"));
269	}
270}
271
272return exports;
273}));
274