Skip to content

Commit

Permalink
make it an npm module
Browse files Browse the repository at this point in the history
  • Loading branch information
bronson committed Sep 20, 2011
1 parent 7372b45 commit 4971ed4
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 187 deletions.
20 changes: 20 additions & 0 deletions LICENSE
@@ -0,0 +1,20 @@
Copyright (c) 2011 Scott Bronson

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7 changes: 4 additions & 3 deletions README.md
Expand Up @@ -2,15 +2,16 @@

- Small, light, zero dependencies.
- Can be used in Node, browsers, and Mongo/Couch Map/Reduce functions.
- Offers an error object similar to ActiveRecord validations.
- Can be used declaratively as well as immediately.
- Can recursively test JSON data structures.
- Easy to extend with your own validations.
- Tests be used declaratively as well as immediately.
- Good test coverage.


### Declarative

var inRange = Valid.min(4).max(9)
inRange(12) // throws an error
inRange.verify(12) // throws an error
var stringInRange = inRange.typeof('string').errorHandler(Valid.TrueFalse)
if(stringInRange.validate("not in range")) { /\* not executed \*/ }

Expand Down
94 changes: 94 additions & 0 deletions lib/valid-engine.js
@@ -0,0 +1,94 @@
// valid-engine.js Scott Bronson 2011
// This file defines the core objects that make Valid run.
// See valid.js for the default set of tests.


// todo? is it possible to turn test objects into arrays?

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);
};
};

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) return error;
}
};


// core api
// TODO: test? check? verify? these names suck.

// returns null if valid, the error string if invalid
Valid.test = function test(value) {
var self = this.GetChain();
return self.GetChain().ValidateQueue(self._queue, value);
};

// returns true if valid, false if invalid
Valid.check = function check(value) {
return !this.test(value);
};

// raises an error if invalid
Valid.verify = function assert(value) {
var message = this.test(value);
if(message) throw value + " " + message;
};

// Allows you to reuse a chain as as a chainable test. If you get the error
// Property 'myfunc' of object function Valid() { } is not a function
// then you forgot to call define().
//
// 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;
};
};

89 changes: 89 additions & 0 deletions lib/valid.js
@@ -0,0 +1,89 @@
// valid.js Scott Bronson 2011
// This file defines the Valid object and some core validation tests.

// todo? is it possible to turn test objects into arrays?

var Valid = require('./valid-engine');
module.exports = Valid;


// core tests

Valid.nop = Valid.SimpleTest(function Nop(val) { });
Valid.fail = Valid.SimpleTest(function Fail(val,msg) { return msg || "failed"; });
Valid.equal = Valid.SimpleTest(function Equal(val,want) { if(val !== want) return "is not equal to "+want; });
Valid.mod = Valid.SimpleTest(function mod(val,by,rem) { if(val%by !== (rem||0)) return "mod "+by+" is "+(val%by)+" not "+rem; });

Valid.type = Valid.SimpleTest(function Type(val,type) {
if(typeof type !== 'string') return "type requires a string argument, not "+(typeof type);
if(typeof val !== type) return "is of type " + (typeof val) + " not " + type;
});

Valid.messageFor = Valid.SimpleTest(function Msg(value, test, message) {
var error = this.ValidateQueue(test._queue, value);
if(error) return message;
});

Valid.not = Valid.SimpleTest(function Not(value, test, message) {
var error = this.ValidateQueue(test._queue, value);
if(!error) return message || "test succeeded";
});

// seems somewhat useless since V.a().b() is the same as V.and(V.a(),V.b())
Valid.and = function and() {
var chains = arguments;
return this.AddTest( function And(value) {
for(var i=0; i<chains.length; i++) {
var error = this.ValidateQueue(chains[i]._queue, value);
if(error) return error;
}
}, chains);
};

Valid.or = function or() {
var chains = arguments;
return this.AddTest(function Or(value) {
var errors = [];
for(var i=0; i<chains.length; i++) {
var error = this.ValidateQueue(chains[i]._queue, value);
if(!error) return; // short circuit
errors.push(error);
}
return errors.length > 0 ? errors.join(" and ") : undefined;
}, chains);
};

Valid.match = function match(pattern, modifiers) {
if(typeof pattern !== 'function') pattern = new RegExp(pattern, modifiers);
return this.string().AddTest( function Match(value) {
if(!value.match(pattern)) return "does not match " + pattern;
});
};


// composite tests

Valid.todo = function(name) {
return this.fail((name ? name : "this") + " is still todo");
};

Valid.undefined = Valid.equal(undefined).define();
Valid.defined = Valid.not(Valid.undefined(), "is undefined").define();
Valid.null = Valid.equal(null).define();
Valid.notNull = Valid.not(Valid.null(), "is null").define();
Valid.exists = Valid.messageFor(Valid.defined().notNull(), "does not exist").define();
Valid.noexisty = Valid.not(Valid.exists(), "exists").define();
Valid.boolean = Valid.type('boolean').define();
Valid.true = Valid.equal(true).define();
Valid.false = Valid.equal(false).define();
Valid.number = Valid.type('number').define();
Valid.integer = Valid.number().messageFor(Valid.mod(1), "is not an integer").define();
Valid.string = Valid.type('string').define();
Valid.blank = Valid.messageFor(Valid.or(Valid.noexisty(),Valid.match(/^\s*$/)), "is not blank").define();
Valid.notBlank = Valid.not(Valid.blank(), "is blank").define();
Valid.function = Valid.type('function').define();
Valid.object = Valid.type('object').define();

