UNPKG

obey

Version:

Data modelling and validation library

197 lines (187 loc) 7.92 kB
/* * Copyright (c) 2015 TechnologyAdvice */ /* eslint no-console: 0, consistent-return: 0 */ const dot = require('dot-object') const jexl = require('jexl') const plugins = require('./plugins') const cloneDeep = require('lodash/cloneDeep') const validators = { /** * Validator default method, used by model * @param {Object} def The property configuration * @param {*} value The value being validated */ default: function(def, value) { if (/^(number|boolean)$/.test(def.type)) { if (value === 0 || value === false) return value } return value || cloneDeep(def.default) }, /** * Validator allowed method, used by model * @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 */ allow: function(def, value, key, errors) { const type = 'allow' const sub = typeof def.allow === 'object' && !Array.isArray(def.allow) ? Object.keys(def.allow) : def.allow const subIsArray = Array.isArray(sub) if ((subIsArray && sub.indexOf(value) === -1) || (!subIsArray && sub !== value)) { if (value === '' && def.empty) return errors.push({ type, sub, key, value, message: `Value '${value}' is not allowed` }) } }, /** * Validator min method, used by model * @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 */ min: function(def, value, key, errors) { const type = 'min' const sub = def.min if ((Array.isArray(value) || typeof value === 'string') && value.length < def.min) { errors.push({ type, sub, key, value, message: `Length must be greater than or equal to ${def.min}` }) } else if (typeof value === 'number' && value < def.min) { errors.push({ type, sub, key, value, message: `Value must be greater than or equal to ${def.min}` }) } }, /** * Validator max method, used by model * @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 */ max: function(def, value, key, errors) { const type = 'max' const sub = def.max if ((Array.isArray(value) || typeof value === 'string') && value.length > def.max) { errors.push({ type, sub, key, value, message: `Length must be less than or equal to ${def.max}` }) } else if (typeof value === 'number' && value > def.max) { errors.push({ type, sub, key, value, message: `Value must be less than or equal to ${def.max}` }) } }, /** * Validator requiredIf method, used by model * @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} data The full initial data object */ requiredIf: function(def, value, key, errors, data) { const type = 'requiredIf' const sub = def.requiredIf if (typeof sub === 'object') { const field = Object.keys(sub)[0] const fieldArr = Array.isArray(sub[field]) ? sub[field] : [ sub[field] ] fieldArr.some(val => { /* istanbul ignore else */ if (dot.pick(field, data) === val && value === undefined) { errors.push({ type, sub, key, value, message: `Value required by existing '${field}' value` }) return true } }) } else if (dot.pick(sub, data) !== undefined && value === undefined) { errors.push({ type, sub, key, value, message: `Value required because '${sub}' exists` }) } }, /** * Alias for requiredIf */ requireIf: function(def, value, key, errors, data) { console.log('-----\nObey Warning: `requireIf` should be `requiredIf`\n-----') def.requiredIf = def.requireIf delete def.requireIf validators.requiredIf(def, value, key, errors, data) }, /** * Validator requiredIfNot method, used by model * @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} data The full initial data object */ requiredIfNot: function(def, value, key, errors, data) { const type = 'requiredIfNot' const sub = def.requiredIfNot if (typeof sub === 'object') { const field = Object.keys(sub)[0] const fieldArr = Array.isArray(sub[field]) ? sub[field] : [ sub[field] ] fieldArr.some(val => { /* istanbul ignore else */ if (dot.pick(field, data) !== val && value === undefined) { errors.push({ type, sub, key, value, message: `Value required because '${field}' value is not one specified` }) return true } }) } else if (dot.pick(sub, data) === undefined && value === undefined) { errors.push({ type, sub, key, value, message: `Value required because '${sub}' is undefined`}) } }, /** * Alias for requiredIfNot */ requireIfNot: function(def, value, key, errors, data) { console.log('-----\nObey Warning: `requireIfNot` should be `requiredIfNot`\n-----') def.requiredIfNot = def.requireIfNot delete def.requireIfNot validators.requiredIfNot(def, value, key, errors, data) }, /** * Validator equalTo method, used by model * @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} data The full initial data object */ equalTo: function(def, value, key, errors, data) { const type = 'equalTo' const sub = def.equalTo if (dot.pick(sub, data) !== value) { errors.push({ type, sub, key, value, message: `Value must match ${sub} value`}) } }, /** * Validator jexl method, used by model * @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} data The full initial data object */ jexl: (def, value, key, errors, data) => { const type = 'jexl' const sub = Array.isArray(def.jexl) ? def.jexl : [ def.jexl ] const promises = sub.map((obj) => { const { expr, message = 'Value failed Jexl evaluation' } = obj const instance = plugins.lib.jexl || jexl return instance.eval(expr, { root: data, value }) .then(val => { if (!val) errors.push({ type, sub: obj, key, value, message }) }) .catch(() => { errors.push({ type, sub: obj, key, value, message }) }) }) Promise.all(promises) } } module.exports = validators