Ticket #5975: jsonSchema.diff

File jsonSchema.diff, 17.1 kB (added by kriszyp, 8 months ago)

JSON Schema

  • jsonSchema.js

     
     1dojo.provide("dojox.validate.jsonSchema"); 
     2 
     3 
     4dojox.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}; 
     24dojox.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}; 
     40dojox.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

     
     1dojo.provide("dojox.validate.tests.jsonSchema"); 
     2dojo.require("dojox.validate.jsonSchema"); 
     3dojo.require("dojox.json.ref"); 
     4 
     5var 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} 
     17var 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} 
     27var 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} 
     36var 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} 
     192schemaForSchemas = dojox.json.ref.resolveJson(schemaForSchemas); 
     193doh.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]);