Comparing version 0.3.0 to 0.4.0
64
index.js
@@ -19,3 +19,2 @@ var _ = require('underscore'); | ||
return this; | ||
@@ -28,20 +27,38 @@ } | ||
// Enforce that the data matches the specified ruleset | ||
// If it doesn't, throw an error. | ||
// If the callback is specified, instead of throwing, use first arg | ||
Anchor.prototype.to = function (ruleset, cb) { | ||
Anchor.prototype.to = function (ruleset) { | ||
// If callback is specififed, trigger it at the end | ||
// also, handle error instead of throwing it | ||
if (cb) this.cb = cb; | ||
var errors = []; | ||
// Use deep match to descend into the collection and verify each item and/or key | ||
// Stop at default maxDepth (50) to prevent infinite loops in self-associations | ||
Anchor.match(this.data, ruleset, this); | ||
// If ruleset doesn't contain any explicit rule keys, | ||
// assume that this is a type | ||
// If a callback was specified, trigger it | ||
// If an error object was stowed away in the ctx, pass it along | ||
// (otherwise we never should have made it this far, the error should have been thrown) | ||
cb && cb(this.error); | ||
// Look for explicit rules | ||
for (var rule in ruleset) { | ||
if (rule === 'type') { | ||
// Use deep match to descend into the collection and verify each item and/or key | ||
// Stop at default maxDepth (50) to prevent infinite loops in self-associations | ||
errors = errors.concat(Anchor.match.type(this.data, ruleset['type'])); | ||
} | ||
// Validate a non-type rule | ||
else { | ||
errors = errors.concat(Anchor.match.rule(this.data, rule, ruleset[rule])); | ||
} | ||
} | ||
// If errors exist, return the list of them | ||
if (errors.length) { | ||
return errors; | ||
} | ||
// No errors, so return false | ||
else return false; | ||
}; | ||
Anchor.prototype.hasErrors = Anchor.prototype.to; | ||
// Coerce the data to the specified ruleset if possible | ||
@@ -54,6 +71,3 @@ // otherwise throw an error | ||
// Coerce the data to the specified ruleset no matter what | ||
Anchor.prototype.hurl = function (ruleset, cb) { | ||
// If callback is specififed, trigger it at the end | ||
// also, handle error instead of throwing it | ||
if (cb) this.cb = cb; | ||
Anchor.prototype.hurl = function (ruleset) { | ||
@@ -71,9 +85,4 @@ // Iterate trough given data attributes | ||
// If a callback has been passed pass the error there | ||
if(typeof cb === 'function') { | ||
return cb(err); | ||
} | ||
// Or just throw it | ||
else throw err; | ||
// just throw it | ||
throw err; | ||
} | ||
@@ -86,7 +95,2 @@ } | ||
Anchor.match(this.data, ruleset, this); | ||
// If a callback was specified, trigger it | ||
// If an error object was stowed away in the ctx, pass it along | ||
// (otherwise we never should have made it this far, the error should have been thrown) | ||
cb && cb(this.error); | ||
}; | ||
@@ -93,0 +97,0 @@ |
163
lib/match.js
var _ = require('underscore'); | ||
var rules = require('./rules'); | ||
module.exports = deepMatch; | ||
module.exports = { | ||
type: deepMatchType, | ||
rule: match | ||
}; | ||
@@ -10,2 +13,64 @@ // Max depth value | ||
/** | ||
* Match a miscellaneous rule | ||
* Returns an empty list on success, | ||
* or a list of errors if things go wrong | ||
*/ | ||
function match ( data, ruleName, args ) { | ||
var errors = []; | ||
// Lowercase ruleName | ||
ruleName = ruleName.toLowerCase(); | ||
// Ensure args is a list, then prepend it with data | ||
if ( !_.isArray(args) ) { | ||
args = [args]; | ||
} | ||
args.unshift(data); | ||
// Lookup rule and determine outcome | ||
var outcome; | ||
var rule = rules[ruleName]; | ||
if (!rule) { | ||
throw new Error ('Unknown rule: ' + ruleName); | ||
} | ||
try { | ||
outcome = rule.apply(rule, args); | ||
} | ||
catch (e) { | ||
outcome = false; | ||
} | ||
// If outcome is false, an error occurred | ||
if (!outcome) return failure(data, ruleName, args); | ||
else return []; | ||
// On failure-- stop and get out. | ||
// If a cb was specified, call it with a first-arity error object. | ||
// Otherwise, return a list of error objets. | ||
function failure() { | ||
// Construct error message | ||
var errMsg = ''; | ||
var argStr = _.reduce(args, function (memo, arg, i) { | ||
// Prepend w/ comma if this isn't the first argument | ||
return ( (i > 0) ? ',' : '' ) + arg; | ||
}, ''); | ||
errMsg += 'Validation error: "'+data+'" '; | ||
errMsg += 'Rule "' + ruleName + '(' + argStr + ')" failed.'; | ||
// Construct error object | ||
return [{ | ||
data: data, | ||
message: errMsg, | ||
rule: ruleName, | ||
args: args | ||
}]; | ||
} | ||
} | ||
/** | ||
* | ||
@@ -15,10 +80,12 @@ * | ||
* Match a complex collection or model against a schema | ||
* Return a list of errors (or an empty list if no errors were found) | ||
* | ||
* | ||
*/ | ||
function deepMatch (data, ruleset, ctx, depth, keyName) { | ||
function deepMatchType (data, ruleset, depth, keyName) { | ||
var errors = []; | ||
// If ruleset is not an object or array, use the provided function to validate | ||
if (!_.isObject(ruleset)) { | ||
return match(data,ruleset,ctx, keyName); | ||
return matchType(data,ruleset, keyName); | ||
} | ||
@@ -30,3 +97,3 @@ | ||
if (depth > maxDepth) { | ||
throw new Error ('Depth of object being parsed exceeds maxDepth (). Maybe it links to itself?'); | ||
throw new Error ('Depth of object being parsed exceeds maxDepth (). Maybe it\'s recursively referencing itself?'); | ||
} | ||
@@ -41,3 +108,7 @@ | ||
// Handle plurals (arrays with a schema rule) | ||
return _.all(data, matchArray); | ||
// Match each object in data array against ruleset until error is detected | ||
_.each(data, function (model) { | ||
errors = errors.concat(deepMatchType(model, ruleset[0], depth+1, keyName)); | ||
}); | ||
return errors; | ||
} | ||
@@ -49,23 +120,17 @@ | ||
// Don't treat empty object as a ruleset | ||
// Instead, treat it as 'object' | ||
if (_.keys(ruleset).length === 0) { | ||
return match(data, ruleset, ctx, keyName); | ||
return matchType(data, ruleset, keyName); | ||
} | ||
else return _.all(ruleset,matchDict); | ||
else { | ||
// Iterate through rules in dictionary until error is detected | ||
_.each(ruleset, function (subRule, key) { | ||
errors = errors.concat(deepMatchType(data[key], ruleset[key], depth+1, key)); | ||
}); | ||
return errors; | ||
} | ||
} | ||
// Leaf rules land here and execute the iterator | ||
else return match(data, ruleset, ctx, keyName); | ||
// Iterate through rules in dictionary until error is detected | ||
function matchDict(subRule,key) { | ||
if (ctx && ctx.error) return false; | ||
else return deepMatch(data[key], ruleset[key], ctx, depth+1, key); | ||
} | ||
// Match each object in array against ruleset until error is detected | ||
function matchArray(model) { | ||
if (ctx && ctx.error) return false; | ||
else return deepMatch(model, ruleset[0], ctx, depth+1, keyName); | ||
} | ||
else return matchType(data, ruleset, keyName); | ||
} | ||
@@ -88,6 +153,6 @@ | ||
* ruleName :: (STRING) | ||
* returns a list of errors, or an empty list in the absense of them | ||
*/ | ||
function match (datum, ruleName, ctx, keyName) { | ||
function matchType (datum, ruleName, keyName) { | ||
try { | ||
@@ -104,3 +169,3 @@ var outcome, rule; | ||
// {} specified as data type checks for any object | ||
rule = _.isObject; | ||
rule = _.isObject; | ||
} | ||
@@ -110,4 +175,8 @@ else if (_.isRegExp(ruleName)) { | ||
rule = function (x) { | ||
if (!_.isString(x)) return false; | ||
x.match(ruleName); | ||
// If argument to regex rule is not a string, | ||
// fail on 'string' validation | ||
if (!_.isString(x)) { | ||
rule = rules['string']; | ||
} | ||
else x.match(ruleName); | ||
}; | ||
@@ -125,8 +194,12 @@ } | ||
// False outcome is a failure | ||
if (!outcome) return failure(datum,ruleName, keyName); | ||
else return outcome; | ||
// If validation failed, return an error | ||
if (!outcome) { | ||
return failure(datum,ruleName, keyName); | ||
} | ||
// If everything is ok, return an empty list | ||
else return []; | ||
} | ||
catch (e) { | ||
failure(datum, ruleName, keyName); | ||
return failure(datum, ruleName, keyName); | ||
} | ||
@@ -136,21 +209,19 @@ | ||
// If a cb was specified, call it with a first-arity error object. | ||
// Otherwise, throw an error. | ||
// Otherwise, return a list of error objets. | ||
function failure(datum, ruleName, keyName) { | ||
// Construct error | ||
var err = ''; | ||
err += 'Validation error: "'+datum+'" '; | ||
err += keyName ? '('+keyName+') ' : ''; | ||
err += 'is not of type "'+ruleName+'"'; | ||
err = new Error(err); | ||
// Construct error message | ||
var errMsg = ''; | ||
errMsg += 'Validation error: "'+datum+'" '; | ||
errMsg += keyName ? '('+keyName+') ' : ''; | ||
errMsg += 'is not of type "'+ruleName+'"'; | ||
// Handle the error in callback instead of throwing it | ||
if (ctx.cb) { | ||
ctx.error = err; | ||
return false; | ||
} | ||
// Or throw error if there was no callback | ||
else throw err; | ||
// Construct error object | ||
return [{ | ||
property: keyName, | ||
data: datum, | ||
message: errMsg, | ||
rule: ruleName | ||
}]; | ||
} | ||
} |
@@ -6,3 +6,7 @@ var _ = require('underscore'); | ||
// Type rules | ||
'empty' : function (x) { return x === ''; }, | ||
'required' : function (x) { return check(x).notEmpty(); }, | ||
'notEmpty' : function (x) { return check(x).notEmpty(); }, | ||
'undefined' : _.isUndefined, | ||
@@ -18,4 +22,8 @@ | ||
'ip' : function (x){ return check(x).isIP(); }, | ||
'ipv4' : function (x){ return check(x).isIPv4(); }, | ||
'ipv6' : function (x){ return check(x).isIPv6(); }, | ||
'creditcard': function (x){ return check(x).isCreditCard();}, | ||
'uuid' : function (x, version){ return check(x).isUUID(version);}, | ||
'uuidv3' : function (x){ return check(x).isUUIDv3();}, | ||
'uuidv4' : function (x){ return check(x).isUUIDv4();}, | ||
@@ -33,2 +41,3 @@ 'int' : function (x) { return check(x).isInt(); }, | ||
'null' : _.isNull, | ||
'notNull' : function (x) { return check(x).notNull(); }, | ||
@@ -39,6 +48,30 @@ 'boolean' : _.isBoolean, | ||
'date' : _.isDate, | ||
'date' : function (x) { return check(x).isDate(); }, | ||
'hexadecimal': function (x) { return check(x).hexadecimal(); }, | ||
'hexColor': function (x) { return check(x).hexColor(); }, | ||
'lowercase': function (x) { return check(x).lowercase(); }, | ||
'uppercase': function (x) { return check(x).uppercase(); }, | ||
// Miscellaneous rules | ||
'after' : function (x,date) { return check(x).isAfter(date); }, | ||
'before' : function (x,date) { return check(x).isBefore(date); } | ||
'before' : function (x,date) { return check(x).isBefore(date); }, | ||
}; | ||
'is' : function (x, regex) { return check(x).is(regex); }, | ||
'regex' : function (x, regex) { return check(x).regex(regex); }, | ||
'not' : function (x, regex) { return check(x).not(regex); }, | ||
'notRegex' : function (x, regex) { return check(x).notRegex(regex); }, | ||
'equals' : function (x, equals) { return check(x).equals(equals); }, | ||
'contains' : function (x, str) { return check(x).contains(str); }, | ||
'notContains': function (x, str) { return check(x).notContains(str); }, | ||
'len' : function (x, min, max) { return check(x).len(min, max); }, | ||
'in' : function (x, arrayOrString) { return check(x).isIn(arrayOrString); }, | ||
'notIn' : function (x, arrayOrString) { return check(x).notIn(arrayOrString); }, | ||
'max' : function (x, val) { return check(x).max(val); }, | ||
'min' : function (x, val) { return check(x).min(val); }, | ||
'minLength' : function (x, min) { return check(x).len(min); }, | ||
'maxLength' : function (x, max) { return check(x).len(0, max); } | ||
}; |
{ | ||
"name": "anchor", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Recursive validation library with support for objects and lists", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
anchor | ||
====== | ||
> IMPORTANT: Anchor is under **active development**! Please follow/star and keep up to date as the project develops. | ||
> If you're interested in contributing, drop me a note at @mikermcneil. I welcome your feedback, ideas, and pull requests :) | ||
Anchor is a javascript library that lets you define strict types. | ||
<!-- err todo: It also helps you validate and normalize the usage of command line scripts and even individual functions. --> | ||
Anchor is a javascript library that lets you define strict types. It also helps you validate and normalize the usage of command line scripts and even individual functions. | ||
This makes it really useful for things like: | ||
@@ -34,2 +31,4 @@ + Form validation on the client or server | ||
<!-- | ||
## Basic Usage | ||
@@ -84,2 +83,4 @@ ```javascript | ||
--> | ||
<!-- | ||
@@ -86,0 +87,0 @@ ## Custom rules |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32104
676
455
1