assurance
Version:
Node validation/sanitization library with a handsome API
1,169 lines (933 loc) • 24.9 kB
JavaScript
;(function(){
/**
* hasOwnProperty.
*/
var has = Object.prototype.hasOwnProperty;
/**
* Require the given path.
*
* @param {String} path
* @return {Object} exports
* @api public
*/
function require(path, parent, orig) {
var resolved = require.resolve(path);
// lookup failed
if (null == resolved) {
orig = orig || path;
parent = parent || 'root';
var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
err.path = orig;
err.parent = parent;
err.require = true;
throw err;
}
var module = require.modules[resolved];
// perform real require()
// by invoking the module's
// registered function
if (!module.exports) {
module.exports = {};
module.client = module.component = true;
module.call(this, module.exports, require.relative(resolved), module);
}
return module.exports;
}
/**
* Registered modules.
*/
require.modules = {};
/**
* Registered aliases.
*/
require.aliases = {};
/**
* Resolve `path`.
*
* Lookup:
*
* - PATH/index.js
* - PATH.js
* - PATH
*
* @param {String} path
* @return {String} path or null
* @api private
*/
require.resolve = function(path) {
if (path.charAt(0) === '/') path = path.slice(1);
var index = path + '/index.js';
var paths = [
path,
path + '.js',
path + '.json',
path + '/index.js',
path + '/index.json'
];
for (var i = 0; i < paths.length; i++) {
var path = paths[i];
if (has.call(require.modules, path)) return path;
}
if (has.call(require.aliases, index)) {
return require.aliases[index];
}
};
/**
* Normalize `path` relative to the current path.
*
* @param {String} curr
* @param {String} path
* @return {String}
* @api private
*/
require.normalize = function(curr, path) {
var segs = [];
if ('.' != path.charAt(0)) return path;
curr = curr.split('/');
path = path.split('/');
for (var i = 0; i < path.length; ++i) {
if ('..' == path[i]) {
curr.pop();
} else if ('.' != path[i] && '' != path[i]) {
segs.push(path[i]);
}
}
return curr.concat(segs).join('/');
};
/**
* Register module at `path` with callback `definition`.
*
* @param {String} path
* @param {Function} definition
* @api private
*/
require.register = function(path, definition) {
require.modules[path] = definition;
};
/**
* Alias a module definition.
*
* @param {String} from
* @param {String} to
* @api private
*/
require.alias = function(from, to) {
if (!has.call(require.modules, from)) {
throw new Error('Failed to alias "' + from + '", it does not exist');
}
require.aliases[to] = from;
};
/**
* Return a require function relative to the `parent` path.
*
* @param {String} parent
* @return {Function}
* @api private
*/
require.relative = function(parent) {
var p = require.normalize(parent, '..');
/**
* lastIndexOf helper.
*/
function lastIndexOf(arr, obj) {
var i = arr.length;
while (i--) {
if (arr[i] === obj) return i;
}
return -1;
}
/**
* The relative require() itself.
*/
function localRequire(path) {
var resolved = localRequire.resolve(path);
return require(resolved, parent, path);
}
/**
* Resolve relative to the parent.
*/
localRequire.resolve = function(path) {
var c = path.charAt(0);
if ('/' == c) return path.slice(1);
if ('.' == c) return require.normalize(p, path);
// resolve deps by returning
// the dep in the nearest "deps"
// directory
var segs = parent.split('/');
var i = lastIndexOf(segs, 'deps') + 1;
if (!i) i = 0;
path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
return path;
};
/**
* Check if module is defined at `path`.
*/
localRequire.exists = function(path) {
return has.call(require.modules, localRequire.resolve(path));
};
return localRequire;
};
require.register("danmilon-object.keys-shim/index.js", function(exports, require, module){
// grabbed from https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
Object.keys = (function () {
var has = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString')
var dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
]
var dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
throw new TypeError('Object.keys called on non-object');
}
var result = [];
for (var prop in obj) {
if (has.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (var i = 0; i < dontEnumsLength; i++) {
if (has.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
}
})()
}
module.exports = Object.keys
});
require.register("yields-isarray/index.js", function(exports, require, module){
/**
* isArray
*/
var isArray = Array.isArray;
/**
* toString
*/
var str = Object.prototype.toString;
/**
* Whether or not the given `val`
* is an array.
*
* example:
*
* isArray([]);
* // > true
* isArray(arguments);
* // > false
* isArray('');
* // > false
*
* @param {mixed} val
* @return {bool}
*/
module.exports = isArray || function (val) {
return !! val && '[object Array]' == str.call(val);
};
});
require.register("assurance/lib/shims.js", function(exports, require, module){
// if we run in the browser, load some shims
// if not, supress the error and move on
try {
require('object.keys-shim')
Array.isArray = Array.isArray || require('isarray')
} catch (ex) {
}
});
require.register("assurance/lib/index.js", function(exports, require, module){
// just for the browser
require('./shims')
var Assurance = require('./Assurance')
, errors = require('./errors')
, AssuranceGroup = require('./AssuranceGroup')
, singleton = require('./singleton')
, validators = require('./validators')
, sanitizers = require('./sanitizers')
module.exports = singleton
module.exports.validators = validators
module.exports.sanitizers = sanitizers
module.exports.errors = errors
module.exports.Assurance = Assurance
module.exports.single = function (object, key, alias) {
var assure = new Assurance(object, alias)
return assure.me(key)
}
module.exports.group = function () {
return new AssuranceGroup()
}
});
require.register("assurance/lib/Assurance.js", function(exports, require, module){
var validators = require('./validators')
, sanitizers = require('./sanitizers')
, errors = require('./errors')
/**
* @param {Object} object Object to validate
* @param {Array} onlyFields Only validate fields in this array
* @param {Object} alias Alias param value of errors
*
* @constructor
*/
function Assurance(object, onlyFields, alias) {
if (!(this instanceof Assurance)) {
return new Assurance(object, onlyFields, alias)
}
this.restart(object, onlyFields, alias)
return this
}
/**
* Restarts this instance as it would be if it was called
* as `new Assurance(object, onlyFields, alias)`
*
* @param {Object} object Object to validate
* @param {Array} onlyFields Only validate fields in this array
* @param {Object} alias Alias param value of errors
*/
Assurance.prototype.restart = function (object, onlyFields, alias) {
this.only = undefined
this.alias = undefined
this.object = object
this._path = []
this.errors = []
this._skip = false
this._canPop = true
if (typeof onlyFields === 'string') {
onlyFields = [onlyFields]
}
if (Array.isArray(onlyFields)) {
this.only = onlyFields
}
else if (typeof onlyFields === 'object') {
alias = onlyFields
onlyFields = undefined
}
var aliasType = typeof alias
if (aliasType !== 'undefined') {
if (aliasType !== 'object') {
throw new Error('alias not an object')
}
this.alias = alias
}
return this
}
/**
* @return {Boolean} Whether any error has occured yet or not
*/
Assurance.prototype.hasErrors = function () {
return this.errors.length !== 0
};
/**
* @return {Array} Errors occured so far
*/
Assurance.prototype.end = function () {
return this.errors
}
Assurance.prototype.errors = Assurance.prototype.end
/**
* Throws the first error in the stack
*/
Assurance.prototype.throw = function () {
if (this.hasErrors()) {
throw this.errors[0]
}
}
Assurance.prototype._pop = function () {
if (this._canPop) {
this._path.pop()
}
}
/**
* @return {Object} The top object in the path stack
* @private
*/
Assurance.prototype._top = function () {
if (this._path.length === 0) {
return this.object
}
return this._path[this._path.length - 1]
}
/**
* @return {} The value of the top object in the path stack
* @private
*/
Assurance.prototype._val = function (set) {
if (this._path.length === 0) {
return this.object
}
var top = this._top()
if (arguments.length !== 0) {
if (typeof top.index === 'undefined') {
top.object[top.key] = set
}
else {
top.object[top.key][top.index] = set
}
return this
}
else {
var val = top.object[top.key]
if (typeof top.index !== 'undefined') {
val = val[top.index]
}
return val
}
}
/**
* Computes a string representing the trail of the path stack
* eg: `address.zipCode`, `sisters[1].firstName`
*
* @return {String} Trail of top object in path stack
* @private
*/
Assurance.prototype._param = function () {
var param
for (var i = 0; i < this._path.length; i++) {
var step = this._path[i]
if (!param) {
param = step.key
}
else {
param += '.' + step.key
}
if (typeof step.index !== 'undefined') {
param += '[' + step.index + ']'
}
}
if (this.alias) {
param = this.alias[param] || param
}
return param
}
/**
* Enables the skip flag if the top object in the
* path stack doesn't exist
*/
Assurance.prototype.optional = function () {
if (!this._skip) {
var val = this._val()
// null is not included here because it is
// considered an actual value
var shouldSkip =
(typeof val === 'undefined') ||
(val === '')
if (shouldSkip) {
this._skip = true
}
}
return this
}
/**
* If the currently validated field is missing, val is assigned
*
* @param {} val Value to assign
*/
Assurance.prototype.default = function (val) {
var currVal = this._val()
var isInvalid =
(typeof currVal === 'undefined') ||
(currVal === null) ||
(currVal === '')
if (isInvalid) {
this._val(val)
}
return this
}
/**
* If the last value is an array, repetively calls fn
* for each value in the array
*
* If it is an object, it dive's into the object, so nested
* fields can be validated
*
* @param {Function} fn Function to call
*/
Assurance.prototype.nest = function (fn) {
if (this._skip) {
return
}
var val = this._val()
if (Array.isArray(val)) {
var top = this._top()
for (var i = 0; i < val.length; i++) {
// set the index since it's an array
// this will be picked up by _param() to set
// a meaningful error parameter
top.index = i
// repeatidly call fn
fn(this)
// in case validation failed for one value,
// we don't want to skip for the rest also.
// So force _skip to false after every iteration
this._skip = false
}
}
else if (typeof val === 'object') {
// since we're going to nest, we dont want the object we just pushed
// to be popped in the next .me() call
this._canPop = false
var prevLength = this._path.length
fn(this)
// now its safe to pop
this._canPop = true
// if fn called .me() at least once, then there is a single
// remaining object on the path stack that needs to be popped
if (this._path.length !== prevLength) {
this._pop()
}
}
else {
// force it to push an error
this.is('object')
}
return this
}
/**
* Allows for custom validation. calls fn(val, errors)
*
* If fn returns an error, it is added on the stack
*
* @param {Function} fn Function to call
*/
Assurance.prototype.custom = function (fn) {
if (this._skip) {
return
}
var val = this._val()
, error = fn(val, errors)
if (error) {
// set error param and push
error.param = this._param()
this.errors.push(error)
// since we failed once, skip rest validations
this._skip = true
}
return this
}
/**
* Validate top[key] where top is either
*
* * the initial object passed to assurance
* * the last nested object
*
* If fn is passed, it instantly nests
*
* @param {String} key Key to validate
* @param {Function} fn Nest and call fn
*/
Assurance.prototype.me = function (key, fn) {
// if we check only for certain fields and this is not
// one of them then skip all following validation
if (this.only && this.only.indexOf(key) === -1) {
this._skip = true
return this
}
// pop the last object from the path stack, since it's
// validations are over (now we'll validate another field)
this._pop()
// the next .me() calls can pop me
this._canPop = true
// add new object in path
this._path.push({
key: key,
object: this._val()
})
// we're starting a new set of validations
// so unset any previously set options
this._skip = false
// if a function is passed, then we need to nest
if (fn) {
this.nest(fn)
}
return this
}
Assurance.prototype.check = Assurance.prototype.me
Assurance.runValidator = function (fn) {
return function () {
if (this._skip) {
return this
}
var args = Array.prototype.slice.call(arguments)
, val = this._val()
args.unshift(val)
var err = fn.apply(null, args)
if (typeof err === 'undefined') {
return this
}
else {
err.param = this._param()
this.errors.push(err)
this._skip = true
}
return this
}
}
Assurance.runSanitizer = function (fn) {
return function () {
if (this._skip) {
return this
}
var args = Array.prototype.slice.call(arguments)
, top = this._top()
, val = this._val()
args.unshift(val)
var newVal = fn.apply(null, args)
if (typeof newVal === 'undefined') {
return this
}
if (newVal instanceof Error) {
newVal.param = this._param()
this.errors.push(newVal)
this._skip = true
}
else {
top.object[top.key] = newVal
}
return this
}
}
// install sanitizers
Object.keys(sanitizers).forEach(function (sanitizer) {
var fn = sanitizers[sanitizer]
Assurance.prototype[sanitizer] = Assurance.runSanitizer(fn)
})
// install validators
Object.keys(validators).forEach(function (validator) {
var fn = validators[validator]
Assurance.prototype[validator] = Assurance.runValidator(fn)
})
module.exports = Assurance
});
require.register("assurance/lib/AssuranceGroup.js", function(exports, require, module){
// CRAPNESS. SHOULD BE DEPRECATED
var Assurance = require('./Assurance')
function AssuranceGroup() {
this.assurances = []
}
AssuranceGroup.prototype.me = function (object, key) {
var assure = new Assurance(object)
this.assurances.push(assure)
return assure.me(key)
}
AssuranceGroup.prototype.end = function () {
var totalErrors = []
for (var i = 0; i < this.assurances.length; i++) {
var errors = this.assurances[i].end()
for (var j = 0; j < errors.length; j++) {
totalErrors.push(errors[j])
}
}
return totalErrors
}
AssuranceGroup.prototype.hasErrors = function () {
for (var i = 0; i < this.assurances.length; i++) {
if (this.assurances[i].hasErrors()) {
return true
}
}
return false
}
module.exports = AssuranceGroup
});
require.register("assurance/lib/errors.js", function(exports, require, module){
// from https://github.com/joyent/node/blob/27a91387ae79a3b7bc2b00a40a17c1b7846da55b/lib/util.js#L551
function inherits(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
})
}
/**
* Root of all the evil
*
* @param {String} message Error message
*/
function ValidationError(message) {
if (message) {
this.message = message
}
this.type = this.constructor.name
}
inherits(ValidationError, Error)
exports.ValidationError = ValidationError
function MissingParameter() {
ValidationError.call(this)
this.message = 'This parameter is required'
}
inherits(MissingParameter, ValidationError)
exports.MissingParameter = MissingParameter
function InvalidType(expected, is) {
ValidationError.call(this)
this.expected = expected
this.is = is
this.message = 'value is of type ' + is + ' but ' + expected + ' was expected'
}
inherits(InvalidType, ValidationError)
exports.InvalidType = InvalidType
function InvalidLength(min, max, is) {
ValidationError.call(this)
this.max = max
this.is = is
if (min === max) {
this.message = 'length must be less than ' + this.max
}
else {
this.min = min
this.message = 'length must be between ' + this.min + ' and ' + this.max
}
}
inherits(InvalidLength, ValidationError)
exports.InvalidLength = InvalidLength
function InvalidEmail(val) {
ValidationError.call(this)
this.message = val + ' is not a valid email'
}
inherits(InvalidEmail, ValidationError)
exports.InvalidEmail = InvalidEmail
function NotAnOption(val, arr) {
ValidationError.call(this)
this.message = val + ' was not expected'
this.expected = arr
this.is = val
}
inherits(NotAnOption, ValidationError)
exports.NotAnOption = NotAnOption
function InvalidValue(message, is) {
ValidationError.call(this)
this.message = message
this.is = is
}
inherits(InvalidValue, ValidationError)
exports.InvalidValue = InvalidValue
function ParamNotExpected(message) {
ValidationError.call(this)
this.message = message
}
inherits(ParamNotExpected, ValidationError)
exports.ParamNotExpected = ParamNotExpected
});
require.register("assurance/lib/sanitizers.js", function(exports, require, module){
var errors = require('./errors')
var checkNaN = Number.isNaN || isNaN
exports.toInt = function (val) {
if (typeof val === 'number') {
return Math.floor(val)
}
var newVal = parseInt(val, 10)
if (checkNaN(newVal)) {
return new errors.InvalidType('integer', typeof val)
}
return newVal
}
exports.toFloat = function (val) {
if (typeof val === 'number') {
return
}
var newVal = parseFloat(val)
if (checkNaN(newVal)) {
return new errors.InvalidType('float', typeof val)
}
return newVal
}
exports.trim = function (val) {
return val.trim()
}
exports.toUpperCase = function (val) {
return val.toUpperCase()
}
exports.toLowerCase = function (val) {
return val.toLowerCase()
}
});
require.register("assurance/lib/singleton.js", function(exports, require, module){
var Assurance = require('./Assurance')
var assurance = new Assurance()
module.exports = function singleton(object, onlyFields, alias) {
return assurance.restart(object, onlyFields, alias)
}
module.exports.extend = function (type, name, fn) {
if (typeof name === 'function') {
fn = name
name = fn.name
}
if (type === 'validator') {
Assurance.prototype[name] = Assurance.runValidator(fn)
}
else if (type === 'sanitizer') {
Assurance.prototype[name] = Assurance.runSanitizer(fn)
}
else {
throw new Error('unknown type ' + type)
}
}
});
require.register("assurance/lib/validators.js", function(exports, require, module){
var errors = require('./errors')
exports.matches = function (val, regex) {
if (!regex.test(val)) {
return new errors.InvalidValue('value must match ' + regex, val)
}
}
exports.len = function (val, min, max) {
var len = val.length
if (arguments.length === 2) {
max = min
min = max
if (len > max) {
return new errors.InvalidLength(min, max, len)
}
}
else {
if ((len > max) || (len < min)) {
return new errors.InvalidLength(min, max, len)
}
}
}
exports.isInt = function (val) {
if (val % 1 !== 0) {
return new errors.InvalidValue('value must be an integer', val)
}
}
// see http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
var EMAIL_REGEX =
'^(([^<>()[\\]\\\.,;:\\s@\\"]+(\\.[^<>()[\\]\\\.,;:\\s@\\"]+)*)|(\\".+\\"))@((\\[[0-9]' +
'{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]' +
'{2,}))$'
EMAIL_REGEX = new RegExp(EMAIL_REGEX)
exports.isEmail = function (val) {
var type = typeof val
if (type !== 'string') {
return new errors.InvalidType('string', type)
}
if (!EMAIL_REGEX.test(val)) {
return new errors.InvalidEmail(val)
}
}
exports.oneOf = function (val, index) {
if (Array.isArray(index)) {
if (index.indexOf(val) === -1) {
return new errors.NotAnOption(val, index)
}
}
else {
if (!index[val]) {
return new errors.NotAnOption(val, Object.keys(index))
}
}
}
exports.required = function (val) {
if ((typeof val === 'undefined') || (val === null)) {
return new errors.MissingParameter()
}
}
exports.is = function (val, expectedType) {
var type = typeof val
var expectedButNotPresent =
(expectedType !== 'undefined') &&
(expectedType !== null) &&
(type === 'undefined' || val === null)
if (expectedButNotPresent) {
return new errors.MissingParameter()
}
if (expectedType === 'array') {
if (Array.isArray(val)) {
return
}
else {
return new errors.InvalidType(expectedType, type)
}
}
if (type !== expectedType) {
return new errors.InvalidType(expectedType, type)
}
}
exports.gt = function (val, gtVal) {
if (!(val > gtVal)) {
return new errors.InvalidValue('expected a value greater than ' + gtVal, val)
}
}
exports.lt = function (val, ltVal) {
if (!(val < ltVal)) {
return new errors.InvalidValue('expected a value less than ' + ltVal, val)
}
}
exports.max = function (val, maxVal) {
if (!(val <= maxVal)) {
return new errors.InvalidValue('must be at most ' + maxVal, val)
}
}
exports.min = function (val, minVal) {
if (!(val >= minVal)) {
return new errors.InvalidValue('must be at least ' + minVal, val)
}
}
exports.equals = function (val, otherVal) {
if (val !== otherVal) {
return new errors.InvalidValue('value must equal ' + otherVal, val)
}
}
exports.notEquals = function (val, otherVal) {
if (val === otherVal) {
return new errors.InvalidValue('value must not equal ' + otherVal, val)
}
}
exports.consistsOf = function (val, index) {
var i
if (Array.isArray(index) || typeof index === 'string') {
for (i = 0; i < val.length; i++) {
if (index.indexOf(val[i]) === -1) {
return new errors.InvalidValue('didnt expect value to contain ' + val[i], val)
}
}
}
else {
for (i = 0; i < val.length; i++) {
if (!index[val[i]]) {
return new errors.InvalidValue('didnt expect value to contain ' + val[i], val)
}
}
}
}
// TODO: unicode support
exports.isLowerCase = function (val) {
if (/[A-Z]/.test(val)) {
return new errors.InvalidValue('must contain only lower case characters', val)
}
}
// TODO: unicode support
exports.isUpperCase = function (val) {
if (/[a-z]/.test(val)) {
return new errors.InvalidValue('must contain only upper case characters', val)
}
}
exports.contains = function (val, elem) {
if (val.indexOf(elem) === -1) {
return new errors.InvalidValue('must contain ' + elem, val)
}
}
});
require.alias("danmilon-object.keys-shim/index.js", "assurance/deps/object.keys-shim/index.js");
require.alias("danmilon-object.keys-shim/index.js", "assurance/deps/object.keys-shim/index.js");
require.alias("danmilon-object.keys-shim/index.js", "danmilon-object.keys-shim/index.js");
require.alias("yields-isarray/index.js", "assurance/deps/isArray/index.js");
require.alias("assurance/lib/index.js", "assurance/index.js");
if (typeof exports == "object") {
module.exports = require("assurance");
} else if (typeof define == "function" && define.amd) {
define(require("assurance"));
} else {
window["assurance"] = require("assurance");
}})();