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