Ticket #5975: jsonSchema.diff
| File jsonSchema.diff, 17.1 kB (added by kriszyp, 8 months ago) |
|---|
-
jsonSchema.js
1 dojo.provide("dojox.validate.jsonSchema"); 2 3 4 dojox.validate.jsonSchema.validate = function(/*Any*/instance,/*Object*/schema) { 5 // summary: 6 // To use the validator call this with an instance object and an optional schema object. 7 // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating), 8 // that schema will be used to validate and the schema parameter is not necessary (if both exist, 9 // both validations will occur). 10 // instance: 11 // The instance value/object to validate 12 // schema: 13 // The schema to use to validate 14 // return: 15 // The validate method will return an object with two properties: 16 // valid: A boolean indicating if the instance is valid by the schema 17 // errors: An array of validation errors. If there are no errors, then an 18 // empty list will be returned. A validation error will have two properties: 19 // property: which indicates which property had the error 20 // message: which indicates what the error was 21 // 22 return this._validate(instance,schema,false); 23 }; 24 dojox.validate.jsonSchema.checkPropertyChange = function(/*Any*/value,/*Object*/schema) { 25 // summary: 26 // The checkPropertyChange method will check to see if an value can legally be in property with the given schema 27 // This is slightly different than the validate method in that it will fail if the schema is readonly and it will 28 // not check for self-validation, it is assumed that the passed in value is already internally valid. 29 // The checkPropertyChange method will return the same object type as validate, see JSONSchema.validate for 30 // information. 31 // instance: 32 // The new instance value/object to check 33 // schema: 34 // The schema to use to validate 35 // return: 36 // see dojox.validate.jsonSchema.validate 37 // 38 return this._validate(value,schema,true); 39 }; 40 dojox.validate.jsonSchema._validate = function(/*Any*/instance,/*Object*/schema,/*Boolean*/ _changing) { 41 42 43 var errors = []; 44 // validate a value against a property definition 45 function checkProp(value, schema, path,i) { 46 if(typeof schema != 'object') { 47 return; 48 } 49 path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i; 50 function addError(message) { 51 errors.push({property:path,message:message}); 52 } 53 if(_changing && schema.readonly) { 54 addError("is a readonly field, it can not be changed"); 55 } 56 if(schema instanceof Array) { 57 if(!(value instanceof Array)) { 58 return [{property:path,message:"An array tuple is required"}]; 59 } 60 for (i =0; i < schema.length; i++) { 61 errors.concat(checkProp(value[i],schema[i],path,i)); 62 } 63 return errors; 64 } 65 if(schema['extends']){ // if it extends another schema, it must pass that schema as well 66 checkProp(value,schema['extends'],path,i); 67 } 68 // validate a value against a type definition 69 function checkType(type,value) { 70 if(type) { 71 if(typeof type == 'string' && type != 'any' 72 && (type == 'null' ? value !== null : typeof value != type) 73 && !(value instanceof Array && type == 'array') 74 && !(type == 'integer' && !(value%1))){ 75 return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}]; 76 } 77 if(type instanceof Array){ 78 var unionErrors=[]; 79 for (var j = 0; j < type.length; j++){// a union type 80 if(!(unionErrors=checkType(type[j],value)).length){ 81 break; 82 } 83 } 84 if(unionErrors.length){ 85 return unionErrors; 86 } 87 } 88 else if(typeof type == 'object') { 89 checkProp(value,type,path); 90 } 91 } 92 return []; 93 } 94 if(value !== null) { 95 if(value === undefined) { 96 if(!schema.optional){ 97 addError("is missing and it is not optional"); 98 } 99 } 100 else { 101 errors = errors.concat(checkType(schema.type,value)); 102 if(schema.disallow && !checkType(schema.disallow,value).length){ 103 addError(" disallowed value was matched"); 104 } 105 if(value instanceof Array) { 106 if(schema.items) { 107 for (var i =0,l=value.length; i < l; i++) { 108 errors.concat(checkProp(value[i],schema.items,path,i)); 109 } 110 } 111 if(schema.minItems && value.length < schema.minItems) { 112 addError("There must be a minimum of " + schema.minItems + " in the array"); 113 } 114 if(schema.maxItems && value.length > schema.maxItems) { 115 addError("There must be a maximum of " + schema.maxItems + " in the array"); 116 } 117 } 118 else if(schema.properties && typeof value == 'object') { 119 errors.concat(checkObj(value,schema.properties,path,schema.additionalProperties)); 120 } 121 if(schema.pattern && typeof value == 'string' && !value.match(schema.pattern)){ 122 addError("does not match the regex pattern " + schema.pattern); 123 } 124 if(schema.maxLength && typeof value == 'string' && value.maxLength > schema.maxLength){ 125 addError("may only be " + schema.maxLength + " characters long"); 126 } 127 if(schema.minLength && typeof value == 'string' && value.minLength < schema.minLength){ 128 addError("must be at least " + schema.minLength + " characters long"); 129 } 130 if(typeof schema.minimum !== undefined && typeof value == typeof schema.minimum && schema.minimum > value){ 131 addError("must have a minimum value of " + schema.minimum); 132 } 133 if(typeof schema.maximum !== undefined && typeof value == typeof schema.maximum && schema.maximum < value){ 134 addError("must have a maximum value of " + schema.maximum); 135 } 136 if(schema.options && !schema.unconstrained) { 137 var l = schema.options.length; 138 var found; 139 for(var j = 0; j < l; j++){ 140 if(schema.options[j].value===value) { 141 found=1; 142 break; 143 } 144 } 145 if(!found) { 146 var labels = []; 147 var options = schema.options; 148 for (var i = 0;i < options.length;i++) { 149 labels.push(options[i].label || options[i].value); 150 } 151 addError("does not have a value in the options " + labels.join(", ")); 152 } 153 } 154 if(typeof schema.maxDecimal == 'number' && (value * 10^schema.maxDecimal)%1){ 155 addError("may only have " + schema.maxDecimal + " digits of decimal places"); 156 } 157 } 158 } 159 160 } 161 // validate an object against a schema 162 function checkObj(instance,objTypeDef,path,additionalProp) { 163 164 if(typeof objTypeDef =='object') { 165 if(typeof instance != 'object' || instance instanceof Array){ 166 errors.push({property:path,message:"an object is required"}); 167 } 168 169 for(var i in objTypeDef){ 170 if(objTypeDef.hasOwnProperty(i)) { 171 var value = instance[i]; 172 var propDef = objTypeDef[i]; 173 checkProp(value,propDef,path,i); 174 } 175 } 176 } 177 for (var i in instance) { 178 if(instance.hasOwnProperty(i) && objTypeDef && !objTypeDef[i] && additionalProp===false){ 179 errors.push({property:path,message:(typeof value) + "The property " + i + " is not defined in the objTypeDef and the objTypeDef does not allow additional properties"}); 180 } 181 var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires; 182 if(requires && !(requires in instance)){ 183 errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"}); 184 } 185 value = instance[i]; 186 if(objTypeDef && typeof objTypeDef == 'object' && !(i in objTypeDef)){ 187 checkProp(value,additionalProp,path,i); 188 } 189 // if(!_changing && value && value.type) 190 // errors = errors.concat(checkObj(value,value.type,path + '.' + i)); 191 if(!_changing && value && value.$schema){ 192 errors = errors.concat(checkProp(value,value.$schema,path,i)); 193 } 194 } 195 return errors; 196 } 197 if(schema){ 198 checkProp(instance,schema,'',''); 199 } 200 if(!_changing && instance.$schema){ 201 checkProp(instance,instance.$schema,'',''); 202 } 203 return {valid:!errors.length,errors:errors}; 204 }; 205 /* will add this later 206 newFromSchema : function() { 207 } 208 */ -
tests/jsonSchema.js
1 dojo.provide("dojox.validate.tests.jsonSchema"); 2 dojo.require("dojox.validate.jsonSchema"); 3 dojo.require("dojox.json.ref"); 4 5 var simpleObj = { 6 "name" : "John Doe", 7 "age" : 30, 8 "schema":{ 9 "type" : { 10 "name": {"type":"string", 11 "required":true}, 12 "age" : {"type":"number", 13 "maximum":125} 14 } 15 } 16 } 17 var biggerObj = { 18 "name" : "John Doe", 19 "born" : "1979-03-23T06:26:07Z", 20 "gender" : "male", 21 tuple:[1,"a",false], 22 "address" : {"street":"123 S Main St", 23 "city":"Springfield", 24 "state":"CA"} 25 26 } 27 var invalidBiggerObj = { 28 "name" : "John Doe", 29 "born" : false, 30 "gender" : "mal", 31 "address" : {"street":"123 S Main St", 32 "city":"Springfield", 33 "state":"CA"} 34 35 } 36 var biggerSchema = { 37 "readonly":true, 38 "type":"object", 39 "properties":{ 40 "name": {"type":"string", 41 length:5, 42 "optional":false}, 43 "tuple":[{"type":"integer"}, 44 {"type":"string"}, 45 {"type":"boolean"}], 46 "born" : {"type":["number","string"], //allow for a numeric year, or a full date 47 "format":"date", //format when a string value is used 48 "minimum":1900, //min/max for when a number value is used 49 "maximum":2010 50 }, 51 "gender" : {"type":"string", 52 "options":[{"label":"Male",value:"male"},{"label":"Female",value:"female"}]}, 53 "address" : {"type":"object", 54 "properties": 55 {"street":{"type":"string"}, 56 "city":{"type":"string"}, 57 "state":{"type":"string"}} 58 } 59 } 60 } 61 var schemaForSchemas ={"description":"This is the JSON Schema for JSON Schemas.", 62 "type":["object","array"], 63 "items":{ 64 "type":"object", 65 "properties":{"$ref":"$.properties"}, 66 "description":"When the schema is an array, it indicates that it is enforcing tuple typing. Each item in the instance array must correspond to the item in the schema array"}, 67 "properties":{ 68 "type":{ 69 "type":["string","array"], 70 "items":{"$ref":"$.properties.type"}, 71 "description":"This is a type definition value. This can be a simple type, or a union type", 72 "options":["string","object","array","boolean","number","integer","null","any"], 73 "unconstrained":true, 74 "optional":true, 75 "default":"any"}, 76 "optional":{ 77 "type":"boolean", 78 "description":"This indicates that the instance property in the instance object is not required.", 79 "optional":true, 80 "default":false}, 81 "properties":{ 82 "type":"object", 83 "additionalProperties":{"$ref":"$"}, 84 "description":"This is a definition for the properties of an object value", 85 "optional":true, 86 "default":{} 87 }, 88 "items":{ 89 "type":"object", 90 "properties":{"$ref":"$.properties"}, 91 "description":"When the value is an array, this indicates the schema to use to validate each item in an array", 92 "optional":true, 93 "default":{}}, 94 "additionalProperties":{ 95 "type":["boolean","object"], 96 "properties":{"$ref":"$.properties"}, 97 "description":"This provides a default property definition for all properties that are not explicitly defined in an object type definition.", 98 "optional":true, 99 "default":{}}, 100 "specificity":{ 101 "type":"number", 102 "description":"This indicates an order of specificity of properties. If an instance defines another property with a higher specificity than this one, than this instance property is required.", 103 "optional":true, 104 "default":false}, 105 "unique":{ 106 "type":"boolean", 107 "description":"This indicates that the instance property should have unique values. No other property with the same name in the instance object tree should have the same value.", 108 "optional":true, 109 "default":false}, 110 "minimum":{ 111 "type":"number", 112 "optional":true, 113 "description":"This indicates the minimum value for the instance property when the type of the instance value is a number, or it indicates the minimum number of values in an array when an array is the instance value."}, 114 "maximum":{ 115 "type":"number", 116 "optional":true, 117 "description":"This indicates the maximum value for the instance property when the type of the instance value is a number, or it indicates the maximum number of values in an array when an array is the instance value."}, 118 "pattern":{ 119 "type":"string", 120 "format":"regex", 121 "description":"When the instance value is a string, this provides a regular expression that a instance string value should match in order to be valid.", 122 "optional":true, 123 "default":".*"}, 124 "maxLength" :{ 125 "type":"number", 126 "optional":true, 127 "description":"When the instance value is a string, this indicates maximum length of the string."}, 128 "minLength" :{ 129 "type":"number", 130 "optional":true, 131 "description":"When the instance value is a string, this indicates minimum length of the string."}, 132 "maxItems" :{ 133 "type":"number", 134 "optional":true, 135 "description":"When the instance value is an array, this indicates maximum number of items."}, 136 "minItems" :{ 137 "type":"number", 138 "optional":true, 139 "description":"When the instance value is an array, this indicates minimum number of items."}, 140 "options" : { 141 "type":[], 142 "optional":true, 143 "description":"This provides an enumeration of possible values that are valid for the instance property."}, 144 "unconstrained":{ 145 "type":"boolean", 146 "description":"When used in conjunction with the options property, this indicates a value can be used that is not in the list of options. This has no meaning when the options property is not a sibling.", 147 "optional":true, 148 "default":false}, 149 "readonly":{ 150 "type":"boolean", 151 "description":"This indicates that the instance property should not be changed (this is only for interaction, it has no effect for standalone validation).", 152 "optional":true, 153 "default":false}, 154 "description":{ 155 "type":"string", 156 "optional":true, 157 "description":"This provides a description of the purpose the instance property. The value can be a string or it can be an object with properties corresponding to various different instance languages (with an optional default property indicating the default description)."}, 158 "format":{ 159 "type":"string", 160 "optional":true, 161 "description":"This indicates what format the data is among some predefined formats which may include:\n\ndate - a string following the ISO format \naddress \nschema - a schema definition object \nperson \npage \nhtml - a string representing HTML"}, 162 "default":{ 163 "type":"any", 164 "optional":true, 165 "description":"This indicates the default for the instance property."}, 166 "transient":{ 167 "type":"boolean", 168 "optional":true, 169 "description":"This indicates that the property will be used for transient/volatile values that should not be persisted.", 170 "default":false}, 171 "maxDecimal":{ 172 "type":"integer", 173 "optional":true, 174 "description":"This indicates the maximum number of decimal places in a floating point number."}, 175 "hidden":{ 176 "type":"boolean", 177 "optional":true, 178 "description":"This indicates whether the property should be hidden in user interfaces."}, 179 "extends":{ 180 "type":"object", 181 "properties":{"$ref":"$.properties"}, 182 "description":"This indicates the schema extends the given schema. All instances of this schema must be valid to by the extended schema also.", 183 "optional":true, 184 "default":{}}, 185 "id":{ 186 "type":["string","number"], 187 "optional":true, 188 "format":"url", 189 "unique":true} 190 } 191 } 192 schemaForSchemas = dojox.json.ref.resolveJson(schemaForSchemas); 193 doh.register("dojox.validate.tests.jsonSchema", 194 [{ 195 name:"isValidForSchema", 196 runTest: function(t) { 197 doh.t(dojox.validate.jsonSchema.validate(simpleObj).valid); //a simple self-validating test 198 doh.t(dojox.validate.jsonSchema.validate(biggerObj,biggerSchema).valid); //a bigger instance and schema 199 doh.f(dojox.validate.jsonSchema.validate(invalidBiggerObj,biggerSchema).valid); //should have two errors 200 doh.t(dojox.validate.jsonSchema.validate(schemaForSchemas,schemaForSchemas).valid); //test the schema for schemas against it's self. Narcissitic testing 201 doh.t(dojox.validate.jsonSchema.validate(biggerSchema,schemaForSchemas).valid); //Test the big schema against the schema for schemas 202 } 203 }, 204 { 205 name:"isValidPropertyChange", 206 runTest: function(t) { 207 doh.f(dojox.validate.jsonSchema.checkPropertyChange(biggerObj,biggerSchema).valid); //this should fail because of the 208 doh.t(dojox.validate.jsonSchema.checkPropertyChange(schemaForSchemas,schemaForSchemas).valid); //schema should be valid 209 } 210 } 211 ]);