validlyjs
Version:
ValidlyJS is a lightweight, type-safe validation library inspired by Laravel's validation syntax
171 lines • 6.51 kB
JavaScript
import { parseRules } from "../parsers/index.js";
import { getRuleHandler } from "../rules/index.js";
import { defaultConfig, setLocale, getConfig } from "../config.js";
import { prepareValue } from "../utils/common.js";
import { DATA_TYPES } from "../utils/typeCheck.js";
export class Validator {
rules;
config;
constructor(schema, config) {
this.rules = new Map();
this.config = { ...defaultConfig, schema, ...config };
Object.entries(schema).forEach(([field, ruleDef]) => {
const rules = parseRules(ruleDef);
const dataTypeRules = rules.filter((rule) => DATA_TYPES.includes(rule.name) && !rule.custom);
if (dataTypeRules.length > 1) {
throw new Error(`Field "${field}" cannot have more than one data type rule (string, number, boolean, array, date, file or object).`);
}
this.rules.set(field, rules);
});
}
setLocale(locale) {
setLocale(locale);
this.config = { ...this.config, ...getConfig() };
return this;
}
updateCleanData(field, value, cleanData) {
const parts = field.split('.');
let target = cleanData;
for (let i = 0; i < parts.length - 1; i++) {
const key = parts[i];
if (!target[key]) {
if (!isNaN(Number(parts[i + 1]))) {
target[key] = [];
}
else {
target[key] = {};
}
}
target = target[key];
}
target[parts[parts.length - 1]] = value;
}
async validateField(field, rules, data, cleanData, errors, isAsync = false) {
const parts = field.split('.');
let currentObj = data;
let value = null;
let isValid = true;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (currentObj === undefined || currentObj === null) {
isValid = false;
break;
}
if (i === parts.length - 1) {
value = currentObj[part];
}
else {
currentObj = currentObj[part];
}
}
if (!isValid) {
errors.set(field, ["The specified path does not exist"]);
return;
}
const isNullable = rules.some((rule) => rule.name === "nullable");
const isRequired = rules.some((rule) => rule.name === "required");
if (isNullable && (value === null || value === undefined)) {
this.updateCleanData(field, value, cleanData);
return;
}
if (isRequired &&
(value === null || value === undefined || value === "")) {
const handler = getRuleHandler("required");
const message = handler.message([], {
value,
data,
field,
config: this.config,
schema: this.config.schema,
formatMessage: this.formatMessage.bind(this),
});
errors.set(field, [message]);
return;
}
const preparedValue = prepareValue(value, this.config);
const context = {
value: preparedValue,
data,
field,
config: this.config,
schema: this.config.schema,
formatMessage: this.formatMessage.bind(this),
};
const dataType = rules.find((rule) => DATA_TYPES.includes(rule.name))?.name;
for (const rule of rules) {
if (rule.name === "nullable")
continue;
try {
const handler = getRuleHandler(rule.name, dataType);
let validationResult;
if (isAsync) {
validationResult = await handler.validate(preparedValue, rule.params, context);
}
else {
validationResult = handler.validate(preparedValue, rule.params, context);
}
if (!validationResult) {
const message = handler.message(rule.params, context);
const fieldErrors = errors.get(field) || [];
fieldErrors.push(message);
errors.set(field, fieldErrors);
if (this.config.bail)
break;
}
}
catch (error) {
console.error(`Validation error for rule ${rule.name} on field ${field}: `, error.message);
}
}
if (value !== undefined) {
this.updateCleanData(field, value, cleanData);
}
}
validate(data) {
const errors = new Map();
const cleanData = {};
for (const [field, rules] of this.rules.entries()) {
this.validateField(field, rules, data, cleanData, errors, false);
}
return {
isValid: errors.size === 0,
data: cleanData,
errors: Object.fromEntries(errors),
};
}
async validateAsync(data) {
const errors = new Map();
const cleanData = {};
for (const [field, rules] of this.rules.entries()) {
await this.validateField(field, rules, data, cleanData, errors, true);
}
return {
isValid: errors.size === 0,
data: cleanData,
errors: Object.fromEntries(errors),
};
}
formatMessage(params, defaultMessage) {
if (typeof defaultMessage === "string") {
return this.replaceMessageParams(defaultMessage, params);
}
if (typeof defaultMessage === "object") {
console.log(params, defaultMessage);
const [ruleName, subKey] = Object.keys(params)[0].split(".");
if (ruleName && subKey && defaultMessage[ruleName]) {
const message = defaultMessage[ruleName];
if (typeof message === "string") {
return this.replaceMessageParams(message, params);
}
if (typeof message === "object" && message[subKey]) {
return this.replaceMessageParams(message[subKey], params);
}
}
}
return "Validation error";
}
replaceMessageParams(message, params) {
return message.replace(/:(\w+)/g, (_, key) => params[key] || "");
}
}
//# sourceMappingURL=Validator.js.map