Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n-friendly-error-messages #15

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
113 changes: 84 additions & 29 deletions lib/validate.js
Expand Up @@ -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).
Expand Down Expand Up @@ -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);
Expand All @@ -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=[];
Expand All @@ -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){
Expand All @@ -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++){
Expand All @@ -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");
}
}
}
Expand All @@ -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){
Expand All @@ -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))){
Expand Down Expand Up @@ -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;
});