Comparing version 0.2.0-pre to 0.5.0
928
checkit.js
@@ -1,546 +0,550 @@ | ||
// Checkit.js 0.2.0 | ||
// Checkit.js 0.5.0 | ||
// http://tgriesser.com/checkit | ||
// (c) 2013 Tim Griesser | ||
// (c) 2013-2015 Tim Griesser | ||
// Checkit may be freely distributed under the MIT license. | ||
(function(factory) { | ||
var _ = require('lodash') | ||
var createError = require('create-error') | ||
var Promise = require('when/es6-shim/Promise') | ||
var inherits = require('inherits') | ||
"use strict"; | ||
// The top level `Checkit` constructor, accepting the | ||
// `validations` to be run and any additional `options`. | ||
var Checkit = function(validations, options) { | ||
if (!(this instanceof Checkit)) { | ||
return new Checkit(validations, options); | ||
} | ||
this.conditional = []; | ||
options = _.clone(options || {}); | ||
this.labels = options.labels || {}; | ||
this.messages = options.messages || {}; | ||
this.language = Checkit.i18n[options.language || Checkit.language]; | ||
this.labelTransform = options.labelTransform || Checkit.labelTransform | ||
this.validations = prepValidations(validations || {}); | ||
this.validator = new Validator(this) | ||
}; | ||
factory(function(_, createError, Promise) { | ||
Checkit.VERSION = '0.5.0'; | ||
// The top level `Checkit` constructor, accepting the | ||
// `validations` to be run and any (optional) `options`. | ||
var Checkit = function(validations, options) { | ||
if (!(this instanceof Checkit)) { | ||
return new Checkit(validations, options); | ||
} | ||
options = _.clone(options || {}); | ||
this.conditional = []; | ||
this.language = options.language; | ||
this.labels = options.labels || {}; | ||
this.messages = options.messages || {}; | ||
this.validations = prepValidations(validations || {}); | ||
}; | ||
// Possibly run a validations on this object, depending on the | ||
// result of the `conditional` handler. | ||
Checkit.prototype.maybe = function(validations, conditional) { | ||
this.conditional.push([prepValidations(validations), conditional]); | ||
return this; | ||
} | ||
Checkit.VERSION = '0.2.0'; | ||
// Asynchronously runs a validation block, returning a promise | ||
// which resolves with the validated object items, or is rejected | ||
// with a `Checkit.Error` | ||
Checkit.prototype.run = | ||
Checkit.prototype.validate = function(target, context) { | ||
return new Runner(this).run(target, context); | ||
} | ||
Checkit.prototype = { | ||
// Synchronously runs a validation block, returning an object of all fields | ||
// validated, or throwing a `Checkit.Error` object. | ||
Checkit.prototype.runSync = | ||
Checkit.prototype.validateSync = function(target, context) { | ||
try { | ||
return [null, new SyncRunner(this).run(target, context)] | ||
} catch (err) { | ||
return [err, null] | ||
} | ||
} | ||
// Possibly run a validations on this object, depending on the | ||
// result of the `conditional` handler. | ||
maybe: function(validations, conditional) { | ||
this.conditional.push([prepValidations(validations), conditional]); | ||
return this; | ||
}, | ||
Checkit.prototype.getMessage = function(item, key) { | ||
var language = this.language; | ||
var label = item.label || this.labels[key] || language.labels[key] || this.labelTransform(key); | ||
var message = item.message || this.messages[item.rule] || language.messages[item.rule] || language.messages.fallback; | ||
message = message.replace(labelRegex, label); | ||
for (var i = 0, l = item.params.length; i < l; i++) { | ||
message = message.replace(varRegex(i+1), item.params[i]); | ||
} | ||
return message; | ||
} | ||
// Asynchronously runs a validation block, returning a promise | ||
// which resolves with the validated object items, or is rejected | ||
// with a `Checkit.Error` instance. | ||
run: function(target) { | ||
return new Checkit.Runner(this).run(target); | ||
} | ||
// Used to transform the label before using it, can be | ||
// set globally or in the `options` for the Checkit object. | ||
Checkit.labelTransform = function(label) { | ||
return label; | ||
} | ||
}; | ||
// Object containing languages for the validations... Feel free to | ||
// add anything to this object. | ||
Checkit.i18n = { | ||
en: require('./lang/en'), | ||
es: require('./lang/es') | ||
} | ||
// The default language for all validations, defaults to "en" which | ||
// is included with the library by default. To add additional languages, | ||
// add them to the `Checkit.i18n` object. | ||
Checkit.language = 'en'; | ||
// The default language for all validations, defaults to "en" which | ||
// is included with the library by default. To add additional languages, | ||
// add them to the `Checkit.i18n` object. | ||
Checkit.language = 'en'; | ||
// Runs validation on an individual rule & value, for convenience. | ||
// e.g. `Checkit.check('email', 'foo@domain', 'email').then(...` | ||
Checkit.check = function(key, value, rules) { | ||
var input = {}, validations = {}; | ||
input[key] = value; | ||
validations[key] = rules; | ||
// Runs validation on an individual rule & value, for convenience. | ||
// e.g. `Checkit.check('email', 'foo@domain', 'email').then(...` | ||
Checkit.check = function(key, value, rules, sync) { | ||
var input = {}, validations = {}; | ||
input[key] = value; | ||
validations[key] = rules; | ||
if (sync) { | ||
return checkSync(validations, input, key) | ||
} else { | ||
return new Checkit(validations).run(input).then(null, function(err) { | ||
if (err instanceof Checkit.Error) throw err.get(key); | ||
throw err; | ||
}); | ||
}; | ||
if (err instanceof CheckitError) throw err.get(key); | ||
throw err | ||
}) | ||
} | ||
} | ||
Checkit.checkSync = function(key, value, rules) { | ||
return Checkit.check(key, value, rules, true) | ||
} | ||
// The validator is the object which is dispatched with the `run` | ||
// call from the `checkit.run` method. | ||
var Runner = Checkit.Runner = function(base) { | ||
this.errors = {}; | ||
this.base = base; | ||
this.validations = _.clone(base.validations); | ||
this.conditional = base.conditional; | ||
this.language = Checkit.i18n[base.language || Checkit.language]; | ||
this.labelTransform = base.labelTransform || Checkit.labelTransform; | ||
}; | ||
// Synchronously check an individual field against a rule. | ||
function checkSync(validations, input, key) { | ||
var arr = new Checkit(validations).runSync(input); | ||
if (arr[0] === null) return arr; | ||
if (arr[0] instanceof CheckitError) { | ||
return [arr[0].get(key), null] | ||
} | ||
return arr; | ||
} | ||
Runner.prototype = { | ||
// The validator is the object which is dispatched with the `run` | ||
// call from the `checkit.run` method. | ||
function Runner(checkit) { | ||
this.errors = {}; | ||
this.checkit = checkit; | ||
this.conditional = checkit.conditional; | ||
} | ||
// Runs the validations on a specified "target". | ||
run: function(target) { | ||
target = this.target = _.clone(target || {}); | ||
var runner = this, validations = this.validations, | ||
errors = this.errors, | ||
pending = []; | ||
// Runs the validations on a specified "target". | ||
Runner.prototype.run = function(target, context) { | ||
var runner = this; | ||
this.target = target = _.clone(target || {}) | ||
this.context = context = _.clone(context || {}) | ||
for (var i = 0, l = this.conditional.length; i < l; i++) { | ||
pending.push(this.checkConditional(this.conditional[i])); | ||
} | ||
var validationHash = _.clone(this.checkit.validations); | ||
var errors = {} | ||
return Checkit.Promise.all(pending).then(function() { | ||
var pendingConditionals = _.map(this.conditional, function(conditional) { | ||
return Promise.resolve(checkConditional(runner, conditional)) | ||
.then(function(result) { | ||
if (result !== true) return; | ||
addVerifiedConditional(validationHash, conditional) | ||
}) | ||
.catch(function() {}) | ||
}) | ||
// Use a fresh "pending" stack. | ||
var pending = []; | ||
return Promise.all(pendingConditionals) | ||
.then(function() { | ||
var pending = [] | ||
_.each(validationHash, function(validations, key) { | ||
_.each(validations, function(validation) { | ||
pending.push(processItemAsync(runner, validation, key, context).catch(addError(errors, key, validation))) | ||
}) | ||
}) | ||
return Promise.all(pending) | ||
}) | ||
.then(function() { | ||
if (!_.isEmpty(errors)) { | ||
var err = new CheckitError(_.keys(errors).length + ' invalid values'); | ||
err.errors = errors; | ||
throw err; | ||
} | ||
return _.pick(target, _.keys(validationHash)); | ||
}); | ||
}; | ||
// Loop through each of the `validations`, running | ||
// each of the validations associated with the `key`. | ||
for (var key in validations) { | ||
var validation = validations[key]; | ||
for (var i = 0, l = validation.length; i < l; i++) { | ||
pending.push(runner.processItem.call(runner, validation[i], key)); | ||
} | ||
} | ||
// Only if we explicitly return `true` do we go ahead | ||
// and add the validations to the stack for a particular rule. | ||
function addVerifiedConditional(validations, conditional) { | ||
_.each(conditional[0], function(val, key) { | ||
validations[key] = validations[key] || []; | ||
validations[key] = validations[key].concat(val); | ||
}) | ||
} | ||
// Once all promise blocks have finished, we'll know whether | ||
// the promise should be rejected with an error or resolved with | ||
// the validated items. | ||
return Checkit.Promise.all(pending).then(function() { | ||
if (!_.isEmpty(errors)) { | ||
var err = new CheckitError(_.keys(errors).length + ' invalid values'); | ||
err.errors = errors; | ||
throw err; | ||
} | ||
return _.pick.apply(_, [target].concat(_.keys(validations))); | ||
}); | ||
// Runs through each of the `conditional` validations, and | ||
// merges them with the other validations if the condition passes; | ||
// either by returning `true` or a fulfilled promise. | ||
function checkConditional(runner, conditional) { | ||
try { | ||
return conditional[1].call(runner, runner.target); | ||
} catch (e) {} | ||
} | ||
}); | ||
}, | ||
// Get value corresponding to key containing "." from nested object. | ||
// If key containing "." is proper in object (e.g. {"foo.bar": 100}) return 100. | ||
function getVal(target, key){ | ||
var value = _.clone(target), keys; | ||
if(value[key]) return value[key]; | ||
if((keys = key.split('.')).length === 0) return undefined; | ||
// Runs through each of the `conditional` validations, and | ||
// merges them with the other validations if the condition passes; | ||
// either by returning `true` or a fulfilled promise. | ||
checkConditional: function(conditional) { | ||
var runner = this, validations = this.validations; | ||
return Checkit.Promise.resolve(true).then(function() { | ||
return conditional[1].call(runner, runner.target); | ||
}).then(function(result) { | ||
// Only if we explicitly return `true` do we go ahead | ||
// and add the validations to the stack for a particular rule. | ||
if (result === true) { | ||
var newVals = conditional[0]; | ||
for (var key in newVals) { | ||
validations[key] = validations[key] || []; | ||
validations[key] = validations[key].concat(newVals[key]); | ||
} | ||
} | ||
// We don't need to worry about thrown errors or failed promises, | ||
// because they're just a sign we're not supposed to run this rule. | ||
}, function(err) {}); | ||
}, | ||
while(keys.length > 0){ | ||
value = value[keys.shift()]; | ||
} | ||
return value; | ||
} | ||
// Processes an individual item in the validation collection for the current | ||
// validation object. Returns the value from the completed validation, which will | ||
// be a boolean, or potentially a promise if the current object is an async validation. | ||
processItem: function(currentValidation, key) { | ||
var result; | ||
var runner = this, errors = this.errors; | ||
var value = this.target[key]; | ||
var rule = currentValidation.rule; | ||
var params = [value].concat(currentValidation.params); | ||
function processItem(runner, currentValidation, key, context) { | ||
var value = getVal(runner.target, key); | ||
var rule = currentValidation.rule; | ||
var params = [value].concat(currentValidation.params).concat(context); | ||
// If the rule isn't an existence / required check, return | ||
// true if the value doesn't exist. | ||
if (rule !== 'accepted' && rule !== 'exists' && rule !== 'required') { | ||
if (value === '' || value == null) return; | ||
} | ||
// If the rule isn't an existence / required check, return | ||
// true if the value doesn't exist. | ||
if (rule !== 'accepted' && rule !== 'exists' && rule !== 'required') { | ||
if (value === '' || value == null) return; | ||
} | ||
var result = runRule(runner.checkit.validator, runner, rule, params) | ||
if (_.isBoolean(result) && result === false) { | ||
throw new ValidationError(runner.checkit.getMessage(currentValidation, key)); | ||
} | ||
return result; | ||
} | ||
// Create a fulfilled promise, so we can safely | ||
// run any function and not have a thrown error mess up our day. | ||
return Checkit.Promise.resolve(true).then(function() { | ||
if (_.isFunction(rule)) { | ||
result = rule.apply(runner, params); | ||
} else if (Validators[rule]) { | ||
var v = new Validator(runner); | ||
result = v[rule].apply(v, params); | ||
} else if (_[rule]) { | ||
result = _[rule].apply(_, params); | ||
} else if (_['is' + capitalize(rule)]) { | ||
result = _['is' + capitalize(rule)].apply(_, params); | ||
} else if (Regex[rule]) { | ||
result = Regex[rule].test(value); | ||
} else { | ||
var valErr = new ValidationError('No validation defined for ' + rule); | ||
valErr.validationObject = currentValidation; | ||
throw valErr; | ||
} | ||
return result; | ||
// Processes an individual item in the validation collection for the current | ||
// validation object. Returns the value from the completed validation, which will | ||
// be a boolean, or potentially a promise if the current object is an async validation. | ||
function processItemAsync(runner, currentValidation, key, context) { | ||
return Promise.resolve(true).then(function() { | ||
return processItem(runner, currentValidation, key, context) | ||
}); | ||
} | ||
// If the promise is fulfilled, but the value is explicitly `false`, | ||
// it's a failed validation... throw it as a `Checkit.ValidationError`. | ||
}).then(function(result) { | ||
if (_.isBoolean(result) && result === false) { | ||
throw new ValidationError(runner.getMessage(currentValidation, key)); | ||
} | ||
// Finally, catch any errors thrown from in the validation. | ||
}).then(null, function(err) { | ||
var fieldError; | ||
if (!(fieldError = errors[key])) { | ||
fieldError = errors[key] = new FieldError(err); | ||
fieldError.key = key; | ||
} | ||
// Attach the "rule" in case we want to reference it. | ||
err.rule = rule; | ||
fieldError.errors.push(err); | ||
}); | ||
}, | ||
// Gets the formatted messaage for the validation error, depending | ||
// on what's passed and whatnot. | ||
getMessage: function(item, key) { | ||
var base = this.base; | ||
var language = this.language; | ||
var label = item.label || base.labels[key] || language.labels[key] || this.labelTransform(key); | ||
var message = item.message || base.messages[item.rule] || language.messages[item.rule] || language.messages.fallback; | ||
message = message.replace(labelRegex, label); | ||
for (var i = 0, l = item.params.length; i < l; i++) { | ||
message = message.replace(varRegex(i+1), item.params[i]); | ||
} | ||
return message; | ||
function addError(errors, key, validation) { | ||
return function(err) { | ||
var fieldError = errors[key]; | ||
if (!fieldError) { | ||
fieldError = errors[key] = new FieldError(err) | ||
fieldError.key = key | ||
} | ||
}; | ||
err.rule = validation.rule | ||
fieldError.errors.push(err); | ||
} | ||
} | ||
// All of the stock "Validator" functions | ||
// also attached as `Checkit.Validators` for easy access to add new validators. | ||
var Validators = Checkit.Validators = { | ||
function runRule(validator, runner, rule, params) { | ||
var result; | ||
if (_.isFunction(rule)) { | ||
result = rule.apply(runner, params); | ||
} | ||
else if (typeof validator[rule] === 'function') { | ||
result = validator[rule].apply(validator, params); | ||
} | ||
else if (typeof _[rule] === 'function') { | ||
result = _[rule].apply(_, params); | ||
} | ||
else if (typeof _['is' + capitalize(rule)] === 'function') { | ||
result = _['is' + capitalize(rule)].apply(_, params); | ||
} | ||
else if (Checkit.Regex[rule]) { | ||
result = Checkit.Regex[rule].test(params[0]); | ||
} | ||
else { | ||
throw new ValidationError('No validation defined for ' + rule); | ||
} | ||
return result; | ||
} | ||
// Check if the value is an "accepted" value, useful for form submissions. | ||
accepted: function(val) { | ||
return _.contains(this._language.accepted, val); | ||
}, | ||
function SyncRunner() { | ||
Runner.apply(this, arguments) | ||
} | ||
inherits(SyncRunner, Runner) | ||
// The item must be a number between the given `min` and `max` values. | ||
between: function(val, min, max) { | ||
return (this.greaterThan(val, min) && | ||
this.lessThan(val, max)); | ||
}, | ||
// Runs the validations on a specified "target". | ||
SyncRunner.prototype.run = function(target, context) { | ||
var runner = this; | ||
this.target = target = _.clone(target || {}) | ||
this.context = context = _.clone(context || {}) | ||
// Check that an item contains another item, either a string, | ||
// array, or object. | ||
contains: function(val, item) { | ||
if (_.isString(val)) return val.indexOf(item) !== -1; | ||
if (_.isArray(val)) return _.indexOf(val, item) !== -1; | ||
if (_.isObject(val)) return _.has(val, item); | ||
return false; | ||
}, | ||
var validationHash = _.clone(this.checkit.validations); | ||
var errors = {} | ||
// The current value should be different than another field in the current | ||
// validation object. | ||
different: function(val, field) { | ||
return !this.matchesField(val, field); | ||
}, | ||
_.each(this.conditional, function(conditional) { | ||
var result = checkConditional(runner, conditional) | ||
if (result !== true) return; | ||
addVerifiedConditional(validationHash, conditional) | ||
}) | ||
// Check if two items are the exact same length | ||
exactLength: function(val, length) { | ||
return checkInt(length) || val.length === parseInt(length, 10); | ||
}, | ||
_.each(validationHash, function(validations, key) { | ||
_.each(validations, function(validation) { | ||
try { | ||
processItem(runner, validation, key, context) | ||
} catch(err) { | ||
addError(errors, key, validation)(err) | ||
} | ||
}) | ||
}) | ||
if (!_.isEmpty(errors)) { | ||
var err = new CheckitError(_.keys(errors).length + ' invalid values'); | ||
err.errors = errors; | ||
throw err; | ||
} | ||
return _.pick(target, _.keys(validationHash)); | ||
} | ||
// Key must not be `undefined`. | ||
exists: function(val) { | ||
return val !== void 0; | ||
}, | ||
// Constructor for running the `Validations`. | ||
function Validator(runner) { | ||
this._language = runner.language; | ||
this._target = runner.target || {}; | ||
} | ||
// Field is required and not empty (zero does not count as empty). | ||
required: function(val) { | ||
return (val != null && val !== '' ? true : false); | ||
}, | ||
_.extend(Validator.prototype, { | ||
// Matches another named field in the current validation object. | ||
matchesField: function(val, field) { | ||
return _.isEqual(val, this._target[field]); | ||
}, | ||
// Check if the value is an "accepted" value, useful for form submissions. | ||
accepted: function(val) { | ||
return _.contains(this._language.accepted, val); | ||
}, | ||
// Check that an item is a minimum length | ||
minLength: function(val, length) { | ||
return checkInt(length) || val.length >= length; | ||
}, | ||
// The item must be a number between the given `min` and `max` values. | ||
between: function(val, min, max) { | ||
return (this.greaterThan(val, min) && | ||
this.lessThan(val, max)); | ||
}, | ||
// Check that an item is less than a length | ||
maxLength: function(val, length) { | ||
return checkInt(length) || val.length <= length; | ||
}, | ||
// The item must be a number equal or larger than the given `min` and | ||
// equal or smaller than the given `max` value. | ||
range: function(val, min, max) { | ||
return (this.greaterThanEqualTo(val, min) && | ||
this.lessThanEqualTo(val, max)); | ||
}, | ||
// Check if one items is greater than another | ||
greaterThan: function(val, param) { | ||
return checkNumber(val) || checkNumber(param) || parseFloat(val) > parseFloat(param); | ||
}, | ||
// Check that an item contains another item, either a string, | ||
// array, or object. | ||
contains: function(val, item) { | ||
if (_.isString(val)) return val.indexOf(item) !== -1; | ||
if (_.isArray(val)) return _.indexOf(val, item) !== -1; | ||
if (_.isObject(val)) return _.has(val, item); | ||
return false; | ||
}, | ||
// Check if one items is greater than or equal to another | ||
greaterThanEqualTo: function(val, param) { | ||
return checkNumber(val) || checkNumber(param) || parseFloat(val) >= parseFloat(param); | ||
}, | ||
// The current value should be different than another field in the current | ||
// validation object. | ||
different: function(val, field) { | ||
return !this.matchesField(val, field); | ||
}, | ||
// Check if one item is less than another | ||
lessThan: function(val, param) { | ||
return checkNumber(val) || checkNumber(param) || parseFloat(val) < parseFloat(param); | ||
}, | ||
// Check if two items are the exact same length | ||
exactLength: function(val, length) { | ||
return checkInt(length) || val.length === parseInt(length, 10); | ||
}, | ||
// Check if one item is less than or equal to another | ||
lessThanEqualTo: function(val, param) { | ||
return checkNumber(val) || checkNumber(param) || parseFloat(val) <= parseFloat(param); | ||
}, | ||
// Key must not be `undefined`. | ||
exists: function(val) { | ||
return val !== void 0; | ||
}, | ||
// Check if this item is a plain object, defaulting to the lodash check, | ||
// otherwise using a simple underscore fallback. | ||
isPlainObject: function(val) { | ||
return _.isPlainObject ? _.isPlainObject.apply(this, arguments) : | ||
(_.isObject(val) && !_.isFunction(val) && !_.isArray(val)); | ||
}, | ||
// Field is required and not empty (zero does not count as empty). | ||
required: function(val) { | ||
return (val != null && val !== '' ? true : false); | ||
}, | ||
// Check if the value is numeric | ||
isNumeric: function(val) { | ||
return !isNaN(parseFloat(val)) && isFinite(val); | ||
} | ||
// Matches another named field in the current validation object. | ||
matchesField: function(val, field) { | ||
return _.isEqual(val, this._target[field]); | ||
}, | ||
}; | ||
// Check that an item is a minimum length | ||
minLength: function(val, length) { | ||
return checkInt(length) || val.length >= length; | ||
}, | ||
// Constructor for running the `Validations`. | ||
var Validator = Checkit.Validator = function(runner) { | ||
this._language = runner.language; | ||
this._target = runner.target; | ||
}; | ||
Validator.prototype = Validators; | ||
// Check that an item is less than a length | ||
maxLength: function(val, length) { | ||
return checkInt(length) || val.length <= length; | ||
}, | ||
// Validation helpers & regex | ||
// Check if one items is greater than another | ||
greaterThan: function(val, param) { | ||
return checkNumber(val) || checkNumber(param) || parseFloat(val) > parseFloat(param); | ||
}, | ||
function checkInt(val) { | ||
if (!val.match(Regex.integer)) | ||
throw new Error("The validator argument must be a valid integer"); | ||
} | ||
// Check if one items is greater than or equal to another | ||
greaterThanEqualTo: function(val, param) { | ||
return checkNumber(val) || checkNumber(param) || parseFloat(val) >= parseFloat(param); | ||
}, | ||
function checkNumber(val) { | ||
if (!Validators.isNumeric(val)) | ||
throw new Error("The validator argument must be a valid number"); | ||
} | ||
// Check if one item is less than another | ||
lessThan: function(val, param) { | ||
return checkNumber(val) || checkNumber(param) || parseFloat(val) < parseFloat(param); | ||
}, | ||
function checkString(val) { | ||
if (!_.isString(val)) | ||
throw new Error("The validator argument must be a valid string"); | ||
} | ||
// Check if one item is less than or equal to another | ||
lessThanEqualTo: function(val, param) { | ||
return checkNumber(val) || checkNumber(param) || parseFloat(val) <= parseFloat(param); | ||
}, | ||
// Standard regular expression validators. | ||
var Regex = Checkit.Regex = { | ||
alpha: /^[a-z]+$/i, | ||
alphaDash: /^[a-z0-9_\-]+$/i, | ||
alphaNumeric: /^[a-z0-9]+$/i, | ||
alphaUnderscore: /^[a-z0-9_]+$/i, | ||
base64: /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/, | ||
email: /^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,6}$/i, | ||
integer: /^\-?[0-9]+$/, | ||
ipv4: /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/i, | ||
luhn: /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/, | ||
natural: /^[0-9]+$/i, | ||
naturalNonZero: /^[1-9][0-9]*$/i, | ||
url: /^((http|https):\/\/(\w+:{0,1}\w*@)?(\S+)|)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/, | ||
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i | ||
}; | ||
// Check if the value is numeric | ||
numeric: numeric | ||
// An error for an individual "validation", where one or more "validations" | ||
// make up a single ruleset. These are grouped together into a `FieldError`. | ||
var ValidationError = Checkit.ValidationError = createError('ValidationError'); | ||
}) | ||
// An `Error` object specific to an individual field, | ||
// useful in the `Checkit.check` method when you're only | ||
// validating an individual field. It contains an "errors" | ||
// array which keeps track of any falidations | ||
var FieldError = Checkit.FieldError = createError('FieldError', {errors: []}); | ||
// Validation helpers & regex | ||
_.extend(FieldError.prototype, { | ||
function checkInt(val) { | ||
if (!val.match(Regex.integer)) | ||
throw new Error('The validator argument must be a valid integer'); | ||
} | ||
// Call `toString` on the current field, which should | ||
// turn the error into the format: | ||
toString: function(flat) { | ||
var errors = flat ? [this.errors[0]] : this.errors; | ||
return this.key + ': ' + | ||
_.pluck(errors, 'message').join(', '); | ||
}, | ||
function checkNumber(val) { | ||
if (!numeric(val)) | ||
throw new Error('The validator argument must be a valid number'); | ||
} | ||
// Returns the current error in json format, by calling `toJSON` | ||
// on the error, if there is one, otherwise returning the message. | ||
toJSON: function() { | ||
return this.map(function(err) { | ||
if (err.toJSON) return err.toJSON(); | ||
return err.message; | ||
}); | ||
} | ||
function numeric(val) { | ||
return !isNaN(parseFloat(val)) && isFinite(val); | ||
} | ||
}); | ||
// Standard regular expression validators. | ||
var Regex = Checkit.Regex = { | ||
alpha: /^[a-z]+$/i, | ||
alphaDash: /^[a-z0-9_\-]+$/i, | ||
alphaNumeric: /^[a-z0-9]+$/i, | ||
alphaUnderscore: /^[a-z0-9_]+$/i, | ||
base64: /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/, | ||
email: /^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,6}$/i, | ||
integer: /^\-?[0-9]+$/, | ||
ipv4: /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/i, | ||
ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i, | ||
luhn: /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/, | ||
natural: /^[0-9]+$/i, | ||
naturalNonZero: /^[1-9][0-9]*$/i, | ||
url: /^((http|https):\/\/(\w+:{0,1}\w*@)?(\S+)|)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/, | ||
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i | ||
}; | ||
// An object that inherits from the `Error` prototype, | ||
// but contains methods for working with the individual errors | ||
// created by the failed Checkit validation object. | ||
var CheckitError = Checkit.Error = createError('CheckitError', {errors: {}}); | ||
// An error for an individual "validation", where one or more "validations" | ||
// make up a single ruleset. These are grouped together into a `FieldError`. | ||
var ValidationError = createError('ValidationError'); | ||
_.extend(CheckitError.prototype, { | ||
// An `Error` object specific to an individual field, | ||
// useful in the `Checkit.check` method when you're only | ||
// validating an individual field. It contains an "errors" | ||
// array which keeps track of any falidations | ||
var FieldError = createError('FieldError', {errors: []}); | ||
get: function(name) { | ||
return this.errors[name]; | ||
}, | ||
_.extend(FieldError.prototype, { | ||
// Convert the current error object toString, by stringifying the JSON representation | ||
// of the object. | ||
toString: function(flat) { | ||
return 'Checkit Errors - ' + this.invoke('toString', flat).join('; '); | ||
}, | ||
// Call `toString` on the current field, which should | ||
// turn the error into the format: | ||
toString: function(flat) { | ||
var errors = flat ? [this.errors[0]] : this.errors; | ||
return this.key + ': ' + | ||
_.pluck(errors, 'message').join(', '); | ||
}, | ||
// Creates a JSON object of the validations, if `true` is passed - it will | ||
// flatten the error into a single value per item. | ||
toJSON: function(flat) { | ||
return this.reduce(function(memo, val, key) { | ||
memo[key] = val.toJSON(); | ||
return memo; | ||
}, {}); | ||
} | ||
// Returns the current error in json format, by calling `toJSON` | ||
// on the error, if there is one, otherwise returning the message. | ||
toJSON: function() { | ||
return this.map(function(err) { | ||
if (err.toJSON) return err.toJSON(); | ||
return err.message; | ||
}); | ||
} | ||
}); | ||
}); | ||
// Similar to a Backbone.js `Model` or `Collection`, we'll mixin the underscore | ||
// methods that make sense to act on `CheckitError.errors` or `FieldError.errors`. | ||
var objMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; | ||
var arrMethods = ['first', 'initial', 'rest', 'last']; | ||
var shareMethods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', | ||
'find', 'filter', 'reject', 'invoke', 'toArray', 'size', 'shuffle']; | ||
// An object that inherits from the `Error` prototype, | ||
// but contains methods for working with the individual errors | ||
// created by the failed Checkit validation object. | ||
var CheckitError = createError('CheckitError', {errors: {}}); | ||
_.each(shareMethods.concat(objMethods), function(method) { | ||
CheckitError.prototype[method] = function() { | ||
return _[method].apply(_, [this.errors].concat(_.toArray(arguments))); | ||
}; | ||
}); | ||
_.each(shareMethods.concat(arrMethods), function(method) { | ||
FieldError.prototype[method] = function() { | ||
return _[method].apply(_, [this.errors].concat(_.toArray(arguments))); | ||
}; | ||
}); | ||
_.extend(CheckitError.prototype, { | ||
// Used to transform the label before using it, can be | ||
// set globally or in the `options` for the Checkit object. | ||
Checkit.labelTransform = function(label) { | ||
return label; | ||
}; | ||
get: function(name) { | ||
return this.errors[name]; | ||
}, | ||
// Object containing languages for the validations... Feel free to | ||
// add anything to this object. | ||
Checkit.i18n = { | ||
// Convert the current error object toString, by stringifying the JSON representation | ||
// of the object. | ||
toString: function(flat) { | ||
return 'Checkit Errors - ' + this.invoke('toString', flat).join('; '); | ||
}, | ||
en: { | ||
// Creates a JSON object of the validations, if `true` is passed - it will | ||
// flatten the error into a single value per item. | ||
toJSON: function(flat) { | ||
return this.transform(function(acc, val, key) { | ||
var json = val.toJSON(); | ||
acc[key] = flat && _.isArray(json) ? json[0] : json | ||
}, {}); | ||
} | ||
accepted: ['on', 'yes', 1, '1', true, 'true'], | ||
}); | ||
labels: {}, | ||
// Similar to a Backbone.js `Model` or `Collection`, we'll mixin the underscore | ||
// methods that make sense to act on `CheckitError.errors` or `FieldError.errors`. | ||
var objMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; | ||
var arrMethods = ['first', 'initial', 'rest', 'last']; | ||
var shareMethods = ['forEach', 'each', 'map', 'reduce', 'transform', 'reduceRight', | ||
'find', 'filter', 'reject', 'invoke', 'toArray', 'size', 'shuffle']; | ||
messages: { | ||
_.each(shareMethods.concat(objMethods), function(method) { | ||
CheckitError.prototype[method] = function() { | ||
return _[method].apply(_, [this.errors].concat(_.toArray(arguments))); | ||
}; | ||
}); | ||
_.each(shareMethods.concat(arrMethods), function(method) { | ||
FieldError.prototype[method] = function() { | ||
return _[method].apply(_, [this.errors].concat(_.toArray(arguments))); | ||
}; | ||
}); | ||
// Custom Predicates | ||
email: 'The {{label}} must be a valid email address', | ||
exactLength: 'The {{label}} must be exactly {{var_1}} characters long', | ||
exists: 'The {{label}} must be defined', | ||
required: 'The {{label}} is required', | ||
minLength: 'The {{label}} must be at least {{var_1}} characters long', | ||
maxLength: 'The {{label}} must not exceed {{var_1}} characters long', | ||
lessThan: 'The {{label}} must be a number less than {{var_1}}', | ||
lessThanEqualTo: 'The {{label}} must be a number less than or equal to {{var_1}}', | ||
greaterThanEqualTo: 'The {{label}} must be a number greater than or equal to {{var_1}}', | ||
numeric: 'The {{label}} must be a numeric value', | ||
// Assorted Helper Items: | ||
// -------------------------- | ||
// Underscore Predicates | ||
date: 'The {{label}} must be a Date', | ||
equal: 'The {{label}} does not match {{var_1}}', | ||
'boolean': 'The {{label}} must be type "boolean"', | ||
empty: 'The {{label}} must be empty', | ||
array: 'The {{label}} must be an array', | ||
// Regular expression for matching the `field_name` and `var_n` | ||
var labelRegex = /\{\{label\}\}/g; | ||
function varRegex(i) { return new RegExp('{{var_' + i + '}}', 'g'); } | ||
// Regex specific messages. | ||
alpha: 'The {{label}} must only contain alphabetical characters', | ||
alphaDash: 'The {{label}} must only contain alpha-numeric characters, underscores, and dashes', | ||
alphaNumeric: 'The {{label}} must only contain alpha-numeric characters', | ||
alphaUnderscore: 'The {{label}} must only contain alpha-numeric characters, underscores, and dashes', | ||
natural: 'The {{label}} must be a positive number', | ||
naturalNonZero: 'The {{label}} must be a number greater than zero', | ||
ipv4: 'The {{label}} must be a valid IPv4 string', | ||
base64: 'The {{label}} must be a base64 string', | ||
luhn: 'The {{label}} must be a valid credit card number', | ||
uuid: 'The {{label}} must be a valid uuid', | ||
// Simple capitalize helper. | ||
function capitalize(word) { | ||
return word.charAt(0).toUpperCase() + word.slice(1); | ||
} | ||
// If there is no validation provided for an item, use this generic line. | ||
fallback: 'Validation for {{label}} did not pass' | ||
} | ||
// Preps the validations being sent to the `run` block, to standardize | ||
// the format and allow for maximum flexibility when passing to the | ||
// validation blocks. | ||
function prepValidations(validations) { | ||
validations = _.clone(validations); | ||
for (var key in validations) { | ||
var validation = validations[key]; | ||
if (!_.isArray(validation)) validations[key] = validation = [validation]; | ||
for (var i = 0, l = validation.length; i < l; i++) { | ||
validation[i] = assembleValidation(validation[i]); | ||
} | ||
}; | ||
// Assorted Helper Items: | ||
// -------------------------- | ||
// Regular expression for matching the `field_name` and `var_n` | ||
var labelRegex = /\{\{label\}\}/g; | ||
function varRegex(i) { return new RegExp('{{var_' + i + '}}', 'g'); } | ||
// Simple capitalize helper. | ||
function capitalize(word) { | ||
return word.charAt(0).toUpperCase() + word.slice(1); | ||
} | ||
return validations; | ||
} | ||
// Preps the validations being sent to the `run` block, to standardize | ||
// the format and allow for maximum flexibility when passing to the | ||
// validation blocks. | ||
function prepValidations(validations) { | ||
validations = _.clone(validations); | ||
for (var key in validations) { | ||
var validation = validations[key]; | ||
if (!_.isArray(validation)) validations[key] = validation = [validation]; | ||
for (var i = 0, l = validation.length; i < l; i++) { | ||
validation[i] = assembleValidation(validation[i]); | ||
} | ||
} | ||
return validations; | ||
// Turns the current validation item into an object literal, | ||
// containing the rule, any arguments split from the `:` delimeter | ||
function assembleValidation(validation) { | ||
if (!_.isPlainObject(validation)) { | ||
validation = {rule: validation, params: []}; | ||
} | ||
// Turns the current validation item into an object literal, | ||
// containing the rule, any arguments split from the `:` delimeter, | ||
// and the | ||
function assembleValidation(validation) { | ||
if (!Validators.isPlainObject(validation)) { | ||
validation = {rule: validation, params: []}; | ||
if (_.isString(validation.rule)) { | ||
var splitRule = validation.rule.split(':'); | ||
validation.rule = splitRule[0]; | ||
if (_.isEmpty(validation.params)) { | ||
validation.params = _.rest(splitRule); | ||
} | ||
if (_.isString(validation.rule)) { | ||
var splitRule = validation.rule.split(':'); | ||
validation.rule = splitRule[0]; | ||
if (_.isEmpty(validation.params)) { | ||
validation.params = _.rest(splitRule); | ||
} | ||
} else if (!_.isFunction(validation.rule)) { | ||
throw new TypeError('Invalid validation'); | ||
} | ||
return validation; | ||
} else if (!_.isFunction(validation.rule)) { | ||
throw new TypeError('Invalid validation'); | ||
} | ||
return validation; | ||
} | ||
Checkit.Promise = Promise; | ||
// feel free to swap this out if you'd like | ||
Checkit.FieldError = FieldError | ||
Checkit.Error = CheckitError | ||
Checkit.ValidationError = ValidationError | ||
Checkit.Runner = Runner | ||
Checkit.SyncRunner = SyncRunner | ||
Checkit.Validator = Validator | ||
return Checkit; | ||
}); | ||
// Boilerplate UMD definition block... | ||
// get the correct dependencies and initialize everything. | ||
})(function(checkitLib) { | ||
// AMD setup | ||
if (typeof define === 'function' && define.amd) { | ||
define(['lodash', 'create-error', 'bluebird'], checkitLib); | ||
// CJS setup | ||
} else if (typeof exports === 'object') { | ||
module.exports = checkitLib(require('lodash'), require('create-error'), require('bluebird')); | ||
// Browser globals | ||
} else { | ||
var root = this; | ||
var Checkit = root.Checkit = checkitLib(root._, root.createError, root.Promise); | ||
Checkit.noConflict = function() { | ||
root.checkit = lastCheckit; | ||
return checkit; | ||
}; | ||
} | ||
}); | ||
module.exports = Checkit |
{ | ||
"name": "checkit", | ||
"version": "0.2.0-pre", | ||
"version": "0.5.0", | ||
"description": "Simple validations for node and the browser.", | ||
"main": "checkit.js", | ||
"scripts": { | ||
"test": "mocha -R spec test/index.js" | ||
"test": "mocha -R spec test/index.js", | ||
"build": "npm run build:main && npm run build:dist", | ||
"build:main": "webpack --output-library Checkit --output-library-target umd checkit.js dist/checkit.js", | ||
"build:dist": "webpack --output-library Checkit --output-library-target umd --optimize-minimize checkit.js dist/checkit.min.js" | ||
}, | ||
@@ -13,5 +16,6 @@ "directories": { | ||
"dependencies": { | ||
"bluebird": ">=0.11.0", | ||
"create-error": ">=0.2.1", | ||
"lodash": ">=2.4.0" | ||
"inherits": "^2.0.1", | ||
"lodash": ">=2.0", | ||
"when": "^3.7.2" | ||
}, | ||
@@ -34,2 +38,2 @@ "devDependencies": { | ||
"license": "MIT" | ||
} | ||
} |
136
README.md
# Checkit.js | ||
A DOM-independent validation library for **Node.js** and the **browser**. | ||
A DOM-independent validation library for **Node.js**, **io.js** and the **browser**. | ||
It supports both sync | ||
It allows you to seamlessly validate full javascript objects, defining custom messages, labels, and validations, with full support for asynchronous validations with promises. It supports [conditional validations](#conditional-validations), and has powerful, consistent [error structuring](#checkit-errors) and [utility methods](#error-utility-methods) for manipulating your errors' output any way you can imagine. | ||
@@ -22,45 +24,12 @@ | ||
<!-- | ||
The [annotated source](/checkit/docs/checkit.html) code is available for browsing. | ||
--> | ||
## Installation | ||
Checkit has three hard dependencies: | ||
- an underscore library ([lo-dash](http://lodash.com) or | ||
[underscore.js](http://underscorejs.org)) | ||
- the [create-error](https://github.com/tgriesser/create-error) library | ||
- an A+ promise implementation, by default [bluebird](https://github.com/petkaantonov/bluebird), but this may be replaced with [Q](http://documentup.com/kriskowal/q/) or [when.js](https://github.com/cujojs/when). | ||
For more reading on promises, and the Bluebird API, take a look at following links: | ||
- [Why Promises?](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them) | ||
- [Bluebird API](https://github.com/petkaantonov/bluebird/blob/master/API.md) | ||
- [Promise Anti-Patterns](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns) | ||
- [Promise Nuggets](http://spion.github.io/promise-nuggets/) | ||
- [Snippets for Common Promise Code](https://github.com/petkaantonov/bluebird/wiki/Snippets) | ||
#### Node.js | ||
Installing with node.js `npm install checkit`, your dependencies should be taken care of automatically. | ||
``` | ||
npm install checkit | ||
``` | ||
#### Browser | ||
The easiest way to use the library is with AMD, but if you prefer to use browser globals, you'll need to include all dependencies before using the library: | ||
The easiest way to use the library is with [webpack](http://webpack.github.io) or [browserify](http://browserify.org) | ||
```html | ||
<script src="/create-error.js"></script> | ||
<script src="/lodash.js"></script> <!-- (or underscore.js) --> | ||
<script src="/bluebird.js"></script> | ||
<script src="/checkit.js"></script> | ||
``` | ||
If you'd prefer not to use "[bluebird](https://github.com/petkaantonov/bluebird)" as the promise implementation in the browser, you should change the AMD path to a different promise library, so that `Checkit.Promise` is set to an object containing two methods, `resolve` and `all`. | ||
The subsitute must be an "A+" promise implementation ([jQuery won't cut it](http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/)), but `when.js` or `Q` would both be suitable to swap out; if using browser globals, like the following: | ||
```js | ||
Checkit.Promise = Q; | ||
``` | ||
## API: | ||
@@ -76,14 +45,17 @@ | ||
Used to specify the default language key for using a particular language []() block. | ||
Used to specify the default language key for using a particular language file, currently en and es are supported. | ||
##### labels | ||
Specifies labels for use in error messages for specific keys | ||
##### messages | ||
Adds specific messages for individual errors | ||
#### Example: | ||
### checkit.run | ||
### checkit.validate (alias) | ||
```js | ||
@@ -110,2 +82,25 @@ var checkit = new Checkit({ | ||
### checkit.runSync | ||
### checkit.validateSync (alias) | ||
```js | ||
var checkit = new Checkit({ | ||
firstName: 'required', | ||
lastName: 'required', | ||
email: ['required', 'email'] | ||
}); | ||
var body = { | ||
email: 'test@example.com', | ||
firstName: 'Tim', | ||
lastName: 'Griesser', | ||
githubUsername: 'tgriesser' | ||
}; | ||
var [err, validated] = checkit.validateSync(body) | ||
// ... | ||
``` | ||
### Checkit.check(key, value, rules) | ||
@@ -120,11 +115,15 @@ | ||
### Checkit.Validators | ||
### Checkit.checkSync(key, value, rules) | ||
```js | ||
// ES6... | ||
var [err, resp] = Checkit.checkSync('email', email, ['required', 'validEmail']) | ||
### Checkit.language | ||
if (err) { | ||
} else { | ||
// ... | ||
} | ||
``` | ||
### Checkit.i18n | ||
An object containing default language | ||
## Available Validators | ||
@@ -232,5 +231,9 @@ | ||
<td>ipv4</td> | ||
<td>The value must be formatted as an IP address.</td> | ||
<td>The value must be formatted as an IPv4 address.</td> | ||
</tr> | ||
<tr> | ||
<td>ipv6</td> | ||
<td>The value must be formatted as an IPv6 address.</td> | ||
</tr> | ||
<tr> | ||
<td>lessThan:value</td> | ||
@@ -377,4 +380,4 @@ <td>The value must be "less than" the specified value.</td> | ||
arr: { | ||
rule: 'contains', // different behavior than "contains:10" | ||
params: [10] | ||
rule: 'contains', | ||
params: [10] // Number => Different behavior than "contains:10" | ||
} | ||
@@ -384,2 +387,25 @@ } | ||
You may also use the `context` parameter passed to `run` when using a function on the validation array of a property. This can be particularly useful if your validation function needs to execute within a transaction: | ||
```js | ||
{ | ||
email: { | ||
rule: function(val, params, context){ | ||
var query = knex('users'); | ||
if (context && context.transacting){ | ||
query.transacting(context.transacting); | ||
} | ||
return query.where('email', '=', val) | ||
.andWhere('id', '<>', this.target.id) | ||
.then(function(resp){ | ||
if (resp.length > 0){ | ||
throw new Error('The email address is already in use.'); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
``` | ||
Second, you may add a custom validator to the `Checkit.Validators` object, returning a boolean value or a promise. | ||
@@ -480,3 +506,3 @@ | ||
The `Checkit.labelTransform` method takes a function which | ||
The `Checkit.labelTransform` method takes a function that receives the field name and returns a human-readable label for use in error messages. | ||
@@ -486,2 +512,8 @@ | ||
### 0.5.0 | ||
- Major internal refactoring, using when.js to shave bytes in the browser build. | ||
- Added sync api with runSync / checkSync / validateSync | ||
- Alias `validate` for `run` | ||
### 0.2.0 | ||
@@ -495,2 +527,2 @@ | ||
Initial release | ||
Initial release |
@@ -37,2 +37,5 @@ | ||
ipv6Long: '2001:cdba:0000:0000:0000:0000:3257:9652', | ||
ipv6Short: '::', | ||
isFunction: function() { | ||
@@ -83,2 +86,2 @@ return true; | ||
})(); | ||
})(); |
@@ -5,2 +5,3 @@ global.Promise = require('bluebird'); | ||
global.assert = require('assert'); | ||
global.equal = require('assert').equal; | ||
@@ -10,2 +11,3 @@ global.deepEqual = require('assert').deepEqual; | ||
require('./block'); | ||
require('./spec'); | ||
require('./spec'); | ||
require('./sync'); |
335
test/spec.js
describe('Checkit', function() { | ||
var handler = function(dfd, ok, onRejected) { | ||
dfd.then(null, onRejected).then(function() { | ||
ok(); | ||
}).then(null, function(err) { | ||
ok(err.toString()); | ||
}); | ||
}; | ||
describe('.check', function() { | ||
it('should accept a field, value, and rules', function(ok) { | ||
it('should accept a field, value, and rules', function() { | ||
Checkit.check('email', 'tim@tgriesser', ['required', 'email']).then(null, function(err) { | ||
return Checkit.check('email', 'tim@tgriesser', ['required', 'email']).catch(function(err) { | ||
equal(err instanceof Checkit.FieldError, true); | ||
equal(err.message, 'The email must be a valid email address'); | ||
ok(); | ||
}); | ||
@@ -29,4 +20,4 @@ | ||
it('passes with on', function(ok) { | ||
handler(Checkit({ | ||
it('passes with on', function() { | ||
return Checkit({ | ||
accepted1: 'accepted', | ||
@@ -36,3 +27,3 @@ accepted2: 'accepted', | ||
accepted4: 'accepted' | ||
}).run(testBlock), ok); | ||
}).run(testBlock); | ||
}); | ||
@@ -44,18 +35,20 @@ | ||
it('should pass for numbers', function(ok) { | ||
handler(Checkit({ | ||
it('should pass for numbers', function() { | ||
return Checkit({ | ||
integer: ['between:10:15'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
it('should pass for numbers in strings', function(ok) { | ||
handler(Checkit({ | ||
it('should pass for numbers in strings', function() { | ||
return Checkit({ | ||
stringInteger: ['between:10:15'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
it('should fail if the value is outside the range', function(ok) { | ||
handler(Checkit({ | ||
it('should fail if the value is outside the range', function() { | ||
return Checkit({ | ||
integer: ['between:0:10'] | ||
}).run(testBlock), ok, function() {}); | ||
}).run(testBlock).catch(function() { | ||
return true; | ||
}).then(function(val) { equal(val, true) }) | ||
}); | ||
@@ -65,14 +58,52 @@ | ||
describe('range', function() { | ||
it('should pass for numbers', function() { | ||
return Checkit({ | ||
integer: ['between:10:15'] | ||
}).run(testBlock) | ||
}); | ||
it('should pass for numbers in strings', function() { | ||
return Checkit({ | ||
stringInteger: ['between:10:15'] | ||
}).run(testBlock) | ||
}); | ||
it('should fail if the value is outside the range', function() { | ||
return Checkit({ | ||
integer: ['between:0:10'] | ||
}).run(testBlock).catch(function() { | ||
return true; | ||
}).then(function(val) { equal(val, true) }) | ||
}); | ||
it('should not treat the min values as inclusive', function() { | ||
return Checkit({ | ||
integer: ['between:12:13'] | ||
}).run(testBlock).catch(function() { | ||
return true; | ||
}).then(function(val) { equal(val, true) }) | ||
}); | ||
it('should not treat the max values as inclusive', function() { | ||
return Checkit({ | ||
integer: ['between:0:12'] | ||
}).run(testBlock).catch(function() { | ||
return true; | ||
}).then(function(val) { equal(val, true) }) | ||
}); | ||
}); | ||
describe('emails', function() { | ||
it('passes with a valid email', function(ok) { | ||
handler(Checkit({email: ['email']}).run(testBlock), ok); | ||
it('passes with a valid email', function() { | ||
return Checkit({email: ['email']}).run(testBlock) | ||
}); | ||
it('does not run on an empty input', function(ok) { | ||
handler(Checkit({email: ['email']}).run(testBlock), ok); | ||
it('does not run on an empty input', function() { | ||
return Checkit({email: ['email']}).run(testBlock) | ||
}); | ||
it('fails with an invalid email', function(ok) { | ||
handler(Checkit({emailFail: ['email']}).run(testBlock), ok, function(err) { | ||
it('fails with an invalid email', function() { | ||
return Checkit({emailFail: ['email']}).run(testBlock).catch(function(err) { | ||
equal(err.get('emailFail').toString(), 'emailFail: The emailFail must be a valid email address'); | ||
@@ -85,4 +116,4 @@ }); | ||
describe('integer', function() { | ||
it('should pass for numbers and strings (positive and negative)', function(ok) { | ||
handler(Checkit({ | ||
it('should pass for numbers and strings (positive and negative)', function() { | ||
return Checkit({ | ||
integer: 'integer', | ||
@@ -92,3 +123,3 @@ negativeInteger: 'integer', | ||
negativeStringInteger: 'integer' | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
@@ -98,20 +129,22 @@ }); | ||
describe('numeric', function() { | ||
it('should only pass for numbers for negative numbers and strings', function(ok) { | ||
handler(Checkit({ | ||
negativeInteger: 'isNumeric', | ||
negativeStringInteger: 'isNumeric' | ||
}).run(testBlock), ok); | ||
it('should only pass for numbers for negative numbers and strings', function() { | ||
return Checkit({ | ||
negativeInteger: 'numeric', | ||
negativeStringInteger: 'numeric' | ||
}).run(testBlock) | ||
}); | ||
it('should pass for positive numbers and strings', function(ok) { | ||
handler(Checkit({ | ||
integer: 'isNumeric', | ||
stringInteger: 'isNumeric' | ||
}).run(testBlock), ok); | ||
it('should pass for positive numbers and strings', function() { | ||
return Checkit({ | ||
integer: 'numeric', | ||
stringInteger: 'numeric' | ||
}).run(testBlock) | ||
}); | ||
it('should fail for NaN', function(ok) { | ||
handler(Checkit({ | ||
isNaN: 'isNumeric' | ||
}).run(testBlock), ok, function() {}); | ||
it('should fail for NaN', function() { | ||
return Checkit({ | ||
isNaN: 'numeric' | ||
}).run(testBlock).catch(function() { | ||
return true; | ||
}).then(function(val) { equal(val, true) }) | ||
}); | ||
@@ -123,19 +156,21 @@ | ||
it('should only pass for numbers', function(ok) { | ||
handler(Checkit({ | ||
it('should only pass for numbers', function() { | ||
return Checkit({ | ||
integer: ['isNumber'], | ||
negativeInteger: ['isNumber'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
it('should fail for numbers in strings', function(ok) { | ||
handler(Checkit({ | ||
it('should fail for numbers in strings', function() { | ||
return Checkit({ | ||
stringInteger: ['isNumber'] | ||
}).run(testBlock), ok, function() {}); | ||
}).run(testBlock).catch(function() { | ||
return true; | ||
}).then(function(val) { equal(val, true) }) | ||
}); | ||
it('should pass for NaN', function(ok) { | ||
handler(Checkit({ | ||
it('should pass for NaN', function() { | ||
return Checkit({ | ||
isNaN: ['isNumber'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
@@ -145,7 +180,7 @@ | ||
describe('isNaN', function(ok) { | ||
it('should only pass for NaN', function(ok) { | ||
handler(Checkit({ | ||
describe('isNaN', function() { | ||
it('should only pass for NaN', function() { | ||
return Checkit({ | ||
isNaN: ['isNaN'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
@@ -156,21 +191,25 @@ }); | ||
it('should pass for true and false', function(ok) { | ||
handler(Checkit({ | ||
it('should pass for true and false', function() { | ||
return Checkit({ | ||
booleanTrue: ['boolean'], | ||
booleanFalse: ['boolean'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
it('should not pass for "true" and "false"', function(ok) { | ||
handler(Checkit({ | ||
it('should not pass for "true" and "false"', function() { | ||
return Checkit({ | ||
trueString: ['boolean'], | ||
falseString: ['boolean'] | ||
}).run(testBlock), ok, function() {}); | ||
}).run(testBlock).catch(function() { | ||
return true; | ||
}).then(function(val) { equal(val, true) }) | ||
}); | ||
it('should not pass for 0 and 1', function(ok) { | ||
handler(Checkit({ | ||
it('should not pass for 0 and 1', function() { | ||
return Checkit({ | ||
zero: ['boolean'], | ||
one: ['boolean'] | ||
}).run(testBlock), ok, function() {}); | ||
}).run(testBlock).catch(function() { | ||
return true; | ||
}).then(function(val) { equal(val, true) }) | ||
}); | ||
@@ -180,14 +219,30 @@ | ||
describe('ipv6', function() { | ||
it('should pass for short ipv6', function() { | ||
return Checkit({ | ||
ipv6Short: ['ipv6'] | ||
}).run(testBlock) | ||
}); | ||
it('should pass for long ipv6', function() { | ||
return Checkit({ | ||
ipv6Long: ['ipv6'] | ||
}).run(testBlock) | ||
}); | ||
}); | ||
describe('uuid', function() { | ||
it('should pass for uuid v1', function(ok) { | ||
handler(Checkit({ | ||
it('should pass for uuid v1', function() { | ||
return Checkit({ | ||
uuidv1: ['uuid'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
it('should pass for uuid v4', function(ok) { | ||
handler(Checkit({ | ||
it('should pass for uuid v4', function() { | ||
return Checkit({ | ||
uuidv4: ['uuid'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
@@ -199,12 +254,12 @@ | ||
it('should validate a http url', function(ok) { | ||
handler(Checkit({ | ||
it('should validate a http url', function() { | ||
return Checkit({ | ||
url1: ['url'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
it('should validate a https url', function(ok) { | ||
handler(Checkit({ | ||
it('should validate a https url', function() { | ||
return Checkit({ | ||
url2: ['url'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
@@ -216,8 +271,8 @@ | ||
it('should check ipv4 and addresses', function(ok) { | ||
handler(Checkit({ipv4: ['ipv4']}).run(testBlock), ok); | ||
it('should check ipv4 and addresses', function() { | ||
return Checkit({ipv4: ['ipv4']}).run(testBlock) | ||
}); | ||
it('should return true on a valid base64 string', function(ok) { | ||
handler(Checkit({base64: 'base64'}).run(testBlock), ok); | ||
it('should return true on a valid base64 string', function() { | ||
return Checkit({base64: 'base64'}).run(testBlock) | ||
}); | ||
@@ -229,4 +284,4 @@ | ||
it('should pass with arguments', function(ok) { | ||
handler(Checkit({isArguments: "isArguments"}).run(testBlock), ok); | ||
it('should pass with arguments', function() { | ||
return Checkit({isArguments: "isArguments"}).run(testBlock) | ||
}); | ||
@@ -238,4 +293,4 @@ | ||
it('passes on empty string, array, object, null', function(ok) { | ||
handler(Checkit({ | ||
it('passes on empty string, array, object, null', function() { | ||
return Checkit({ | ||
isEmptyArray: ['isEmpty'], | ||
@@ -245,3 +300,3 @@ isEmptyString: ['isEmpty'], | ||
isEmptyNull: ['isEmpty'] | ||
}).run(testBlock), ok); | ||
}).run(testBlock) | ||
}); | ||
@@ -278,4 +333,4 @@ | ||
it('allows for custom labels and messages', function(ok) { | ||
Checkit(vals).run({email: ''}).then(null, function(err) { | ||
it('allows for custom labels and messages', function() { | ||
return Checkit(vals).run({email: ''}).then(null, function(err) { | ||
equal(err.get('email').message, 'The Email Address Field is required'); | ||
@@ -286,8 +341,6 @@ equal(err.get('first_name').message, 'You must supply a first name value'); | ||
equal(err.get('first_name').message, 'The first name of this application must be at least 3 characters long'); | ||
}).then(function() { | ||
ok(); | ||
}, ok); | ||
}); | ||
}); | ||
it('allows for custom params', function(ok) { | ||
it('allows for custom params', function() { | ||
var containsTest = { | ||
@@ -299,9 +352,7 @@ arr: { | ||
}; | ||
Checkit(containsTest).run({arr: [0, 10, 20]}).then(function() { | ||
return Checkit(containsTest).run({arr: [0, 10, 20]}).then(function() { | ||
return Checkit(_.extend(containsTest, {arr: 'contains:10'})).run({arr: [0, 10, 20]}); | ||
}).then(null, function(err) { | ||
equal(err.get('arr').message, 'Validation for arr did not pass'); | ||
}).then(function() { | ||
ok(); | ||
}, ok); | ||
equal(err.get('arr').message, 'The arr must contain 10'); | ||
}); | ||
}); | ||
@@ -311,2 +362,56 @@ | ||
describe('custom validation rules', function() { | ||
it('should run the rule function on the supplied value', function() { | ||
var value = 'value'; | ||
var rulesTest = { | ||
valueTest: { | ||
rule: function(val) { | ||
equal(value, val); | ||
} | ||
} | ||
}; | ||
return Checkit(rulesTest).run({valueTest: value}) | ||
}) | ||
it('should fail when the validation rule throws an error', function(){ | ||
var rulesTest = { | ||
failedRuleTest: { | ||
rule: function(val){ | ||
throw new Error('thrown from rule function'); | ||
} | ||
} | ||
}; | ||
return Checkit(rulesTest).run({failedRuleTest: "value"}).then(null, function(err){ | ||
equal(err.get('failedRuleTest').message, 'thrown from rule function'); | ||
}); | ||
}) | ||
it('should pass the supplied parameter to the validation rule', function(){ | ||
var parameter = 'parameter'; | ||
var rulesTest = { | ||
parameterTest: { | ||
rule: function(val, param){ | ||
equal(parameter, param); | ||
}, | ||
params: parameter | ||
} | ||
}; | ||
return Checkit(rulesTest).run({parameterTest: "value"}) | ||
}) | ||
it('should pass the context property supplied to the run function to the rule function', function(){ | ||
var runContext = 'the context'; | ||
var rulesTest = { | ||
contextTest: { | ||
rule: function(val, params, context){ | ||
equal(runContext, context); | ||
} | ||
} | ||
} | ||
return Checkit(rulesTest).run({contextTest: "value"}, runContext) | ||
}) | ||
}); | ||
describe('conditional items', function() { | ||
@@ -321,10 +426,9 @@ | ||
it('validates for items that pass the conditional', function(ok) { | ||
checkit.run({email: 'joe@gmail.com', first_name: 'tim'}) | ||
it('validates for items that pass the conditional', function() { | ||
return checkit.run({email: 'joe@gmail.com', first_name: 'tim'}) | ||
.then(function() { | ||
return Promise.reject(new Error('Should not pass')); | ||
}, function(err) { | ||
equal(err.toString(), 'Checkit Errors - email: Validation for email did not pass'); | ||
}).catch(function(err) { | ||
equal(err.toString(), 'Checkit Errors - email: The email must contain tim'); | ||
}) | ||
.then(null, ok) | ||
.then(function() { | ||
@@ -335,13 +439,10 @@ return checkit.run({email: 'tim@gmail', first_name: 'tim'}); | ||
return Promise.reject(new Error('Should not pass')); | ||
}, function(err) { | ||
}) | ||
.catch(function(err) { | ||
equal(err.toString(), 'Checkit Errors - email: The email must be a valid email address'); | ||
ok(); | ||
}) | ||
.then(null, ok); | ||
}); | ||
it('doesnt validate if the item doesnt pass the conditional', function(ok) { | ||
checkit.run({email: 'joe@gmail.com', first_name: 'joe'}).then(function() { | ||
ok(); | ||
}); | ||
it('doesnt validate if the item doesnt pass the conditional', function() { | ||
return checkit.run({email: 'joe@gmail.com', first_name: 'joe'}) | ||
}); | ||
@@ -351,2 +452,10 @@ | ||
}); | ||
describe('nested items', function(){ | ||
it('validates for nested items', function(){ | ||
return Checkit({ | ||
"info.email": ['required', 'email'] | ||
}).run({info: {email: "joe@gmail.com"}}) | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
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
1470053
35
26254
519
10
4
+ Addedinherits@^2.0.1
+ Addedwhen@^3.7.2
+ Addedinherits@2.0.4(transitive)
+ Addedwhen@3.7.8(transitive)
- Removedbluebird@>=0.11.0
- Removedbluebird@3.7.2(transitive)
Updatedlodash@>=2.0