UNPKG

the

Version:

Lightweight schemas for everywhere, whenever.

235 lines (210 loc) 5.47 kB
'use strict' var isType = require('is-typeof') var merge = require('./utils').merge var ValidationError = require('./validationError') var the = The.prototype var not = Not.prototype /** * @class The * @param {Object} meta - Meta data to attach to errors. */ function The (meta) { if (!(this instanceof The)) return new The(meta) this.not = new Not() this._meta = meta || {} this._tests = [] this.not._ = this._ = this } /** * @private * @description * Prototype that holds all negated tests. */ function Not () {} /** * @private * @description * Store metadata information about test. */ the._meta = null /** * @private * @description * A collection of tester functions. */ the._tests = null /** * @private * @description * Indicates if null values should be tested. */ the._required = true /** * @alias #test * @description * Run all of the attached tests against a provided value. * ```javascript * The().string().test('') // -> true * The().string().test(1) // -> ValidationError * ``` * * @param {Any} value - The value to test. * @return {(ValidationError|Boolean)} */ the.test = function (value) { var error = new ValidationError() var tests = this._tests if (value == null) { if (this._required) { error.add(merge({ type: 'required', value: value }, this._meta)) } } else { for (var test, i = tests.length; i--;) { test = tests[i] try { if (test.call(error, value) !== true) throw new Error() } catch (_) { error.add(merge(test.config, { value: value }, this._meta)) } } } return isType.empty(error.errors) || error.toError() } /** * @alias #assert * @description * Run all of the attached tests against a provided value and throws if any fail. * ```javascript * The().string().assert('') // -> true * The().string().assert(1) // -> throw ValidationError * ``` * * @param {Any} value - The value to test. * @throw {ValidationError} All tests must pass on the value. * @return {Boolean} */ the.assert = function (value) { var result = this.test(value) if (result !== true) throw result return true } /** * @alias #not * @description * Stores negated versions of all tests. * ```javascript * The().not.string().test(1) // -> true * The().not.string().test('1') // -> ValidationError * ``` */ the.not = null /** * @alias #required * @description * Fail tests with nullish values. (This is the default behaviour). * ```javascript * The().required(false).test(null) // -> true * The().required().test(null) // -> ValidationError * ``` * * @param {Boolean} [isRequired] - Indicate if the tests are required. */ the.required = function (isRequired) { if (isRequired == null) isRequired = true this._required = Boolean(isRequired) return this } /** * @alias #optional * @description * Pass tests with nullish values. * ```javascript * The().optional().test(null) // -> true * The().optional(false).test(null) -> // ValidationError * ``` * * @param {Boolean} [isOptional] - Indicate if the tests are optional. */ the.optional = function (isOptional) { if (isOptional == null) isOptional = true this._required = !isOptional return this } /** * @alias #merge * @description * Merge other tests onto the current instance. * ```javascript * The().string().merge(The().startWith('hi')).test('hi123') // -> true * The().string().merge(The().startWith('hi')).test('123hi') // -> ValidationError * ``` * * @param {The} the - The The instance to merge with. */ the.merge = function (the) { if (!(the instanceof The)) throw new TypeError('Can only merge other The instances.') this._tests = this._tests.concat(the._tests) return this } /** * @alias #clone * @description * Make a copy of the current tests. */ the.clone = function () { var the = new The(this._meta) return the.merge(this) } /** * @static * @description * Adds new custom tests, with their negated (#not) counter parts. * ```javascript * The.extend({ any: funciton (value) { return true; } }) * The().any().test(1) // -> true * ``` * * @param {Object} tests - The tests to add. */ The.extend = function (tests) { for (var key in tests) { var test = tests[key] the[key] = prepareTest(key, test) if (key !== 'try' && key !== 'eval') { not[key] = prepareTest('not.' + key, test, true) } } return The } /** * @private * @description * Utility to build a test that will work with The and create its (#not) counter part. * * @param {String} type - the name of the test. * @param {Function} test - the tester function. * @param {Boolean} negated - is this the negated version? */ function prepareTest (type, test, negated) { return function () { var options = new Array(arguments.length) for (var i = options.length; i--;) options[i] = arguments[i] var config = { type: type } if (options.length) config.options = options var tester = test.apply(config, options) if (typeof tester !== 'function') { throw new TypeError('Invalid test (testers must return a function).') } var fn = negated ? function (value) { return !tester.call(this, value) } : tester fn.config = config this._._tests.push(fn) return this._ } } module.exports = The.default = The .extend(require('./test/type')) .extend(require('./test/other')) .extend(require('./test/regex')) .extend(require('./test/helper'))