Permalink
Browse files

the great error message cleanup!

  • Loading branch information...
1 parent e4a4947 commit 3de5628fddad67de3c810464bf746d71bda563fb @bronson committed Oct 6, 2011
Showing with 196 additions and 185 deletions.
  1. +29 −20 README.md
  2. +47 −46 lib/valid.js
  3. +91 −90 test/valid.test.js
  4. +29 −29 test/validjson.test.js
View
@@ -41,13 +41,11 @@ A lightweight, chaining validation library.
// Easily define your own validations:
Valid.isPowerOfTen = Valid.mod(10).message("should be a power of ten).define();
Valid.min(10).max(100).isPowerOfTen().check(50);
-```
# Gruntles
This library is scary new.
-- covert to Rails-like "5 should equal 4" instead of current "5 is not equal to 4"?
- simplify error objects
- try to shrink the api, implement kitchen-sink
- npm publish
@@ -58,6 +56,8 @@ This library is scary new.
- Allow putting value first? i.e. Valid(9).lt(12).gt(10) throws "9 is not greater than 10"
- write an assertion function? Valid.assert(12).integer().min(5);
- convert to using nested functions instead of the `__queue` array?
+- 'not' should try to modify the error message on the way through
+- Valid.Escape needs some love
- do a doctest somehow
# Introduction
@@ -77,36 +77,45 @@ below). isValid() just returns true or false.
# Built-In Validations
-This is probably incomplete.
-See [valid.js](https://github.com/bronson/valid/blob/master/lib/valid.js).
+This list is probably incomplete but the code at the bottom of
+[valid.js](https://github.com/bronson/valid/blob/master/lib/valid.js)
+should be reasonably readable.
-- Presence: defined(), undef(), undefined\*(), nil(), null\*(), notNull()
-- Equality: equal(a[,b...]), notEqual(...), oneOf(arrayOrObject), in\*(arrayOrObject)
+- Presence: defined(), undef(), \*undefined(), nil(), \*null(), notNull()
+- Pass error message to Valid constructor? make errorMessage a synonym for message?
+- Equality: equal(a[,b...]), notEqual(...), oneOf(arrayOrObject), \*in(arrayOrObject)
- Comparison: eq(n), lt(n), le(n), ge(n), gt(n), ne(n)
- Numbers: number(), integer(), mod(x[,rem]), even(), odd()
-- Booleans: boolean(), isTrue(), true\*(), isFalse(), false\*()
+- Booleans: \*boolean(), isTrue(), \*true(), isFalse(), \*false()
- Arrays: array([validationForEachItem]), len(min,max), empty()
- Strings: string(), len(min,max), blank(), notBlank()
- Regexps: match(regex[,modifiers]), nomatch(regex[,modifiers])
- Logic: and(test[,test...]), or(test[,test...]), not(test,message)
- Utilities: nop(), fail([message]), message(msg), todo([test])
- JSON: json(schema)
-\*: These are JavaScript keywords. While `Valid.undefined()` works
-with a lot of interpreters, it doesn't work everywhere.
-Each keyword validation has a more compatible alternative that should
-be used instead: Valid.undef(), Valid.nil(), etc.
+\*: These function names are also JavaScript keywords. While
+`Valid.undefined()` works with a lot of interpreters, it doesn't work
+everywhere. Each keyword validation has a more compatible alternative that
+should be used instead: Valid.undef(), Valid.nil(), etc.
# Errors
-When a validation fails, the validation returns a string ready
-for concatenation: "is not even", "is not equal to 12", "mod 4 is 2".
-If the error is being thrown then the failing value is tacked onto the front:
-"3 is not even", "null is not equal to 12", "6 mod 4 is 2".
+When a validation fails, check() returns a concise, positive message with the
+value implied at the front: "must be even", "can't be blank", etc. The messages
+are meant to be understandable by end users.
+
+Concatenating the value and message is one obvioius use ("7 must be even"), but
+this style of wording is useful in other ways too. For instance, a web app can
+display the message to the right of the form element containing the error.
+
+It's easy to supply your own error messages:
+
+ Valid.match(/-/).message("must contain a dash")
-There is one exception: JSON validations. Because they can return multiple
-errors, they return an object instead of a string. The `value` field contains
-the value that failed to validate.
+Because JSON validations need to return multiple errors, they return an object
+instead of a string. The `value` field contains the value that failed to
+validate.
```javascript
{
@@ -129,8 +138,8 @@ and add it to the root object:
You can also add validations that take parameters:
```javascript
- Valid.mod10 = function(rem) { return this.mod(10,rem) }
- Valid.mod10(6).check(127); // returns "127 mod 10 is 7 not 6"
+ Valid.mod10 = function(rem) { return this.mod(10,rem).message("must end in" + rem); }
+ Valid.mod10(6).check(127); // returns "must end in 6"
```
Or just rename them:
View
@@ -106,7 +106,7 @@ Valid.isValid = function isValid(value) {
Valid.nop = Valid.SimpleValidation(function Nop(val) { });
Valid.fail = Valid.SimpleValidation(function Fail(val,msg) { return msg || "failed"; });
-Valid.mod = Valid.SimpleValidation(function mod(val,by,rem) { if(val%by !== (rem||0)) return "mod "+by+" is "+(val%by)+" not "+rem; });
+Valid.mod = Valid.SimpleValidation(function mod(val,by,rem) { if(val%by !== (rem||0)) return "mod "+by+" must be "+rem+" not "+val%by; });
Valid.optional = Valid.SimpleValidation(function Optional(value) { if(value === null || value === undefined) return Valid; });
Valid.equal = Valid.SimpleValidation(function Equal(value) {
@@ -118,39 +118,40 @@ Valid.equal = Valid.SimpleValidation(function Equal(value) {
if(value === arguments[i]) return;
opts.push(this.Escape(arguments[i]));
}
- if(arguments.length === 2) return "is not equal to " + opts[0];
+ if(arguments.length === 2) return "must equal " + opts[0];
var lastopt = opts.pop();
- return "is not " + opts.join(", ") + " or " + lastopt;
+ return "must be " + opts.join(", ") + " or " + lastopt;
});
Valid.oneOf = Valid.SimpleValidation(function OneOf(value,collection) {
if(collection === null || collection === undefined) return "oneOf needs a collection";
if(value in collection) return;
- return "is not one of the options";
+ return "is not an option";
});
Valid.type = Valid.SimpleValidation(function Type(value,type) {
if(typeof type !== 'string') return "type requires a string argument, not "+(typeof type);
- if(typeof value !== type) return "is of type " + (typeof value) + " not " + type;
+ if(typeof value !== type) return "must be of type " + type + " not " + (typeof value);
});
Valid.array = Valid.SimpleValidation(function Arry(value, validation) {
- if(!Array.isArray(value)) return "is not an array";
+ if(!Array.isArray(value)) return "must be an array";
if(validation !== undefined) {
for(var i=0; i<value.length; i++) {
var error = this.ValidateQueue(validation._queue, value[i]);
- if(error) return "item " + i + " " + error;
+ if(error) return "item " + i + " " + error; // TODO: this sucks
}
}
});
Valid.len = Valid.SimpleValidation(function Len(value,min,max) {
- if(typeof value === 'null' || typeof value === 'undefined' || typeof value.length === 'undefined') return "doesn't have a length field";
- if(typeof value.length !== 'number') return "length field is of type " + (typeof value.length) + ", not number";
+ var items = typeof value === 'string' ? 'character' : 'element';
+ if(typeof value === 'null' || typeof value === 'undefined' || typeof value.length === 'undefined') return "must have a length field";
+ if(typeof value.length !== 'number') return "must have a numeric length field, not " + (typeof value.length);
// now we can read the property without risking throwing an exception
- if(value.length < min) return "has length " + value.length + ", less than " + min;
+ if(value.length < min) return "is too short (minimum is " + min + " " + items + (min === 1 ? '' : 's') + ")";
if(typeof max !== undefined) {
- if(value.length > max) return "has length " + value.length + ", greater than " + max;
+ if(value.length > max) return "is too long (maximum is " + max + " " + items + (max === 1 ? '' : 's') + ")";
}
});
@@ -164,7 +165,7 @@ Valid.message = function message(msg) {
Valid.not = Valid.SimpleValidation(function Not(value, validation, message) {
var error = this.ValidateQueue(validation._queue, value);
- if(!error) return message || "validation should have failed";
+ if(!error) return message || "validation must fail";
});
// seems somewhat useless since V.a().b() is the same as V.and(V.a(),V.b())
@@ -187,58 +188,58 @@ Valid.or = function or() {
if(!error) return; // short circuit
errors.push(error);
}
- return errors.length > 0 ? errors.join(" and ") : undefined;
+ return errors.length > 0 ? errors.join(" or ") : undefined;
}, chains);
};
Valid.match = function match(pattern, modifiers) {
if(typeof pattern !== 'function') pattern = new RegExp(pattern, modifiers);
return this.string().AddValidation( function Match(value) {
- if(!value.match(pattern)) return "does not match " + pattern;
+ if(!value.match(pattern)) return "must match " + pattern;
}, pattern);
};
// composite validations
-Valid.undef = Valid.equal(undefined).define();
-Valid.defined = Valid.not(Valid.undef(), "is undefined").define();
-Valid.nil = Valid.equal(null).define();
-Valid.notNull = Valid.not(Valid.nil(), "is null").define();
-Valid.noexisty = Valid.equal(undefined, null).define();
-Valid.exists = Valid.not(Valid.noexisty(), "does not exist").define();
-Valid.empty = Valid.len(0,0).message("should be empty").define();
-Valid.boolean = Valid.type('boolean').define();
-Valid.isTrue = Valid.equal(true).define();
-Valid.isFalse = Valid.equal(false).define();
-Valid.number = Valid.type('number').define();
-Valid.integer = Valid.number().mod(1).message("is not an integer").define();
-Valid.even = Valid.number().mod(2).message("is not even").define();
-Valid.odd = Valid.number().mod(2,1).message("is not odd").define();
-Valid.string = Valid.type('string').define();
-Valid.blank = Valid.optional().match(/^\s*$/).message("is not blank").define();
-Valid.notBlank = Valid.not(Valid.blank(), "is blank").define();
-Valid.object = Valid.type('object').define();
+Valid.undef = Valid.equal(undefined).message("must be undefined").define();
+Valid.defined = Valid.not(Valid.undef(), "can't be undefined").define();
+Valid.nil = Valid.equal(null).message("must be null").define();
+Valid.notNull = Valid.not(Valid.nil(), "can't be null").define();
+Valid.noexisty = Valid.equal(undefined, null).message("must not exist").define();
+Valid.exists = Valid.not(Valid.noexisty(), "must exist").define();
+Valid.empty = Valid.len(0,0).message("must be empty").define();
+Valid.isBoolean = Valid.type('boolean').message("must be a boolean").define();
+Valid.isTrue = Valid.equal(true).message("must be true").define();
+Valid.isFalse = Valid.equal(false).message("must be false").define();
+Valid.number = Valid.type('number').message("must be a number").define();
+Valid.integer = Valid.number().mod(1).message("must be an integer").define();
+Valid.even = Valid.number().mod(2).message("must be even").define();
+Valid.odd = Valid.number().mod(2,1).message("must be odd").define();
+Valid.string = Valid.type('string').message("must be a string").define();
+Valid.blank = Valid.optional().match(/^\s*$/).message("must be blank").define();
+Valid.notBlank = Valid.not(Valid.blank(), "can't be blank").define();
// reserved words, calling them with dot notation may cause problems with crappy JS implementations
Valid['undefined'] = Valid.undef;
Valid['null'] = Valid.nil;
+Valid['boolean'] = Valid.isBoolean;
Valid['true'] = Valid.isTrue;
Valid['false'] = Valid.isFalse;
Valid['in'] = Valid.oneOf;
// composites that take arguments
Valid.todo = function(name) { return this.fail((name ? name : "validation") + " is still todo"); };
-Valid.notEqual = function(arg) { return this.not(this.equal(arg), "is equal to " + this.Escape(arg)); };
-Valid.nomatch = function(pat,mods) { var match = this.match(pat,mods); return this.not(match, "matches " + match._queue[1].data); };
+Valid.notEqual = function(arg) { return this.not(this.equal(arg), "can't equal " + this.Escape(arg)); };
+Valid.nomatch = function(pat,mods) { var match = this.match(pat,mods); return this.not(match, "can't match " + match._queue[1].data); };
// comparisons
Valid.eq = Valid.equal;
Valid.ne = Valid.notEqual;
-Valid.lt = Valid.SimpleValidation(function lt(val,than) { if(val >= than) return "is not less than " + Valid.Escape(than); });
-Valid.le = Valid.SimpleValidation(function le(val,than) { if(val > than) return "is not less than or equal to " + Valid.Escape(than); });
-Valid.gt = Valid.SimpleValidation(function gt(val,than) { if(val <= than) return "is not greater than " + Valid.Escape(than); });
-Valid.ge = Valid.SimpleValidation(function ge(val,than) { if(val < than) return "is not greater than or equal to " + Valid.Escape(than); });
+Valid.lt = Valid.SimpleValidation(function lt(val,than) { if(val >= than) return "must be less than " + Valid.Escape(than); });
+Valid.le = Valid.SimpleValidation(function le(val,than) { if(val > than) return "must be less than or equal to " + Valid.Escape(than); });
+Valid.gt = Valid.SimpleValidation(function gt(val,than) { if(val <= than) return "must be greater than " + Valid.Escape(than); });
+Valid.ge = Valid.SimpleValidation(function ge(val,than) { if(val < than) return "must be greater than or equal to " + Valid.Escape(than); });
Valid.min = Valid.ge;
Valid.max = Valid.le;
@@ -265,14 +266,14 @@ Valid.JsonObject = function(path, value, schema) {
if(key in value) {
this.JsonField(path.concat(key), value[key], schema[key]);
} else {
- this.JsonError(path, value, "is missing " + key);
+ this.JsonError(path, value, "must include " + key);
}
if(this._errorCount > this._maxErrors) break;
}
for(key in value) {
if(!value.hasOwnProperty(key)) continue;
- if(!(key in schema)) this.JsonError(path, value, "shouldn't have " + key);
+ if(!(key in schema)) this.JsonError(path, value, "can't include " + key);
if(this._errorCount > this._maxErrors) break;
}
};
@@ -284,7 +285,7 @@ Valid.JsonField = function(path, value, schema) {
case 'number':
case 'boolean':
case 'undefined':
- if(value !== schema) this.JsonError(path, value, "does not equal " + this.Escape(schema));
+ if(value !== schema) this.JsonError(path, value, "must equal " + this.Escape(schema));
break;
case 'function':
@@ -297,24 +298,24 @@ Valid.JsonField = function(path, value, schema) {
}
break;
- case 'null':
+ case 'null': // usually typeof null === 'object'
case 'object':
if(schema === null) {
- if(value !== null) this.JsonError(path, value, "is not null");
+ if(value !== null) this.JsonError(path, value, "must be null");
} else if(schema._queue && typeof schema.GetChain === 'function') { // try to detect a Valid chain
var vresult = schema.check(value);
if(vresult) this.JsonError(path, value, vresult);
} else if(value === null) {
- this.JsonError(path, value, "is null");
+ this.JsonError(path, value, "can't be null");
} else if(schema instanceof Array) {
if(value instanceof Array) {
- if(value.length !== schema.length) this.JsonError(path, value, " has " + value.length + " items, not " + schema.length);
+ if(value.length !== schema.length) this.JsonError(path, value, " must have " + value.length + " items, not " + schema.length);
for(var i=0; i < schema.length; i++) {
this.JsonField(path.concat(i), value[i], schema[i]);
if(this._errorCount > this._maxErrors) break;
}
} else {
- this.JsonError(path, value, "is not an Array");
+ this.JsonError(path, value, "must be an array");
}
} else {
this.JsonObject(path, value, schema);
Oops, something went wrong.

0 comments on commit 3de5628

Please sign in to comment.