model-validator-ts
Version:
[](https://www.npmjs.com/package/model-validator-ts)
254 lines • 7.68 kB
JavaScript
function invariant(condition, message) {
if (!condition) {
throw new Error(message);
}
}
export class ErrorBag {
#issues = [];
addError(key, message) {
this.#issues.push({ key, message });
}
firstError(key) {
return this.#issues.find(issue => issue.key === key)?.message;
}
hasErrors() {
return this.#issues.length > 0;
}
toFlattenObject() {
const result = {};
for (const issue of this.#issues) {
result[issue.key] = issue.message;
}
return result;
}
get toObject() {
const result = {};
for (const issue of this.#issues) {
if (!result[issue.key]) {
result[issue.key] = [];
}
result[issue.key].push(issue.message);
}
return result;
}
toString() {
return JSON.stringify(this.toObject);
}
toHtml() {
let result = "";
if (!this.hasErrors()) {
return result;
}
result += "<ul>";
for (const issue of this.#issues) {
let strKey;
if (typeof issue.key === "symbol" || typeof issue.key === "number") {
strKey = issue.key.toString();
}
else {
strKey = issue.key;
}
result += `<li>${strKey}: ${issue.message}</li>`;
}
result += "</ul>";
return result;
}
toText() {
if (!this.hasErrors()) {
return "";
}
const result = [];
let currentKey = null;
for (const issue of this.#issues) {
if (currentKey !== issue.key) {
currentKey = issue.key;
result.push(currentKey);
}
result.push(`- ${issue.message}`);
}
return result.join("\n");
}
}
// Separate class for validator definition
export class ValidatorDefinition {
schema;
rules = [];
['~deps'];
['~preBuildDeps'];
constructor(args) {
this.schema = args.schema;
this.rules = args.rules ?? [];
this['~deps'] = args.deps ?? {};
this['~preBuildDeps'] = args.preBuildDeps ?? {};
}
addRule(ruledef) {
this.rules.push(ruledef);
return this;
}
addRules(ruledefs) {
this.rules.push(...ruledefs);
return this;
}
build(...dependencies) {
const [deps] = dependencies;
return new ValidatorModel(this.schema, this.rules, {
...this['~preBuildDeps'],
...(deps ?? {}),
});
}
}
// Update ValidationBuilder to accept rules and dependencies
export class ValidatorModel {
schema;
#rules;
#deps;
errors = new ErrorBag();
constructor(schema, rules, deps) {
this.schema = schema;
this.#rules = rules;
this.#deps = deps;
}
addError(key, message) {
this.errors.addError(key, message);
return this.errors;
}
mergeErrors(errors) {
for (const [key, message] of Object.entries(errors)) {
if (!message) {
continue;
}
if (typeof key !== "string") {
throw new Error("Invalid error key, expected string");
}
if (typeof message !== "string") {
throw new Error("Invalid error message, expected string");
}
this.addError(key, message);
}
return this.errors;
}
mapErrors(args) {
for (const [key, message] of Object.entries(args.src)) {
let targetKey = null;
if ("mappings" in args) {
// @ts-ignore
targetKey = args.mappings[key] ?? null;
}
targetKey = targetKey ?? key;
this.addError(targetKey, message);
}
return this.errors;
}
/*
* Method to validate does the same as validate but because the input is already typed
* we can use it to validate the shape of the input at compile time
* @param input - the input to validate
*/
async validateShape(input, opts) {
return this.validate(input, opts);
}
/*
* Method to validate the input against the schema and business rules
* @param input - the input to validate
* @return a ValidationResult object
*/
async validate(input, opts) {
const override = opts?.override;
if (override && typeof input === "object" && input !== null) {
Object.assign(input, override);
}
let presult = this.schema['~standard'].validate(input);
if (presult instanceof Promise) {
presult = await presult;
}
this.errors = new ErrorBag();
if (presult.issues) {
for (const issue of presult.issues) {
if (typeof issue.message !== "string") {
throw new Error("Unexpected error format, expected string");
}
let path;
if (Array.isArray(issue.path)) {
path = issue.path.join(".");
}
else if (typeof issue.path === "string") {
path = issue.path;
}
else {
throw new Error(`Unsupported issue path type ${typeof issue.path}: issue: ${JSON.stringify(issue)}`);
}
this.addError(path, issue.message);
}
return {
success: false,
errors: this.errors,
};
}
for (const ruleDef of this.#rules) {
const params = {
property: ruleDef.attribute,
data: presult.value,
builder: this,
deps: this.#deps,
};
if (ruleDef.fn.constructor.name === "AsyncFunction") {
await ruleDef.fn(params);
}
else {
ruleDef.fn(params);
}
}
if (this.errors.hasErrors()) {
return {
success: false,
errors: this.errors,
};
}
else {
return {
success: true,
value: presult.value,
};
}
}
toClientFields() {
throw new Error("Not implemented");
}
}
export function createCommand(args) {
const validator = args.validator.build(args.deps);
return {
runShape(input, opts) {
return this.run(input, opts);
},
async run(input, opts) {
const validation = await validator.validate(input, opts);
if (!validation.success) {
return { validated: false, errors: validation.errors };
}
const result = await args.execute({
data: validation.value,
deps: args.deps,
builder: validator,
});
if (result instanceof ErrorBag) {
return { validated: false, errors: result };
}
return { validated: true, result: result };
},
execute: args.execute,
validator,
};
}
export function createValidatorBuilder(config) {
return function createValidator(args) {
const outerDeps = typeof config.deps === "function" ? config.deps() : config.deps ?? {};
return new ValidatorDefinition({
schema: args.schema,
rules: args.rules ?? [],
deps: args.deps,
preBuildDeps: outerDeps,
});
};
}
//# sourceMappingURL=index.js.map