altheia-async-data-validator
Version:
A very simple, fast and customizable async data validator
198 lines (197 loc) • 6.72 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeBase = void 0;
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;
};
/**
* All type inherit this Class
*/
class TypeBase {
/**
* Constructor
*
* @param {Altheia} inst
*/
constructor(inst) {
this.createTest = createTest_1.createTest;
this.createTestResult = createTest_1.createTestResult;
// eslint-disable-next-line global-require
this.inst = inst || require('./index');
this.tests = [];
this._required = false;
this._needCast = false;
}
/**
* Clone this class
*/
clone() {
const clone = Object.assign(Object.create(this), this);
// Quick deep clone
clone.tests = this.tests.slice(0);
return clone;
}
/**
* Add a test
*
* @param name
* @param func
* @param args
*/
test(name, func, args = {}) {
this.tests.push(() => {
return this.createTest({
type: `${this.name}.${name}`,
func,
args,
});
});
}
/**
* Validate a value based on all tests added
* @param {mixed} toTest
* @param {Function} callback
*/
validate(toTest, callback) {
return __awaiter(this, void 0, void 0, function* () {
// Test presence early to fail/pass early
const presence = this.presence(toTest);
if (presence === false) {
if (this._required === false) {
return returnOrCallback(false, callback);
}
return returnOrCallback(this.createTestResult(this.createTest({
type: 'required',
}), false), callback);
}
if (this._needCast) {
/* eslint-disable no-param-reassign */
toTest = yield this._cast(toTest);
/* eslint-enable no-param-reassign */
}
// Iterate all tests
for (let i = 0; i < this.tests.length; i++) {
const test = this.tests[i]();
// Special condition for IF() we need to display error of deep validation
const internalResult = this.testToTestResult(yield test.func(toTest));
// Let test override current test type/arguments
// Helps when you encapsulate test inside other test and want a transparent error
// e.g: `if()`
if (internalResult.overrideWith) {
test.type = internalResult.overrideWith.type;
test.args = internalResult.overrideWith.args;
}
if (typeof internalResult.error === 'undefined' ||
typeof internalResult.valid === 'undefined') {
throw new Error('test() should return a boolean or an object { valid: boolean, error: string }');
}
// Do not go deeper in test
if (!internalResult.valid) {
return returnOrCallback(this.createTestResult(test, internalResult.valid, internalResult), callback);
}
}
return returnOrCallback(false, callback);
});
}
/**
* Force the value to be non-null
*/
required() {
this._required = true;
return this;
}
/**
* Force the value to be casted before any check
*/
cast() {
this._needCast = true;
return this;
}
/**
* Custom validator
*
* @param {string} name
* @param {Function} callback
* @param {Function} message
*/
custom(name, callback) {
this.test(`custom.${name}`, (value) => __awaiter(this, void 0, void 0, function* () {
try {
return yield callback(value);
}
catch (e) {
return false;
}
}), {});
return this;
}
/**
* Presence validation
*
* @param {mixed} toTest
*/
presence(toTest) {
if (toTest === null ||
typeof toTest === 'undefined' ||
toTest === undefined) {
return false;
}
if (typeof toTest === 'string' && toTest.length <= 0) {
// If the flag is passed to true
// we consider the string valid and can be deeply tested,
// this will trigger the noEmpty() test
if (this._noEmpty) {
return true;
}
return false;
}
return true;
}
/**
* If validation
*
* @param {function} options.test
* @param {function} options.then
* @param {function} options.otherwise
*/
if({ test, then, otherwise, }) {
this.test('if', (str) => __awaiter(this, void 0, void 0, function* () {
const clone = this.clone();
clone.tests = [];
const hasError = yield test(clone).validate(str);
if (!hasError) {
clone.tests = [];
const temp = yield then(clone).validate(str);
return temp && temp.result
? Object.assign(Object.assign({}, temp.result), { overrideWith: temp }) : true;
}
clone.tests = [];
const temp = yield otherwise(clone).validate(str);
return temp && temp.result
? Object.assign(Object.assign({}, temp.result), { overrideWith: temp }) : true;
}));
return this;
}
testToTestResult(result) {
if (typeof result === 'boolean') {
return { valid: result, error: '' };
}
return result;
}
}
exports.TypeBase = TypeBase;
exports.default = TypeBase;