schema.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.json.schema"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.json.schema"] = true;
  8. dojo.provide("dojox.json.schema");
  9. dojox.json.schema.validate = function(/*Any*/instance,/*Object*/schema){
  10. // summary:
  11. // To use the validator call this with an instance object and an optional schema object.
  12. // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
  13. // that schema will be used to validate and the schema parameter is not necessary (if both exist,
  14. // both validations will occur).
  15. // instance:
  16. // The instance value/object to validate
  17. // schema:
  18. // The schema to use to validate
  19. // description:
  20. // The validate method will return an object with two properties:
  21. // valid: A boolean indicating if the instance is valid by the schema
  22. // errors: An array of validation errors. If there are no errors, then an
  23. // empty list will be returned. A validation error will have two properties:
  24. // property: which indicates which property had the error
  25. // message: which indicates what the error was
  26. //
  27. return this._validate(instance,schema,false);
  28. };
  29. dojox.json.schema.checkPropertyChange = function(/*Any*/value,/*Object*/schema, /*String*/ property){
  30. // summary:
  31. // The checkPropertyChange method will check to see if an value can legally be in property with the given schema
  32. // This is slightly different than the validate method in that it will fail if the schema is readonly and it will
  33. // not check for self-validation, it is assumed that the passed in value is already internally valid.
  34. // The checkPropertyChange method will return the same object type as validate, see JSONSchema.validate for
  35. // information.
  36. // value:
  37. // The new instance value/object to check
  38. // schema:
  39. // The schema to use to validate
  40. // return:
  41. // see dojox.validate.jsonSchema.validate
  42. //
  43. return this._validate(value,schema, property || "property");
  44. };
  45. dojox.json.schema.mustBeValid = function(result){
  46. // summary:
  47. // This checks to ensure that the result is valid and will throw an appropriate error message if it is not
  48. // result: the result returned from checkPropertyChange or validate
  49. if(!result.valid){
  50. throw new TypeError(dojo.map(result.errors,function(error){return "for property " + error.property + ': ' + error.message;}).join(", "));
  51. }
  52. }
  53. dojox.json.schema._validate = function(/*Any*/instance,/*Object*/schema,/*Boolean*/ _changing){
  54. var errors = [];
  55. // validate a value against a property definition
  56. function checkProp(value, schema, path,i){
  57. var l;
  58. path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i;
  59. function addError(message){
  60. errors.push({property:path,message:message});
  61. }
  62. if((typeof schema != 'object' || schema instanceof Array) && (path || typeof schema != 'function')){
  63. if(typeof schema == 'function'){
  64. if(!(Object(value) instanceof schema)){
  65. addError("is not an instance of the class/constructor " + schema.name);
  66. }
  67. }else if(schema){
  68. addError("Invalid schema/property definition " + schema);
  69. }
  70. return null;
  71. }
  72. if(_changing && schema.readonly){
  73. addError("is a readonly field, it can not be changed");
  74. }
  75. if(schema['extends']){ // if it extends another schema, it must pass that schema as well
  76. checkProp(value,schema['extends'],path,i);
  77. }
  78. // validate a value against a type definition
  79. function checkType(type,value){
  80. if(type){
  81. if(typeof type == 'string' && type != 'any' &&
  82. (type == 'null' ? value !== null : typeof value != type) &&
  83. !(value instanceof Array && type == 'array') &&
  84. !(type == 'integer' && value%1===0)){
  85. return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}];
  86. }
  87. if(type instanceof Array){
  88. var unionErrors=[];
  89. for(var j = 0; j < type.length; j++){ // a union type
  90. if(!(unionErrors=checkType(type[j],value)).length){
  91. break;
  92. }
  93. }
  94. if(unionErrors.length){
  95. return unionErrors;
  96. }
  97. }else if(typeof type == 'object'){
  98. var priorErrors = errors;
  99. errors = [];
  100. checkProp(value,type,path);
  101. var theseErrors = errors;
  102. errors = priorErrors;
  103. return theseErrors;
  104. }
  105. }
  106. return [];
  107. }
  108. if(value === undefined){
  109. if(!schema.optional){
  110. addError("is missing and it is not optional");
  111. }
  112. }else{
  113. errors = errors.concat(checkType(schema.type,value));
  114. if(schema.disallow && !checkType(schema.disallow,value).length){
  115. addError(" disallowed value was matched");
  116. }
  117. if(value !== null){
  118. if(value instanceof Array){
  119. if(schema.items){
  120. if(schema.items instanceof Array){
  121. for(i=0,l=value.length; i<l; i++){
  122. errors.concat(checkProp(value[i],schema.items[i],path,i));
  123. }
  124. }else{
  125. for(i=0,l=value.length; i<l; i++){
  126. errors.concat(checkProp(value[i],schema.items,path,i));
  127. }
  128. }
  129. }
  130. if(schema.minItems && value.length < schema.minItems){
  131. addError("There must be a minimum of " + schema.minItems + " in the array");
  132. }
  133. if(schema.maxItems && value.length > schema.maxItems){
  134. addError("There must be a maximum of " + schema.maxItems + " in the array");
  135. }
  136. }else if(schema.properties){
  137. errors.concat(checkObj(value,schema.properties,path,schema.additionalProperties));
  138. }
  139. if(schema.pattern && typeof value == 'string' && !value.match(schema.pattern)){
  140. addError("does not match the regex pattern " + schema.pattern);
  141. }
  142. if(schema.maxLength && typeof value == 'string' && value.length > schema.maxLength){
  143. addError("may only be " + schema.maxLength + " characters long");
  144. }
  145. if(schema.minLength && typeof value == 'string' && value.length < schema.minLength){
  146. addError("must be at least " + schema.minLength + " characters long");
  147. }
  148. if(typeof schema.minimum !== undefined && typeof value == typeof schema.minimum &&
  149. schema.minimum > value){
  150. addError("must have a minimum value of " + schema.minimum);
  151. }
  152. if(typeof schema.maximum !== undefined && typeof value == typeof schema.maximum &&
  153. schema.maximum < value){
  154. addError("must have a maximum value of " + schema.maximum);
  155. }
  156. if(schema['enum']){
  157. var enumer = schema['enum'];
  158. l = enumer.length;
  159. var found;
  160. for(var j = 0; j < l; j++){
  161. if(enumer[j]===value){
  162. found=1;
  163. break;
  164. }
  165. }
  166. if(!found){
  167. addError("does not have a value in the enumeration " + enumer.join(", "));
  168. }
  169. }
  170. if(typeof schema.maxDecimal == 'number' &&
  171. (value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){
  172. addError("may only have " + schema.maxDecimal + " digits of decimal places");
  173. }
  174. }
  175. }
  176. return null;
  177. }
  178. // validate an object against a schema
  179. function checkObj(instance,objTypeDef,path,additionalProp){
  180. if(typeof objTypeDef =='object'){
  181. if(typeof instance != 'object' || instance instanceof Array){
  182. errors.push({property:path,message:"an object is required"});
  183. }
  184. for(var i in objTypeDef){
  185. if(objTypeDef.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_')){
  186. var value = instance[i];
  187. var propDef = objTypeDef[i];
  188. checkProp(value,propDef,path,i);
  189. }
  190. }
  191. }
  192. for(i in instance){
  193. if(instance.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && objTypeDef && !objTypeDef[i] && additionalProp===false){
  194. errors.push({property:path,message:(typeof value) + "The property " + i +
  195. " is not defined in the schema and the schema does not allow additional properties"});
  196. }
  197. var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires;
  198. if(requires && !(requires in instance)){
  199. errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"});
  200. }
  201. value = instance[i];
  202. if(objTypeDef && typeof objTypeDef == 'object' && !(i in objTypeDef)){
  203. checkProp(value,additionalProp,path,i);
  204. }
  205. if(!_changing && value && value.$schema){
  206. errors = errors.concat(checkProp(value,value.$schema,path,i));
  207. }
  208. }
  209. return errors;
  210. }
  211. if(schema){
  212. checkProp(instance,schema,'',_changing || '');
  213. }
  214. if(!_changing && instance && instance.$schema){
  215. checkProp(instance,instance.$schema,'','');
  216. }
  217. return {valid:!errors.length,errors:errors};
  218. };
  219. }