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