UNPKG

node-input-validator

Version:

validation library for nodejs, inspired by laravel.

501 lines (415 loc) 10.9 kB
/* eslint-disable no-restricted-syntax */ const rules = require('./rules/index'); const postRules = require('./postRules/index'); const messages = require('./messages/index'); const { applyRules, implicitRules, applyPostRules } = require('./validator'); const empty = require('./lib/empty'); // const filters = require('./filters/index'); const messageParser = require('./lib/messageParser'); /** * @class Validator */ class Validator { /** * @constructor * @param {*} inputs * @param {*} validationsRules * @param {*} customMessages */ constructor(inputs, validationsRules, customMessages = {}) { // errors collections this.errors = {}; // validations collection this.validations = {}; // filters collection this.filters = {}; // default language this.lang = messages.defaultLang; // post validations collection this.postValidations = []; // inputs collection this.inputs = inputs; // // for top level array support // if (Array.isArray(inputs)) { // this.inputs = { ...inputs }; // } // filter inputs collection this.filterInputs = {}; // validation messages for field this.customMessages = customMessages; this.hasCustomMessages = false; if (Object.keys(this.customMessages).length) { this.hasCustomMessages = true; } // parse rules this.parseRules(validationsRules); this.attributeNames = {}; } /** * set attributes nice names * @param {*} niceNames */ setAttributeNames(niceNames) { this.attributeNames = niceNames; } /** * make validator for arrya rules * @param {*} inputs * @param {*} validationRules * @param {*} customMessages * @return {Validator} */ static make(inputs, validationRules, customMessages = {}) { const v = new Validator(inputs, {}, customMessages); v.makeValidationsFromArray(validationRules); return v; } /** * create validator * @param {*} validationRules * @param {*} customMessages * @return {Validator} */ /* istanbul ignore next */ static create(validationRules, customMessages = {}) { /* istanbul ignore next */ return new Validator({}, rules, customMessages); } /** * apply rules on custom inputs * @param {*} inputs */ async apply(inputs) { /* istanbul ignore next */ const v = new Validator(inputs, {}); /* istanbul ignore next */ v.validations = this.validations; /* istanbul ignore next */ v.postValidations = this.postValidations; /* istanbul ignore next */ return v; } /** * check for validation fails * @return {Promise} */ async fails() { return !(await this.check()); } /** * check if validation passes * @return {Promise} */ passes() { return this.check(); } /* istanbul ignore next */ /** * set before/after filters * @param {*} filters * @param {*} filterInputs */ static filter(filters, filterInputs) { /* istanbul ignore next */ this.filters = filters; /* istanbul ignore next */ this.filterInputs = filterInputs; } /** * set default language for session only * @param {*} lang */ setLang(lang) { this.lang = lang; } /* istanbul ignore next */ /** * check if given value is empty or not * @param {*} value * @return {boolean} */ isEmpty(value) { return empty(value); } /** * add error * @param {string} key * @param {string} rule * @param {string} message */ addError(key, rule, message) { this.errors[key] = { message, rule, }; } /** * add post rule * * post rule is applied to whole input and is used to check constraints * across multiple fields * * @param {*} rule */ addPostRule(rule) { if (typeof rule === 'function') { this.postValidations.push({ rule: 'function', params: rule, values: this.inputs, }); return; } rule = rule.split(':', 2); const ruleName = rule[0]; const ruleFields = rule[1].split(','); // there always be a list of fields // eslint-disable-next-line no-return-assign const values = ruleFields.reduce((acc, field) => { acc[field] = this.parseKey(field, this.inputs); return [acc[field], acc]; }, {}); this.postValidations.push({ rule: ruleName, params: ruleFields, values, }); } /** * add set of post rules * * @param {string[]} postRulesObj */ addPostRules(postRulesObj) { postRulesObj.map((rule) => this.addPostRule(rule)); } /** * validate inputs * @return {Promise.<boolean>} */ async check() { const validations = []; // console.log(this.validations); for (const i in this.validations) { if (this.validations.hasOwnProperty(i)) { validations.push(this.evaluteInputs(this.validations[i])); } } for (const j in this.postValidations) { if (this.postValidations.hasOwnProperty(j)) { validations.push(this.evaluteInputsPostValidation(this.postValidations[j])); } } if (validations.length) { await Promise.all(validations); } return !((this.errors && Object.keys(this.errors) && Object.keys(this.errors).length)); } /** * validate input against rule * @param {*} field * @return {Promise.<void>} */ async evaluteInputs(field) { if (field.rules.length) { await applyRules(field, this); } } /** * validate input as a whole against post rule * @param {*} rule * @return {Promise.<void>} */ async evaluteInputsPostValidation(rule) { await applyPostRules(rule, this); } /** * split by dot * @param {string} key * @param {*} data * @return {*} */ parseKey(key, data) { let value; // let self = this; const keySplit = key.split('.').filter((e) => e !== ''); // console.log('Key Split', keySplit); keySplit.map((item) => { if (typeof value === 'undefined') { value = data && data[item]; } else { value = value[item]; } }); if (value === null) { return value; } switch (typeof value) { case 'string': value = value.trim(); break; case 'undefined': value = ''; break; } return value; } /** * parse input value * @param {*} field * @param {*} multiple * @return {*} */ inputVal(field, multiple = false) { // let val = this.inputs[field] || ''; if (multiple === true) { this.parseKey(field, this.inputs); } return this.parseKey(field, this.inputs); } /** * * @param {*} rulesToParse * @return {*} */ parseRules(rulesToParse) { if (!rulesToParse || !Object.keys(rulesToParse).length) { return; } let rsplit; let argsplit; let args; let field; // here r is field name for (field in rulesToParse) { if (!rulesToParse.hasOwnProperty(field)) { continue; } // console.log('rules->', r); let multipleFields = -1; if (field === '*') { this.addPostRules(rulesToParse[field].split('|')); return; } // console.log('in loop', field); if (!this.validations[field]) { multipleFields = field.indexOf('*'); this.validations[field] = { field, multiple: (multipleFields >= 0), path: field.split('.'), required: false, nullable: false, rules: [], }; } rsplit = rulesToParse[field].toString().split('|'); let rs; for (rs in rsplit) { if (!rsplit.hasOwnProperty(rs)) { continue; } argsplit = rsplit[rs].split(':'); if (typeof argsplit[1] !== 'undefined') { args = argsplit[1].split(','); this.rule = { rule: argsplit[0], args: (args.length > 1) ? args : args[0] }; } else { this.rule = { rule: argsplit[0] }; } if (this.rule.rule === 'nullable') { this.validations[field].nullable = true; } this.populateRule(field); } } // console.log(JSON.stringify(this.validations, null, 2)); } /** * make rules from array * @param {*} arrayRules * @return {*} */ makeValidationsFromArray(arrayRules) { if (!arrayRules || !Object.keys(arrayRules).length) { return; } let field; let fieldRule; // here r is field name // eslint-disable-next-line no-restricted-syntax for (field in arrayRules) { if (!arrayRules.hasOwnProperty(field)) { continue; } // const fieldWithIndex = []; let multipleFields = 0; if (field === '*') { this.addPostRules(arrayRules[field]); return; } // console.log('in loop', field); if (!this.validations[field]) { multipleFields = field.indexOf('*'); this.validations[field] = { field, multiple: (multipleFields > 0), path: field.split('.'), required: false, rules: [], }; } // eslint-disable-next-line no-restricted-syntax for (fieldRule of arrayRules[field]) { let args = []; if (fieldRule instanceof Array) { args = fieldRule; fieldRule = fieldRule.splice(0, 1).toString(); } this.rule = { rule: fieldRule, args: (args.length === 1) ? args[0] : args }; this.populateRule(field); } } } /** * re-arrange rules * @param{*} field */ populateRule(field) { // console.log('filed and rule in populate rules', field, rule); if (implicitRules.indexOf(this.rule.rule) >= 0) { this.validations[field].rules.unshift(this.rule); this.validations[field].required = true; } else { this.validations[field].rules.push(this.rule); } this.rule = {}; } /** * * @param {*} rule * @param {*} field * @param {*} value * @param {*} args * @return {string} */ parseMessage(rule, field, value, args) { return messageParser({ V: this, rule, field, value, args, }); } /** * this is only used in testing * @param {*} rule * @param {*} field * @param {*} value * @param {*} args * @return {*} */ parseExistingMessageOnly(rule, field, value, args) { /* istanbul ignore next */ return messageParser({ V: this, rule, field, value, args, defaultMessage: `Message is missing for rule ${rule}`, }); } } module.exports = Validator; module.exports.rules = rules; module.exports.postRules = postRules;