Valid.optional = function(test) { return Valid.or(Valid.messageFor(Valid.undefined(),"is optional"), test); };
Valid.notEqual = function(arg) { return Valid.not(Valid.equal(arg), "is equal to " + arg); };

14 changes: 14 additions & 0 deletions package.json
@@ -0,0 +1,14 @@
{ "name" : "valid"
, "description" : "Library to quickly validate JSON."
, "version" : "0.1.0"
, "keywords" : [ "validation", "schema", "test", "json" ]
, "author" : { "name" : "Scott Bronson", "email" : "brons_valid@rinspin.com", "url" : "https://github.com/bronson" }
, "contributors" : [ { "name": "Aaron Blohowiak", "url" : "https://github.com/aaronblohowiak" } ]
, "licenses" : [ { "type" : "MIT", "url" : "https://github.com/bronson/valid/blob/master/LICENSE" } ]
, "homepage" : "https://github.com/bronson/valid"
, "repository" : { "type" : "git", "url" : "http://github.com/bronson/valid" }
, "bugs" : { "web" : "https://github.com/bronson/valid/issues" }
, "directories" : { "lib" : "./lib" }
, "main" : "./lib"
, "scripts" : { "test" : "node test/test-valid.js" }
}
22 changes: 11 additions & 11 deletions test-valid.js → test/test-valid.js
@@ -1,4 +1,4 @@
var Valid = require('./valid');
var Valid = require('../lib/valid');

// Like check() but throws if the result doesn't match the expectation
Valid.assert = function assert(value, expected) {
Expand Down Expand Up @@ -40,13 +40,13 @@ Valid.notEqual(null).assert(undefined);
Valid.notEqual(null).assert(null, "is equal to null");


Valid.typeOf('undefined').assert(undefined);
Valid.type('undefined').assert(undefined);
// typeof null returns 'object' on some JS implementations, use null()
Valid.typeOf('number').assert(123);
Valid.typeOf('string').assert('123');
Valid.typeOf('garbage').assert('123', "is of type string not garbage");
Valid.typeOf(undefined).assert(undefined, "typeOf requires a string argument, not undefined");
Valid.typeOf(123).assert(123, "typeOf requires a string argument, not number");
Valid.type('number').assert(123);
Valid.type('string').assert('123');
Valid.type('garbage').assert('123', "is of type string not garbage");
Valid.type(undefined).assert(undefined, "type requires a string argument, not undefined");
Valid.type(123).assert(123, "type requires a string argument, not number");

// booleans
Valid.boolean().assert(true);
Expand Down Expand Up @@ -90,9 +90,9 @@ Valid.equal(2).assert( Valid.match(/^abc$/)._queue.length ); // closure leak mea
Valid.and().assert(null); // passing 0 tests succeeds unconditionally
Valid.and( Valid.null() ).assert(null); // passing 1 arg success
Valid.and( Valid.null() ).assert(undefined, "is not equal to null"); // passing 1 arg failure
Valid.and( Valid.typeOf('string'), Valid.match(/c$/), Valid.match(/^a/) ).assert('abc');
Valid.and( Valid.typeOf('string'), Valid.match(/^bbc$/) ).assert('abc', "does not match /^bbc$/");
Valid.and( Valid.typeOf('number'), Valid.match(/^abc$/) ).assert('abc', "is of type string not number");
Valid.and( Valid.type('string'), Valid.match(/c$/), Valid.match(/^a/) ).assert('abc');
Valid.and( Valid.type('string'), Valid.match(/^bbc$/) ).assert('abc', "does not match /^bbc$/");
Valid.and( Valid.type('number'), Valid.match(/^abc$/) ).assert('abc', "is of type string not number");

Valid.or().assert(undefined); // passing 0 tests succeeds unconditionally
Valid.or( Valid.null() ).assert(null); // passing 1 arg success
Expand All @@ -101,7 +101,7 @@ Valid.or( Valid.null(), Valid.undefined() ).assert(undefined);
Valid.or( Valid.null(), Valid.undefined() ).assert(null);
Valid.or( Valid.null(), Valid.number(), Valid.string() ).assert('mosdef');
Valid.or( Valid.undefined(), Valid.match(/^abc$/), Valid.match(/def$/) ).assert('mosdef');
Valid.or( Valid.typeOf('number'), Valid.match(/^bbc$/) ).assert('abc', "is of type string not number and does not match /^bbc$/");
Valid.or( Valid.type('number'), Valid.match(/^bbc$/) ).assert('abc', "is of type string not number and does not match /^bbc$/");

var nullOrString = Valid.or(Valid.null(), Valid.string());
nullOrString.assert(null);
Expand Down

0 comments on commit 4971ed4

Please sign in to comment.