UNPKG

11 kBJavaScriptView Raw
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}));