You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					272 lines
				
				11 KiB
			
		
		
			
		
	
	
					272 lines
				
				11 KiB
			| 
											3 years ago
										 | /** | ||
|  |  * JSONSchema Validator - Validates JavaScript objects using JSON Schemas | ||
|  |  *	(http://www.json.com/json-schema-proposal/)
 | ||
|  |  * Licensed under AFL-2.1 OR BSD-3-Clause | ||
|  | 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:value + " - " + (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) && i != '__proto__' && i != 'constructor'){ | ||
|  | 					var value = instance.hasOwnProperty(i) ? instance[i] : undefined; | ||
|  | 					// 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:"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; | ||
|  | })); |