abolish
Version:
A javascript object validator.
418 lines (417 loc) • 14.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbolishCompiled = void 0;
const AbolishError_1 = __importDefault(require("./AbolishError"));
const inbuilt_fn_1 = require("./inbuilt.fn");
class AbolishCompiled {
/**
* Constructor
* @param input
*/
constructor(input) {
/**
* Hold Compiled Object
*/
this.data = {};
/**
* Schema Keys and Included Fields
*/
this.fields = [];
/**
* If fields has any dot notation set to true
*/
this.fieldsHasDotNotation = false;
/**
* Is Object is true, but if a variable is passed, it will return false
*/
this.isObject = true;
/**
* if there is an async validator, set to true
*/
this.async = false;
Object.defineProperty(this, "input", {
value: input,
enumerable: false,
writable: true
});
}
/**
* Validate Compiled Schema
* @param data
*/
validateObject(data) {
/**
* If this compiled input is not an object, throw error
*/
if (!this.isObject) {
throw new Error("Variable compiled input cannot be used to validate an object, use object compiled input!");
}
/**
* If this compiled input is async, throw error
*/
if (this.async) {
throw new Error("Rules contains an async validator, use validateObjectAsync instead!");
}
/**
* Validate Object
*/
const validated = { ...data };
if (this.allowedFields) {
const objKeys = Object.keys(validated);
const unknownKeys = objKeys.filter((key) => !this.allowedFields.includes(key));
if (unknownKeys.length) {
return [
{
code: "object.unknown",
type: "internal",
key: "$strict",
validator: "$strict",
message: "Data contains unknown fields!",
data: { unknown: unknownKeys }
},
{}
];
}
}
/**
* Hold current fields
* This will be used in the skip section to remove fields that are not included in the input
*/
let fields = this.fields;
/**
* Loop through all the fields in the input
*/
for (const field in this.data) {
const compiled = this.data[field];
// Current field value
const value = (0, inbuilt_fn_1.abolish_Get)(validated, field, this.fieldsHasDotNotation);
// Check if skip rule is set
if (compiled.$skip) {
let $skip = compiled.$skip;
// Run skip if it is a function
if (typeof $skip === "function") {
$skip = $skip(value, validated);
}
if ($skip) {
// if field is not in included fields, remove it from fields
if (this.includedFields && !this.includedFields.includes(field)) {
fields = fields.filter((f) => f !== field);
}
continue;
}
}
/**
* Loop through all compiled validators
*/
for (const validatorName in compiled.validators) {
const validator = compiled.validators[validatorName];
let result = false;
try {
result = validator.func(value, validated);
}
catch (e) {
return [
{
code: "default",
key: field,
type: "internal",
validator: validatorName,
message: e.message,
data: e.stack
},
{}
];
}
if (typeof result !== undefined &&
(result === false || (0, inbuilt_fn_1.InstanceOf)(AbolishError_1.default, result))) {
return parseErrorMessage(field, value, result, validator, compiled.$name);
}
}
}
let result;
if (fields.length === 1) {
const onlyField = fields[0];
result = { [onlyField]: validated[onlyField] };
}
else if (fields.length > 1) {
result = (0, inbuilt_fn_1.abolish_Pick)(validated, fields, this.fieldsHasDotNotation);
}
else {
result = {};
}
return [undefined, result];
}
async validateObjectAsync(data) {
/**
* If this compiled input is not an object, throw error
*/
if (!this.isObject) {
throw new Error("Variable compiled input cannot be used to validate an object, use object compiled input!");
}
/**
* Validate Object
*/
const validated = { ...data };
if (this.allowedFields) {
const objKeys = Object.keys(validated);
const unknownKeys = objKeys.filter((key) => !this.allowedFields.includes(key));
if (unknownKeys.length) {
return [
{
code: "object.unknown",
type: "internal",
key: "$strict",
validator: "$strict",
message: "Data contains unknown fields!",
data: { unknown: unknownKeys }
},
{}
];
}
}
/**
* Hold current fields
* This will be used in the skip section to remove fields that are not included in the input
*/
let fields = this.fields;
/**
* Loop through all the fields in the input
*/
for (const field in this.data) {
const compiled = this.data[field];
// Current field value
const value = (0, inbuilt_fn_1.abolish_Get)(validated, field, this.fieldsHasDotNotation);
// Check if skip rule is set
if (compiled.$skip) {
let $skip = compiled.$skip;
// Run skip if it is a function
if (typeof $skip === "function") {
$skip = $skip(value, validated);
}
if ($skip) {
// if field is not in included fields, remove it from fields
if (this.includedFields && !this.includedFields.includes(field)) {
fields = fields.filter((f) => f !== field);
}
continue;
}
}
/**
* Loop through all compiled validators
*/
for (const validatorName in compiled.validators) {
const validator = compiled.validators[validatorName];
let result = false;
try {
if (validator.async) {
result = (await validator.func(value, validated));
}
else {
result = validator.func(value, validated);
}
}
catch (e) {
return [
{
code: "default",
key: field,
type: "internal",
validator: validator.name,
message: e.message,
data: e.stack
},
{}
];
}
if (typeof result !== undefined &&
(result === false || (0, inbuilt_fn_1.InstanceOf)(AbolishError_1.default, result))) {
return parseErrorMessage(field, value, result, validator, compiled.$name);
}
}
}
let result;
if (fields.length === 1) {
const onlyField = fields[0];
result = { [onlyField]: validated[onlyField] };
}
else if (fields.length > 1) {
result = (0, inbuilt_fn_1.abolish_Pick)(validated, fields, this.fieldsHasDotNotation);
}
else {
result = {};
}
return [undefined, result];
}
/**
* Validate a variable using a variable compiled input
* @param variable Variable to validate
* @returns
*/
validateVariable(variable) {
if (this.isObject) {
throw new Error("Object compiled cannot be used to validate a variable, use regular compiled input!");
}
this.isObject = true; // set to true to avoid error
const data = this.validateObject({ variable });
this.isObject = false; // set back to false
// get variable from data
data[1] = data[1].variable;
return data;
}
/**
* validateVariable async version
* @param variable Variable to validate
* @returns
*/
async validateVariableAsync(variable) {
if (this.isObject) {
throw new Error("Object compiled cannot be used to validate a variable, use regular compiled input!");
}
this.isObject = true; // set to true to avoid error
const data = await this.validateObjectAsync({ variable });
this.isObject = false; // set back to false
// get variable from data
data[1] = data[1].variable;
return data;
}
validate(value) {
return this.isObject
? this.validateObject(value)
: this.validateVariable(value);
}
async validateAsync(value) {
return this.isObject
? this.validateObjectAsync(value)
: this.validateVariableAsync(value);
}
/**
* Get `this.input` as AbolishRule
*/
getInputRule() {
return this.input;
}
/**
* Get `this.input` as AbolishSchema
*/
getInputSchema() {
return this.input;
}
/**
* Change a fields validator option
* @param fieldName
* @param validatorName
* @param option
*/
setValidatorOption(validatorName, option, fieldName) {
if (!fieldName) {
if (this.isObject) {
throw new Error("Field name is required when using object compiled input!");
}
else {
fieldName = "variable";
}
}
else if (fieldName && !this.isObject) {
throw new Error("Field name is not allowed when using variable compiled input!");
}
if (this.data[fieldName] && this.data[fieldName].validators[validatorName]) {
this.data[fieldName].validators[validatorName].option = option;
}
return this;
}
/**
* Copy current compiled instance
* This is useful when you want to use the same compiled input for multiple validation
* It prevents memory leak
*/
copy() {
const copy = new AbolishCompiled(this.input);
// set other properties
copy.fields = this.fields;
copy.includedFields = this.includedFields;
copy.fieldsHasDotNotation = this.fieldsHasDotNotation;
copy.isObject = this.isObject;
copy.async = this.async;
copy.data = {};
// copy data
for (const field in this.data) {
// copy validators
const validators = {};
for (const validatorName in this.data[field].validators) {
validators[validatorName] = {
...this.data[field].validators[validatorName]
};
}
copy.data[field] = {
$name: this.data[field].$name,
$skip: this.data[field].$skip,
validators
};
}
return copy;
}
}
exports.AbolishCompiled = AbolishCompiled;
/**
* Parse Error Message
* @param field - Field
* @param value - Value
* @param result - Result
* @param validator - Validator
* @param $name - Name of field
* @returns
*/
function parseErrorMessage(field, value, result, validator, $name) {
let message = validator.error;
let data = null;
let code = "default";
let modifiedMessage = false;
if (validator.customError) {
if (validator.errorFn) {
modifiedMessage = true;
message = validator.errorFn({
code,
data,
validator: validator.name,
value
});
}
}
else {
// noinspection SuspiciousTypeOfGuard
if ((0, inbuilt_fn_1.InstanceOf)(AbolishError_1.default, result)) {
result = result;
modifiedMessage = true;
message = result.message;
data = result.data;
code = result.code;
}
}
if (modifiedMessage) {
/**
* Replace :param with rule converted to upperCase
* and if option is stringAble, replace :option with validatorOption
*/
if (message.includes(":param")) {
// Replace all :param with field name
message = message.replace(":param", $name || field);
}
if (validator.optionString && message.includes(":option"))
message = message.replace(":option", validator.optionString);
}
// Return Error using the ValidationResult format
return [
{
code,
key: field,
type: "validator",
validator: validator.name,
message,
data
},
{}
];
}