UNPKG

dobo

Version:

DBMS for Bajo Framework

250 lines (236 loc) 8.2 kB
import joi from 'joi' const excludedTypes = ['object'] const excludedNames = [] /** * @typedef {string[]} TValidatorString * @property {string} 0=alphanum * @property {string} 1=base64 * @property {string} 2=case * @property {string} 3=creditCard * @property {string} 4=dataUri * @property {string} 5=email * @property {string} 6=guid * @property {string} 7=uuid * @property {string} 8=hex * @property {string} 9=hostname * @property {string} 10=insenstive * @property {string} 11=ip * @property {string} 12=isoDate * @property {string} 13=isoDuration * @property {string} 14=length * @property {string} 15=lowercase * @property {string} 16=max * @property {string} 17=min * @property {string} 18=normalize * @property {string} 19=pattern * @property {string} 20=regex * @property {string} 21=replace * @property {string} 22=token * @property {string} 23=trim * @property {string} 24=truncate * @property {string} 25=upercase * @property {string} 26=uri */ /** * @typedef {string[]} TValidatorNumber * @property {string} 0=great * @property {string} 1=less * @property {string} 2=max * @property {string} 3=min * @property {string} 4=multiple * @property {string} 5=negative * @property {string} 6=port * @property {string} 7=positive * @property {string} 8=sign * @property {string} 9=unsafe */ /** * @typedef {string[]} TValidatorBoolean * @property {string} 0=falsy * @property {string} 1=sensitive * @property {string} 2=truthy */ /** * @typedef {string[]} TValidatorDate * @property {string} 0=greater * @property {string} 1=iso * @property {string} 2=less * @property {string} 2=max * @property {string} 2=min */ /** * @typedef {string[]} TValidatorTimestamp * @property {string} 0=timestamp */ /** * @typedef {Object} TValidator * @property {TValidatorString} string * @property {TValidatorNumber} number * @property {TValidatorBoolean} boolean * @property {TValidatorDate} date * @property {TValidatorTimestamp} timestamp */ const validator = { string: ['alphanum', 'base64', 'case', 'creditCard', 'dataUri', 'domain', 'email', 'guid', 'uuid', 'hex', 'hostname', 'insensitive', 'ip', 'isoDate', 'isoDuration', 'length', 'lowercase', 'max', 'min', 'normalize', 'pattern', 'regex', 'replace', 'token', 'trim', 'truncate', 'uppercase', 'uri'], number: ['great', 'less', 'max', 'min', 'multiple', 'negative', 'port', 'positive', 'sign', 'unsafe'], boolean: ['falsy', 'sensitive', 'truthy'], date: ['greater', 'iso', 'less', 'max', 'min'], timestamp: ['timestamp'], array: ['length', 'max', 'min'] } async function buildFromDbModel (opts = {}) { const { isPlainObject, get, isEmpty, isString, keys, find, has, without } = this.app.lib._ const { fields = [], rule = {}, extFields = [] } = opts const obj = {} const { propertyType: propType } = this.app.baseClass.Dobo const refs = [] const me = this function getRuleKv (kvRule) { let key let value let columns if (isPlainObject(kvRule)) { key = kvRule.rule value = kvRule.params columns = kvRule.fields } else if (isString(kvRule)) { [key, value, columns] = kvRule.split(':') } return { key, value, columns } } async function applyFieldRules (prop, obj) { const minMax = { min: false, max: false } const rules = get(rule, prop.name, prop.rules ?? []) if (!Array.isArray(rules)) return rules for (const r of rules) { const types = validator[propType[prop.type].validator] const { key, value } = getRuleKv(r) if (keys(minMax).includes(key)) minMax[key] = true if (key === 'ref') { refs.push(prop.name) obj = joi.ref(value) continue } if (!key || !types.includes(key)) continue obj = obj[key](value) } if (refs.includes(prop.name)) return obj if (['string', 'text'].includes(prop.type)) { for (const k in minMax) { if (minMax[k]) continue if (has(prop, `${k}Length`)) obj = obj[k](prop[`${k}Length`]) } } if (!['id'].includes(prop.name) && prop.required) obj = obj.required() if (prop.values) { const values = await me.buildPropValues(prop, opts) const items = values.map(item => item.value) if (prop.type === 'array') { obj = obj.items(joi.string().valid(...items)) } else obj = obj.valid(...items) } if (prop.rulesMsg) { const msgs = {} for (const k in prop.rulesMsg) { msgs[k] = '~' + prop.rulesMsg[k] // note: to tell bajo error formatter that this is a custom error message } obj = obj.messages(msgs) } return obj } const props = [...this.properties, ...extFields] for (const p of props) { if (excludedTypes.includes(p.type) || excludedNames.includes(p.name)) continue if (opts.partial && fields.length > 0 && !fields.includes(p.name)) continue let item switch (p.type) { case 'text': case 'string': { item = await applyFieldRules(p, joi.string()) break } case 'smallint': case 'integer': item = await applyFieldRules(p, joi.number().integer()) break case 'float': case 'double': if (p.precision) item = await applyFieldRules(p, joi.number().precision(p.precision)) else item = await applyFieldRules(p, joi.number()) break case 'time': case 'date': case 'datetime': item = await applyFieldRules(p, joi.date()) break case 'timestamp': item = await applyFieldRules(p, joi.number().integer()) break case 'boolean': item = await applyFieldRules(p, joi.boolean()) break case 'array': item = await applyFieldRules(p, joi.array()) break } if (item) { if (item.$_root && !p.required) obj[p.name] = item.allow(null, '') else obj[p.name] = item } } if (isEmpty(obj)) return false for (const r of this.rules) { for (const k of without(keys(obj), ...refs)) { const prop = find(props, { name: k }) if (!prop) continue const types = validator[propType[prop.type].validator] const { key, value, columns = [] } = getRuleKv(r) if (!types.includes(key)) continue if (columns.length === 0 || columns.includes(k)) obj[k] = obj[k][key](value) } } const result = joi.object(obj) for (const k of ['with', 'xor', 'without']) { const item = get(this, `extRule.${k}`) if (item) result[k](...item) } return result } /** * Validate value against JOI model * * @method * @memberof Dobo * @async * @param {Object} value - value to validate * @param {Object} joiModel - JOI model * @param {Object} [options={}] - Options object * @param {string} [options.ns=dobo] - Scope's namespace * @param {Array} [options.fields=[]] * @param {Array} [options.extFields=[]] * @param {Object} [options.params={}] - Validation parameters. See {@tutorial config} and {@link https://joi.dev/api/?v=17.13.3#anyvalidateasyncvalue-options|JOI validate's options} * @returns {Object} */ async function validate (body, joiModel, opts = {}) { const { defaultsDeep } = this.app.lib.aneka const { isEmpty } = this.app.lib._ let { extFields = [], params = {}, partial } = opts params = defaultsDeep(params, this.app.dobo.config.validationParams) const { rule = {} } = params delete params.rule const fields = partial ? Object.keys(body) : [...opts.fields] if (isEmpty(joiModel)) joiModel = await buildFromDbModel.call(this, { fields, rule, extFields, partial }) if (!joiModel) return { value: body } try { return await joiModel.validateAsync(body, params) } catch (err) { const payload = { details: err.details, statusCode: 422, code: 'DB_VALIDATION', model: this.name } if (err.values) payload.values = err.values throw this.plugin.error('validationError', payload) } } export default validate