UNPKG

obey

Version:

Data modelling and validation library

105 lines (98 loc) 3.64 kB
/* * Copyright (c) 2015 TechnologyAdvice */ const _ = require('lodash') const strategies = require('./typeStrategies') /** * Types determine and execute the appropriate validation to be performed on the * data during validation * @namespace types */ const types = { /** * @memberof types * @property {Object} Contains type strategies */ strategies, /** * Checks for and applies sub-type to definition * @memberof types * @param {Object} def The rule defintion * @returns {Object} */ checkSubType: def => { const fullType = def.type.split(':') if (fullType.length === 2) { def.type = fullType[0] def.sub = fullType[1] } else { def.sub = 'default' } return def }, /** * Sets up the `fail` method and handles `empty` or `undefined` values. If neither * empty or undefined, calls the appropriate `type` and executes validation * @memberof types * @param {Object} def The property configuration * @param {*} value The value being validated * @param {string} key The key name of the property * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array * to which any additional error objects will be added * @param {Object} initData Initial data object * @returns {*|Promise.<*>} The value if empty or undefined, check method if value requires type validation */ validate: function(def, value, key, errors, initData) { const parsedDef = types.checkSubType(def) const fail = message => { errors.push({ type: def.type, sub: def.sub, key, value, message }) } // Handle `empty` prop for string values if (def.empty && typeof value === 'string' && def.type !== 'array' && value.length === 0) { return value } // Account for stray empties const isEmptyOrUndefined = value === undefined || value === '' // Don't run if undefined on required if (def.required && isEmptyOrUndefined && !def.opts.partial) { errors.push({ type: 'required', sub: 'default', key, value, message: `Property '${key}' is required` }) return value } // Execute check return types.check({ def: parsedDef, key, value, fail, errors, initData }) }, /** * Add (or override) a type in the library * @memberof types * @param {string} name The name of the type * @param {Object|function} handler The type strategy method */ add: (name, handler) => { types.strategies[name] = _.isFunction(handler) ? { default: handler } : handler }, /** * Ensures that the strategy exists, loads if not already in memory, then ensures * subtype and returns the applied type strategy * @memberof types * @param {{def: Object, key: string, value: *, fail: function, errors: Array<{Object}>}} context A type context * @returns {Promise.<*>} Resolves with the provided data, possibly modified by the type strategy */ check: context => { if (!types.strategies[context.def.type]) { if (context.def.type.match(/[\/\\]/)) { throw new Error(`Illegal type name: ${context.def.type}`) } } // Ensure type if (!types.strategies[context.def.type]) { throw new Error(`Type '${context.def.type}' does not exist`) } // Ensure subtype if (!types.strategies[context.def.type][context.def.sub]) { throw new Error(`Type '${context.def.type}:${context.def.sub}' does not exist`) } return Promise.resolve(types.strategies[context.def.type][context.def.sub](context)) .then(res => res === undefined ? context.value : res) } } module.exports = types