UNPKG

indicative-compiler

Version:

Indicative compiler to compile parsed schema into highly optimized functions

220 lines (219 loc) 8.14 kB
"use strict"; /** * @module compiler/validator */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * indicative-compiler * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ const isobject_1 = __importDefault(require("isobject")); const lodash_get_1 = __importDefault(require("lodash.get")); /** * Runs a series of validations on a given field. This class is feeded with the * computed nodes generated via [[TreeWalker]]. */ class ValidationsRunner { constructor(field, type, dotPath, rules, validations, fieldMessages, genericMessages) { this.field = field; this.type = type; this.dotPath = dotPath; this.fieldMessages = fieldMessages; this.genericMessages = genericMessages; /** * We toggle this flag then creating the `validations` object */ this.async = false; /** * Collection of validations to be executed on a given field. */ this.validations = []; /** * Base pointer to this field. When field is inside an * array, then we need to re-compute the pointer * based upon the current index in which this * field is validated. * * However, we don't mutate this field. */ this.pointer = this.dotPath.concat(this.field).join('.'); this.computeValidations(validations, rules); } /** * Creating a list of validation functions to be executed as per * the defined rules. */ computeValidations(validations, rules) { this.validations = rules.map((rule) => { const validation = validations[rule.name]; /** * Raise exception when validation implementation for a * given rule is missing. */ if (!validation) { throw new Error(`${rule.name} is not a registered as a validation`); } /** * The validation node must have a `validate` function. */ if (typeof (validation.validate) !== 'function') { throw new Error(`${rule.name} is missing validate function`); } /** * Mutate args when `compile` function is defined. It is a way to * normalize arguments before the validation process kicks in. */ if (typeof (validation.compile) === 'function') { rule.args = validation.compile(rule.args); } /** * Set the flag to `true` when one or more validations are `async`. * This tells the consumer of [[ValidationsRunner]] class to make * use of `execAsync` over `exec`. All done for performance. */ if (validation.async) { this.async = true; } return { rule: rule, fn: validation.validate, async: validation.async }; }); } /** * Returns a fresh data copy by copying some of the values from the actual * data and then mutating the `tip` and `pointer`. The tip and pointer * are mutated so that the validation function receives the closest * object from the pointer, resulting in performant code. */ getDataCopy(data) { const tip = this.dotPath.length ? lodash_get_1.default(data.tip, this.dotPath) : data.tip; /** * Prefix array pointer and current index, when this field is part * of an array. * Also do not append the pointer when pointer is `::tip::` */ const pointer = data.arrayPointer ? (this.pointer === '::tip::' ? `${data.arrayPointer}.${data.currentIndex}` : `${data.arrayPointer}.${data.currentIndex}.${this.pointer}`) : this.pointer; /** * Updating the tip and pointer */ return Object.assign({}, data, { tip: this.field === '::tip::' ? { [this.field]: tip } : tip, pointer: pointer, }); } /** * Reports value to the collector when current field is a literal * node inside the tree and validation has passed */ reportValueToCollector(passed, data, collector) { if (!passed || this.type !== 'literal') { return; } collector.setValue(data.pointer, data.tip[this.field]); } /** * Reports the validation error to the collector. */ reportErrorToCollector(pointer, rule, collector, exception) { const message = exception || this.fieldMessages[rule.name] || this.genericMessages[rule.name]; collector.setError(pointer, rule, message); } /** * Executes all the validations on a given field synchronously. Run * [[ValidationsRunner.execAsync]] if want to execute asynchronously. */ exec(data, collector, config, bail = false) { const dataCopy = this.getDataCopy(data); /** * Skip validations when the parent value of this field is not * an object. The user must validate the parent to be object * seperately. */ if (!isobject_1.default(dataCopy.tip)) { return true; } let hasFailures = false; /** * Sequentially loop over all the validations. * We break the loop, when `bail=true`. */ for (let validation of this.validations) { let exception = null; let passed = true; /** * Wrapping the validation function for unexpected errors. */ try { passed = validation.fn(dataCopy, this.field, validation.rule.args, config); } catch (error) { exception = error; passed = false; } if (!passed) { hasFailures = true; this.reportErrorToCollector(dataCopy.pointer, validation.rule, collector, exception); if (bail) { break; } } } this.reportValueToCollector(!hasFailures, dataCopy, collector); return !hasFailures; } /** * Executes all the validations on a given field asynchronously. Run * [[ValidationsRunner.exec]] if want to execute synchronously. */ async execAsync(data, collector, config, bail = false) { const dataCopy = this.getDataCopy(data); /** * Skip validations when the parent value of this field is not * an object. The user must validate the parent to be object * seperately. */ if (!isobject_1.default(dataCopy.tip)) { return true; } let hasFailures = false; /** * Sequentially loop over all the validations. * We break the loop, when `bail=true`. */ for (let validation of this.validations) { let exception = null; let passed = true; try { if (validation.async) { passed = await validation.fn(dataCopy, this.field, validation.rule.args, config); } else { passed = validation.fn(dataCopy, this.field, validation.rule.args, config); } } catch (error) { passed = false; exception = error; } if (!passed) { hasFailures = true; this.reportErrorToCollector(dataCopy.pointer, validation.rule, collector, exception); if (bail) { break; } } } this.reportValueToCollector(!hasFailures, dataCopy, collector); return !hasFailures; } } exports.ValidationsRunner = ValidationsRunner;