UNPKG

altheia-async-data-validator

Version:

A very simple, fast and customizable async data validator

202 lines (201 loc) 7.26 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Validator = void 0; const isPlainObject_1 = __importDefault(require("lodash/isPlainObject")); const isEqual_1 = __importDefault(require("lodash/isEqual")); const validators_1 = require("./validators"); const createTest_1 = require("./utils/createTest"); // Return an object and call a callback if needed const returnOrCallback = (result, callback) => { if (callback) { callback(result); } return result; }; /** * Validator class * new Validator({ foo: 'bar' }); */ class Validator { /** * Constructor * @param {object} schema * @param {object} inst An Altheia instance */ constructor(schema, inst) { this.inst = inst; this.isValidator = 1; this.validated = false; this._schema = {}; this._body = {}; this._errors = []; this._errorsRaw = []; this._confirm = []; this._options = { required: false, unknown: false, flatten: false, }; this.schema(schema); } /** * Clone a validator * @return {Validator} */ clone() { const clone = new Validator(this._schema, this.inst); clone.options(this._options); clone._confirm = [...this._confirm]; return clone; } /** * Assign the body to validate * * @param {object} body * @return {this} */ body(body) { this._body = Object.assign({}, body); return this; } /** * Declare the schema that describe the body() * * @param {object} schema * @return {this} */ schema(schema) { if (!(0, isPlainObject_1.default)(schema)) { throw new Error('schema should be object'); } this._schema = schema; return this; } /** * Declare options to change the defaults behaviour of the Validator * * @param {object} options * @return {this} */ options(options) { if (!(0, isPlainObject_1.default)(options)) { throw new Error('schema should be object'); } this._options = Object.assign(this._options, options); return this; } /** * Format any Error Array returned by a test * @param {object} error * @param {string} label * @return {ValidatorErrorFormatted} Formatted error */ formatError(error, label) { return this.inst.formatError(error, label); } flatten(bag, error) { if (error.errors) { return error.errors.reduce(this.flatten, bag); } bag.push(error); return bag; } validate(...params) { return __awaiter(this, void 0, void 0, function* () { if (this.validated) { throw new Error('Already validated, please use .clone() to validate a different body'); } let callback; if (params.length > 0) { if (typeof params[0] === 'function') { // eslint-disable-next-line prefer-destructuring callback = params[0]; } else { this.body(params[0]); // eslint-disable-next-line prefer-destructuring callback = params[1]; } } this.validated = true; const errors = []; // Early return if unknown keys if (this._options.unknown === false) { const only = yield new validators_1.TypeObject() .in(Object.keys(this._schema), { oneErrorPerKey: true }) .validate(this._body); if (typeof only !== 'boolean' && only.result && only.result.errors) { only.result.errors.forEach((error) => { errors.push(this.formatError(error.test, error.label)); }); } } // Use old syntax to allow await in loop without using promise.all const keys = Object.keys(this._schema); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const item = this._schema[key]; const value = typeof this._body[key] !== 'undefined' ? this._body[key] : null; this._body[key] = value; // If not required pass if (this._options.required === true) { item.required(); } // validate the item const hasError = yield item.validate(value); if (!hasError) { continue; } this._errorsRaw.push({ test: hasError, label: key }); const formatted = this.formatError(hasError, key); if (!formatted.errors || !this._options.flatten) { errors.push(formatted); } else if (this._options.flatten && formatted.errors) { errors.push(...formatted.errors.reduce(this.flatten, [])); } } this._errors = errors; // Check confirm after validation if (this._confirm.length > 0) { this._confirm.forEach((item) => { const initial = this._body[item.initial]; const comparison = this._body[item.comparison]; if ((0, isEqual_1.default)(initial, comparison)) { return; } this._errors.push(this.formatError((0, createTest_1.createTestResult)((0, createTest_1.createTest)({ type: 'confirm', args: item }), false), item.comparison)); }); } if (errors.length > 0) { return returnOrCallback(errors, callback); } return returnOrCallback(false, callback); }); } /** * Force the confirmation of one field by an other one * * @param {string} initial The first key * @param {string} comparison The second key * @return {this} */ confirm(initial, comparison) { this._confirm.push({ initial, comparison }); return this; } } exports.Validator = Validator; exports.default = Validator;