chain-able
Version:
interfaces that describe their intentions.
277 lines (256 loc) • 7.14 kB
JavaScript
/**
* @since 4.0.0 <- moved out of the store, into scoped
* @since 1.0.0
* @desc library of validators to use by name
* @modifies this.validators
* @param {Object} validators
*/
const ChainedMap = require('../../ChainedMapBase')
const ENV_DEBUG = require('../env/debug')
const is = require('../is')
const isArray = require('../is/array')
const isReal = require('../is/real')
const isString = require('../is/string')
const isFunction = require('../is/function')
const dopemerge = require('../dopemerge')
const camelCase = require('../camel-case')
const not = require('../conditional/not')
const and = require('../conditional/and')
const or = require('../conditional/or')
const all = require('../conditional/all')
let validators = new ChainedMap()
// eslint-disable-next-line
const stripArithmeticSymbols = x => x.replace(/[?\[\]!\|]/g, '')
const escapedKey = x => camelCase('is-' + x)
const enummy = enums => x => enums === x || enums.includes(x)
// @TODO: .remap!!!
// @TODO: can use these to return noops with error logging on development
const get = key =>
validators.get(key) || validators.get(escapedKey(key)) || enummy(key)
const has = key => validators.has(key) || validators.get(escapedKey(key))
const set = (key, value) => validators.set(key, value)
const doesNotHave = not(has)
/**
* @desc add custom types for validation
* @category types
* @category schema
* @types schema
*
* @since 4.0.0 <- used with schema, used in method chain
* @since 3.0.0 <- took out
* @since 1.0.0
*
* @param {Object} types custom Types
*
* @see deps/validators/validatorFactory
*
* @example
*
* addTypes({yaya: x => typeof x === 'string'})
*
* const chain = new Chain().methods('eh').type('yaya').build()
*
* chain.eh('good')
* //=> chain
*
* chain.eh(!!'throws')
* //=> TypeError(false != {yaya: x => typeof x === 'string'})
*
* @example
*
* const custom = {}
* custom.enums = enums => x => enums.includes(x)
* custom['*'] = x => true
* addTypes(custom)
* //-> void
*
* new Chain().methods('eh').type('*').build().eh
* //=> validateType(custom['*'])
*
*/
const addTypes = types =>
validators.from(dopemerge(validators.entries(), types))
addTypes(is)
// ----
// @NOTE: putting these as functions increased size 20 bytes: worth it
// ----
// @SIZE: another 10bytes for these fns
const isNotRealOrIsEmptyString = and(not(isReal), x => x === '')
// const isArrayOf = predicate => x => isArray(x) && x.every(predicate)
const isArrayOf = predicate => and(isArray, all(predicate))
const includesAndOr = x => x.includes('|') || x.includes('&')
/**
* @memberOf schema
* @category types
*
* @param {string} fullKey a key with `|` and/or '&'
* @return {Function} validator
*
* @example
*
* const isStringOrNumber = typeListFactory('string|number')
*
* isStringOrNumber(1)
* //=> true
* isStringOrNumber('one')
* //=> true
* isStringOrNumber(Object)
* //=> false
*
*/
function typeListFactory(fullKey) {
// already have it
if (has(fullKey)) {
return get(fullKey)
}
// get all types
let orTypes = fullKey.split('|')
let andTypes = fullKey.split('&')
// ensure we have all validators - sets up conditionals
for (let v = 0; v < orTypes.length; v++) {
builder(orTypes[v])
}
// go through all valid options, if any are true, good to go
set(fullKey, x => {
for (let v = 0; v < orTypes.length; v++) {
if (get(orTypes[v])(x)) {
return true
}
}
return false
})
return get(fullKey)
}
// @TODO how to iterate properly with the bitwise fn + AND
// add another param? ignore overly complex |& things? just allow 1?
// just show how to use these shorthand fn builders
/**
* @desc transform arithmetic strings into types
* @since 4.0.0-alpha.1
* @category types
*
* @param {Matchable} fullKey arithmetic type key
* @return {Matchable} function to match with, with .inspect for easy debugging
*
* @types schema
* @test typed
* @test schema
* @see is
* @todo coercing values to certain types: arithmeticTypeFactory('<value>')
*
* @example
*
* arithmeticTypeFactory('?string')
* //=> x => !isReal(x) || isString(x)
*
* @example
*
* arithmeticTypeFactory('?string|string[]')
* //=> x => isString(x) || isArrayOf(isString)(x)
*
* @example
*
* arithmeticTypeFactory('!string')
* //=> x => not(isString)(x)
*
* @example
*
* types.addTypes({star: x => true})
* arithmeticTypeFactory('object|function|star')
* //=> x => isObj(x) || isFunction(x) || isStar(x)
*
* @example
*
* arithmeticTypeFactory('===')
* //=> x => (['===']).includes(x)
*/
function arithmeticTypeFactory(fullKey) {
const key = stripArithmeticSymbols(fullKey)
let fn = get(key)
const optionalType = `?${key}`
const typeOrArrayOrType = `${key}[]`
const notType = `!${key}`
const isValidOrNotRealOrEmptyStr = or(fn, isNotRealOrIsEmptyString)
const isValidOrArrayOfValid = or(fn, isArrayOf(fn))
if (doesNotHave(optionalType)) {
set(optionalType, isValidOrNotRealOrEmptyStr)
}
if (doesNotHave(typeOrArrayOrType)) {
set(typeOrArrayOrType, isValidOrArrayOfValid)
}
if (doesNotHave(notType)) {
set(notType, not(fn))
}
return get(fullKey)
}
// ----
// ; function split
// ----
// v- annoying on comments with ifs
/* prettier-ignore */
/**
* @desc @pattern @builder -> builds using multiple factories depending on conditons
* or abstractFactory whatever
* opinionated: if it's a function, it's a validator...
*
* @category types
* @since 4.0.0
* @param {string | Function | Primitive} fullKey arithmetic key to the validator
* @return {Function} validator
*
* @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Default_parameters
* @NOTE if/else is for uglifying ternaries, even though else if is not needed
* @NOTE if key is number, iterating the array
*
* @example
*
* // functionType
* const isString = x => typeof x === 'string'
* builder(isString)
* // => isString
*
* @example
*
* // stringType (built in, or custom-keyed validator, or eqeqeq)
* builder('string')
* // => isString
*
* const enummy = builder('enum')
* // => x => ['enum'].includes(x)
*
* @example
*
* // arithmeticType
* builder('string|string[]')
* // => isString || isArrayOf(isString)
*
*/
function builder(fullKey) {
if (isFunction(fullKey)) {
/* istanbul ignore next: dev */
if (ENV_DEBUG) {
console.log('functionType', {fullKey})
}
return fullKey
}
else if (isString(fullKey) && includesAndOr(fullKey)) {
/* istanbul ignore next: dev */
if (ENV_DEBUG) {
console.log('andOrType', {fullKey})
}
return typeListFactory(fullKey)
}
else {
/* istanbul ignore next: dev */
if (ENV_DEBUG) {
console.log('arithmeticType', {fullKey}, arithmeticTypeFactory(fullKey))
}
return arithmeticTypeFactory(fullKey)
}
}
builder.has = has
builder.get = get
builder.set = set
builder.addTypes = addTypes // was merge
builder.map = validators
module.exports = builder