[6a3a178] | 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.
|
---|
| 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
|
---|
| 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
|
---|
| 32 | var exports = validate
|
---|
| 33 | exports.Integer = {type:"integer"};
|
---|
| 34 | var primitiveConstructors = {
|
---|
| 35 | String: String,
|
---|
| 36 | Boolean: Boolean,
|
---|
| 37 | Number: Number,
|
---|
| 38 | Object: Object,
|
---|
| 39 | Array: Array,
|
---|
| 40 | Date: Date
|
---|
| 41 | }
|
---|
| 42 | exports.validate = validate;
|
---|
| 43 | function 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 | };
|
---|
| 58 | exports.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 | };
|
---|
| 68 | var 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 | };
|
---|
| 263 | exports.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 |
|
---|
| 272 | return exports;
|
---|
| 273 | }));
|
---|