|
|
- /**
- * 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;
- }));
|