UNPKG

altheia-async-data-validator

Version:

A very simple, fast and customizable async data validator

216 lines (215 loc) 7.59 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.TypeObject = exports.messages = void 0; const isPlainObject_1 = __importDefault(require("lodash/isPlainObject")); const arraydiff_1 = __importDefault(require("../utils/arraydiff")); const base_1 = require("./base"); exports.messages = { 'object.typeof': (name) => `${name} must be a valid object`, 'object.in': (name, args) => `${name} must only contains these keys [${args.in}]`, 'object.not': (name) => `${name} contains forbidden value`, 'object.schema': (name) => `${name} does not match its schema`, 'object.oneOf': (name, args, result) => { if (result.error === 'oneIsRequired') { return `${name} must contain one of these keys [${args.keys}]`; } if (result.keys) { return `${name} can not contain these two keys [${result.keys}] at the same time`; } return 'unknown error'; }, 'object.allOf': (name, args) => `${name} must contain either none or all of these keys [${args.keys}]`, 'object.anyOf': (name, args) => `${name} must contain at least one of these keys [${args.keys}]`, }; /** * Object class */ class TypeObject extends base_1.TypeBase { /** * Constructor */ constructor() { super(); this.name = 'object'; this.typeof(); } _cast() { throw new Error('not available for this validator'); } /** * Test to validate the type of the value * * @return {this} */ typeof() { this.test('typeof', (val) => { return (0, isPlainObject_1.default)(val); }); return this; } in(...array) { let only = array; let options = { oneErrorPerKey: false }; // handle someone passing literal array instead of multiple args if (array.length > 0 && Array.isArray(array[0])) { if ((0, isPlainObject_1.default)(array[1])) { options = Object.assign({}, array[1]); } only = array[0]; } this.test('in', (obj) => { const diff = (0, arraydiff_1.default)(Object.keys(obj), only); if (diff.length <= 0) { return true; } if (options.oneErrorPerKey) { return { valid: false, error: 'in', errors: diff.map((label) => { return { test: this.createTestResult(this.createTest({ type: 'forbidden' }), false), label, }; }), }; } return false; }, { in: only }); return this; } not(...array) { let only = array; // handle someone passing literal array instead of multiple args if (array.length === 1 && Array.isArray(array[0])) { only = array[0]; } this.test('not', (obj) => { const diff = (0, arraydiff_1.default)(only, Object.keys(obj)); return diff.length === only.length; }, { not: only }); return this; } /** * Validate an object with a fully qualified schema * * @param {Altheia} schema * @param {boolean} options.returnErrors If true, deep errors while be returned too * @return {this} */ schema(schema, { returnErrors = true } = {}) { if (typeof schema.isValidator === 'undefined') { throw new Error('schema should be an instance of altheia validator "Alt({ ... })"'); } this.test('schema', (obj) => __awaiter(this, void 0, void 0, function* () { const clone = schema.clone(); const hasError = yield clone.body(obj).validate(); return { valid: hasError === false, error: 'schema', errors: hasError && returnErrors ? clone._errorsRaw : undefined, }; }), { schema }); return this; } oneOf(...params) { if (params.length <= 1) { throw new Error('oneOf expect at least 2 params'); } let oneIsRequired = false; let keys = []; if (typeof params[0] === 'boolean') { oneIsRequired = params[0]; keys = params.splice(1); } else { keys = params; } this.test('oneOf', (obj) => { const presence = { a: null, b: null, }; try { Object.keys(obj).forEach((key) => { if (keys.includes(key)) { if (!presence.a) { presence.a = key; } else if (!presence.b) { presence.b = key; throw new Error('a and b can not be present at the same time'); } } }); } catch (e) { return { valid: false, error: 'exclusion', keys: Object.values(presence), }; } if (oneIsRequired && !presence.a && !presence.b) { return { valid: false, error: 'oneIsRequired' }; } return true; }, { oneIsRequired, keys }); return this; } /** * Force all keys to be mutually required. If one is presents, all are required. Pass if none are present. * * @param {...string} keys * @return {this} */ allOf(...keys) { this.test('allOf', (obj) => { const count = Object.keys(obj).reduce((acc, k) => { if (keys.includes(k)) { // eslint-disable-next-line no-param-reassign acc += 1; } return acc; }, 0); return count === keys.length || count === 0; }, { keys }); return this; } /** * Force one or many keys to be present * * @param {...string} keys * @return {this} */ anyOf(...keys) { this.test('anyOf', (obj) => { return (Object.keys(obj).reduce((acc, k) => { if (keys.includes(k)) { // eslint-disable-next-line no-param-reassign acc += 1; } return acc; }, 0) >= 1); }, { keys }); return this; } } exports.TypeObject = TypeObject; const def = { Class: TypeObject, messages: exports.messages, }; exports.default = def;