Permalink
Browse files

combine valid-engine and validjson modules

  • Loading branch information...
1 parent cf0dd09 commit 0e9844767770b9621a204aa0c2983f1820c57a6e @bronson committed Sep 22, 2011
Showing with 189 additions and 189 deletions.
  1. +4 −7 README.md
  2. +1 −1 console.js
  3. +0 −80 lib/valid-engine.js
  4. +183 −2 lib/valid.js
  5. +0 −98 lib/validjson.js
  6. +1 −1 test/validjson.test.js
View
@@ -18,24 +18,21 @@ A lightweight, chaining validation library.
Valid.optional().string() // success is null, undefined, or a string
Valid.array(Valid.integer).verify([1,2,3]); // each item in the array must be an integer
- // validate JSON:
- var Valid = require('validjson');
-
var Schema = {
Name: Valid.notBlank(),
Numbers: Valid.array(Valid.integer()).len(2,5),
State: /^[A-Z][A-Z]$/,
Country: "US"
};
- var Data = {
+ var data = {
Name: "Jed",
Numbers: [1, 9, 25],
State: "CA",
Country: "US"
}
- Valid.json(Schema).verify(Data);
+ Valid.json(Schema).verify(data);
```
# Gruntles
@@ -47,7 +44,6 @@ This library is scary new.
- todo: isEmail() isIP() isUrl() isUUID()
- Valid is not a great name. it's not even a noun.
- noexisty is a stupid name
-- should valid and validjson really be in separate modules?
- Allow putting value first? i.e. Valid(9).lt(12).gt(10) throws "9 is not greater than 10"
- Allow returning multiple errors for a single field, like Rails validations?
@@ -80,7 +76,8 @@ 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".
There is one exception: JSON validations. Because they can return multiple
-errors, they return an object instead of a string:
+errors, they return an object instead of a string. The `value` field contains
+the value that failed to validate.
```javascript
{
View
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// fires up a console with Valid loaded
-Valid = require('./lib/validjson');
+Valid = require('./lib/valid');
require('repl').start();
View
@@ -1,80 +0,0 @@
-// valid-engine.js
-// This file defines the core objects that make Valid run.
-// Your code should require valid.js or validjson.js.
-
-var Valid = function Valid() { };
-module.exports = Valid;
-
-
-Valid.GetChain = function GetChain() {
- if(this === Valid) {
- // we're the first item in a chain so create a Chain object
- var chain = function Chain() {};
- chain.prototype = this;
- return new chain();
- }
- return this;
-};
-
-// Adds the given test to the current Chain.
-// If data is supplied, it's added to the passed-in test to help introspect when debugging.
-Valid.AddTest = function AddTest(test, data) {
- var self = this.GetChain();
- if(self._queue === undefined) self._queue = [];
- if(data) test.data = data;
- self._queue.push(test);
- return self;
-};
-
-// Supply a function that that returns undefined on success or an error message on failure, produces a full, chainable test.
-// The first arg passed to your your function is the value to test, the rest are the args passed when adding the test.
-// i.e. Valid.t = SimpleTest(fn(){...}); Valid.t(4,2).check(9) would call your function with arguments 9, 4, 2.
-Valid.SimpleTest = function SimpleTest(fn) {
- return function() {
- var args = Array.prototype.slice.call(arguments, 0);
- return this.AddTest( function SimpleTest(value) {
- return fn.apply(this, [value].concat(args));
- }, args);
- };
-};
-
-// Run all the tests in the given queue
-Valid.ValidateQueue = function ValidateQueue(queue, value) {
- if(!queue || queue.length < 1) return "no tests!";
- for(var i=0; i<queue.length; i++) {
- var error = queue[i].call(this, value);
- if(error === Valid) return; // indicates early success, used by optional()
- if(error) return error;
- }
-};
-
-Valid.Escape = function Escape(value) {
- // todo: escape \n, \t, \\, \' and \" in the printed strings
- if(typeof value === 'string') return "'" + value.substring(0,20) + "'";
- return value;
-};
-
-
-// Allows you to reuse a chain as as a chainable test:
-// Valid.isFour = Valid.equal(4).define(); // define the isFour test
-// Valid.integer().isFour().test(4); // success!
-// If you get this error then you forgot to call define() on your chain:
-// Property 'myfunc' of object function Valid() { } is not a function
-// It's really shameful that this function needs to exist.
-// In an ideal world you could just do this: Valid.null() = Valid.equal(null);
-// In our world, that only works if you don't call it: Valid.null.verify(1); Ugh.
-// Since Valid.equal(null) returns the chain object, if you call null:
-// Valid.null().verify(1) JS complains "Property null is not a function"
-// For this to work, JS needs to a callable object with a prototype chain.
-// And, without using nonstandard __proto__, I don't think that's possible...?
-Valid.define = function define() {
- var queue = this._queue;
- return function() {
- var self = this.GetChain();
- for(var i=0; i<queue.length; i++) {
- self.AddTest(queue[i]);
- }
- return self;
- };
-};
-
View
@@ -1,10 +1,96 @@
// valid.js
-// This file contains the default Valid tests but not the JSON schema verifier.
+//
+// todo: change error messages to "can't be blank", "must equal blah", etc.
+// todo: pass a json schema to array()? factor into RunSubtest & have everything call this.
+// todo: dates
+// todo: npm publish
+// todo: test coverage?
+// todo: value first?
-var Valid = require('./valid-engine');
+var Valid = function Valid() { };
module.exports = Valid;
+// Internals
+// ---------
+
+Valid.GetChain = function GetChain() {
+ if(this === Valid) {
+ // we're the first item in a chain so create a Chain object
+ var chain = function Chain() {};
+ chain.prototype = this;
+ return new chain();
+ }
+ return this;
+};
+
+// Adds the given test to the current Chain.
+// If data is supplied, it's added to the passed-in test to help introspect when debugging.
+Valid.AddTest = function AddTest(test, data) {
+ var self = this.GetChain();
+ if(self._queue === undefined) self._queue = [];
+ if(data) test.data = data;
+ self._queue.push(test);
+ return self;
+};
+
+// Supply a function that that returns undefined on success or an error message on failure, produces a full, chainable test.
+// The first arg passed to your your function is the value to test, the rest are the args passed when adding the test.
+// i.e. Valid.t = SimpleTest(fn(){...}); Valid.t(4,2).check(9) would call your function with arguments 9, 4, 2.
+Valid.SimpleTest = function SimpleTest(fn) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ return this.AddTest( function SimpleTest(value) {
+ return fn.apply(this, [value].concat(args));
+ }, args);
+ };
+};
+
+// Run all the tests in the given queue
+Valid.ValidateQueue = function ValidateQueue(queue, value) {
+ if(!queue || queue.length < 1) return "no tests!";
+ for(var i=0; i<queue.length; i++) {
+ var error = queue[i].call(this, value);
+ if(error === Valid) return; // indicates early success, used by optional()
+ if(error) return error;
+ }
+};
+
+Valid.Escape = function Escape(value) {
+ // todo: escape \n, \t, \\, \' and \" in the printed strings
+ if(typeof value === 'string') return "'" + value.substring(0,20) + "'";
+ return value;
+};
+
+
+// Allows you to reuse a chain as as a chainable test:
+// Valid.isFour = Valid.equal(4).define(); // define the isFour test
+// Valid.integer().isFour().test(4); // success!
+// If you get this error then you forgot to call define() on your chain:
+// Property 'myfunc' of object function Valid() { } is not a function
+// It's really shameful that this function needs to exist.
+// In an ideal world you could just do this: Valid.null() = Valid.equal(null);
+// In our world, that only works if you don't call it: Valid.null.verify(1); Ugh.
+// Since Valid.equal(null) returns the chain object, if you call null:
+// Valid.null().verify(1) JS complains "Property null is not a function"
+// For this to work, JS needs to a callable object with a prototype chain.
+// And, without using nonstandard __proto__, I don't think that's possible...?
+Valid.define = function define() {
+ var queue = this._queue;
+ return function() {
+ var self = this.GetChain();
+ for(var i=0; i<queue.length; i++) {
+ self.AddTest(queue[i]);
+ }
+ return self;
+ };
+};
+
+
+
+// User-facing API
+// ---------------
+
// results
Valid.test = function test(value) {
@@ -159,3 +245,98 @@ Valid.ge = Valid.SimpleTest(function ge(val,than) { if(val < than) return "is
Valid.min = Valid.ge;
Valid.max = Valid.le;
+
+
+// JSON Schema
+
+Valid.JsonError = function(path, value, message) {
+ var str = '';
+ for(var i=0; i<path.length; i++) {
+ str += Valid.integer().check(path[i]) ? '['+path[i]+']' : (str === '' ? '' : '.') + path[i];
+ }
+ if(str === '') str = '.';
+
+ if(this._errors[str]) return; // ignore duplicate errors
+ this._errors[str] = {value: value, message: message};
+ this._errorCount += 1;
+};
+
+
+Valid.JsonObject = function(path, value, schema) {
+ for(var key in schema) {
+ if(!schema.hasOwnProperty(key)) continue;
+ if(key in value) {
+ this.JsonField(path.concat(key), value[key], schema[key]);
+ } else {
+ this.JsonError(path, value, "is missing " + 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(this._errorCount > this._maxErrors) break;
+ }
+};
+
+
+Valid.JsonField = function(path, value, schema) {
+ switch(typeof schema) {
+ case 'string':
+ case 'number':
+ case 'boolean':
+ case 'undefined':
+ if(value !== schema) this.JsonError(path, value, "does not equal " + this.Escape(schema));
+ break;
+
+ case 'function':
+ if(schema instanceof RegExp) {
+ var reresult = Valid.match(schema).test(value);
+ if(reresult) this.JsonError(path, value, reresult);
+ } else {
+ var fresult = schema.call(this, value);
+ if(fresult) this.JsonError(path, value, fresult);
+ }
+ break;
+
+ case 'null':
+ case 'object':
+ if(schema === null) {
+ if(value !== null) this.JsonError(path, value, "is not null");
+ } else if(schema._queue && typeof schema.GetChain === 'function') { // try to detect a Valid chain
+ var vresult = schema.test(value);
+ if(vresult) this.JsonError(path, value, vresult);
+ } else if(value === null) {
+ this.JsonError(path, value, "is 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);
+ 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");
+ }
+ } else {
+ this.JsonObject(path, value, schema);
+ }
+ break;
+
+ default:
+ this.JsonError(path, value, "Error in template: what is " + (typeof schema) + "?");
+ }
+};
+
+
+Valid.json = function json(schema) {
+ return this.AddTest(function Json(value, maxErrors) {
+ this._errors = {};
+ this._errorCount = 0;
+ this._maxErrors = maxErrors || 20;
+ this.JsonField([], value, schema, {});
+ if(this._errorCount > 0) return this._errors;
+ }, schema);
+};
+
Oops, something went wrong.

0 comments on commit 0e98447

Please sign in to comment.