diff --git a/lib/validate.js b/lib/validate.js index a9f1438..6331eb7 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -27,7 +27,7 @@ Date.type = "date"; exports.validate = validate; function validate(/*Any*/instance,/*Object*/schema) { // Summary: - // To use the validator call JSONSchema.validate with an instance object and an optional schema object. + // To use the validator call JSONSchema.validate with an instance object and an optional schema object. // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating), // that schema will be used to validate and the schema parameter is not necessary (if both exist, // both validations will occur). @@ -68,15 +68,15 @@ var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*O if((typeof schema != 'object' || schema instanceof Array) && (path || typeof schema != 'function') && !(schema && schema.type)){ if(typeof schema == 'function'){ if(!(value instanceof schema)){ - addError("is not an instance of the class/constructor " + schema.name); + addError("type"); } }else if(schema){ - addError("Invalid schema/property definition " + schema); + addError("invalid"); } return null; } if(_changing && schema.readonly){ - addError("is a readonly field, it can not be changed"); + addError("readonly"); } if(schema['extends']){ // if it extends another schema, it must pass that schema as well checkProp(value,schema['extends'],path,i); @@ -89,7 +89,7 @@ var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*O !(value instanceof Array && type == 'array') && !(value instanceof Date && type == 'date') && !(type == 'integer' && value%1===0)){ - return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}]; + return [{property:path,message:"type"}]; } if(type instanceof Array){ var unionErrors=[]; @@ -113,13 +113,13 @@ var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*O return []; } if(value === undefined){ - if(!schema.optional && !schema.get){ - addError("is missing and it is not optional"); + if((!schema.optional || typeof schema.optional == 'object' && !schema.optional[options.flavor]) && !schema.get && schema['default'] == null){ + addError("required"); } }else{ errors = errors.concat(checkType(schema.type,value)); if(schema.disallow && !checkType(schema.disallow,value).length){ - addError(" disallowed value was matched"); + addError("disallowed"); } if(value !== null){ if(value instanceof Array){ @@ -135,33 +135,35 @@ var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*O } } if(schema.minItems && value.length < schema.minItems){ - addError("There must be a minimum of " + schema.minItems + " in the array"); + addError("minItems"); } if(schema.maxItems && value.length > schema.maxItems){ - addError("There must be a maximum of " + schema.maxItems + " in the array"); + addError("maxItems"); } }else if(schema.properties || schema.additionalProperties){ errors.concat(checkObj(value, schema.properties, path, schema.additionalProperties)); } if(schema.pattern && typeof value == 'string' && !value.match(schema.pattern)){ - addError("does not match the regex pattern " + schema.pattern); + addError("pattern"); } if(schema.maxLength && typeof value == 'string' && value.length > schema.maxLength){ - addError("may only be " + schema.maxLength + " characters long"); + addError("maxLength"); } if(schema.minLength && typeof value == 'string' && value.length < schema.minLength){ - addError("must be at least " + schema.minLength + " characters long"); + addError("minLength"); } if(typeof schema.minimum !== undefined && typeof value == typeof schema.minimum && schema.minimum > value){ - addError("must have a minimum value of " + schema.minimum); + addError("minimum"); } if(typeof schema.maximum !== undefined && typeof value == typeof schema.maximum && schema.maximum < value){ - addError("must have a maximum value of " + schema.maximum); + addError("maximum"); } if(schema['enum']){ var enumer = schema['enum']; + if (typeof enumer == 'function') enumer = enumer(); + // TODO: if enumer.then --> when(enumer, ...) l = enumer.length; var found; for(var j = 0; j < l; j++){ @@ -171,12 +173,12 @@ var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*O } } if(!found){ - addError("does not have a value in the enumeration " + enumer.join(", ")); + addError("enum"); } } if(typeof schema.maxDecimal == 'number' && (value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){ - addError("may only have " + schema.maxDecimal + " digits of decimal places"); + addError("digits"); } } } @@ -185,19 +187,24 @@ var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*O // validate an object against a schema function checkObj(instance,objTypeDef,path,additionalProp){ - if(typeof objTypeDef =='object'){ + if(typeof objTypeDef == 'object'){ if(typeof instance != 'object' || instance instanceof Array){ - errors.push({property:path,message:"an object is required"}); + errors.push({property:path,message:"type"}); } - - for(var i in objTypeDef){ + + for(var i in objTypeDef){ if(objTypeDef.hasOwnProperty(i)){ var value = instance[i]; // skip _not_ specified properties if (value === undefined && options.existingOnly) continue; var propDef = objTypeDef[i]; + // veto readonly props + if (options.vetoReadOnly && (propDef.readonly === true || typeof propDef.readonly == 'object' && propDef.readonly[options.flavor])) { + delete instance[i]; + continue; + } // set default - if(value === undefined && propDef["default"]){ + if(value === undefined && propDef["default"] && options.flavor != 'get'){ value = instance[i] = propDef["default"]; } if(options.coerce && i in instance){ @@ -208,18 +215,17 @@ var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*O } } for(i in instance){ - if(instance.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && objTypeDef && !objTypeDef[i] && additionalProp===false){ - if (options.filter) { + if(instance.hasOwnProperty(i) && objTypeDef && !objTypeDef[i] && (additionalProp===false || options.removeAdditionalProps)){ + if (options.removeAdditionalProps) { delete instance[i]; continue; } else { - errors.push({property:path,message:(typeof value) + "The property " + i + - " is not defined in the schema and the schema does not allow additional properties"}); + errors.push({property:path,message:"unspecifed"}); } } var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires; if(requires && !(requires in instance)){ - errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"}); + errors.push({property:path,message:"requires"}); } value = instance[i]; if(additionalProp && (!(objTypeDef && typeof objTypeDef == 'object') || !(i in objTypeDef))){ @@ -247,9 +253,58 @@ exports.mustBeValid = function(result){ // This checks to ensure that the result is valid and will throw an appropriate error message if it is not // result: the result returned from checkPropertyChange or validate if(!result.valid){ - throw new TypeError(result.errors.map(function(error){return "for property " + error.property + ': ' + error.message;}).join(", \n")); + throw new TypeError(JSON.stringify(result.errors)); } -} +}; + +/* + * if we'd rely on underscore (U) + */ +/* +exports.coerce = function(instance, schema) { + var date, t; + t = schema.type; + if (t === 'string') { + instance = instance != null ? ''+instance : ''; + } else if (t === 'number' || t === 'integer') { + if (!U.isNaN(instance)) { + instance = +instance; + if (t === 'integer') { + instance = Math.floor(instance); + } + } + } else if (t === 'boolean') { + instance = instance === 'false' ? false : !!instance; + } else if (t === 'null') { + instance = null; + } else if (t === 'object') {} else if (t === 'array') { + instance = U.toArray(instance); + } else if (t === 'date') { + date = new Date(instance); + if (!U.isNaN(date.getTime())) { + instance = date; + } + } + return instance; +}; +exports.validateCoerce = function(instance, schema) { + return validate(instance, schema, { + coerce: exports.coerce + }); +}; +exports.validatePart = function(instance, schema) { + return validate(instance, schema, { + existingOnly: true, + coerce: exports.coerce + }); +}; +exports.validateFilter = function(instance, schema) { + return validate(instance, schema, { + removeAdditionalProps: true, + coerce: exports.coerce + }); +}; +*/ return exports; });