altheia-async-data-validator
Version:
A very simple, fast and customizable async data validator
216 lines (215 loc) • 7.59 kB
JavaScript
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;
;