n4s
Version:
typed schema validation version of enforce
134 lines (116 loc) • 3.38 kB
text/typescript
/* eslint-disable max-statements */
/* eslint-disable max-lines-per-function */
import {
dynamicValue,
type DynamicValue,
type Maybe,
type Stringable,
} from 'vest-utils';
import { StandardSchemaV1 } from 'vest-utils/standardSchemaSpec';
import { RuleInstance } from '../../utils/RuleInstance';
import { executeChain, type Predicate } from './chainExecutor';
import { createChainProxyHandlers } from './proxyHandlers';
export type RuleFunctions<T extends RuleInstance<any, any>> = Record<
keyof Omit<T, 'infer' | 'test' | 'validate' | 'parse' | '~standard'>,
(...args: any[]) => boolean | ReturnType<Predicate>
>;
type LazyMessage = DynamicValue<
string,
[value: unknown, originalMessage?: Stringable]
>;
/**
* Creates a chain builder for rule validation.
* Provides methods to add predicates, run validation, and apply custom messages.
* Implements StandardSchema v1 support.
*/
export function createChainBuilder<T extends RuleInstance<any, any>>(
rules: RuleFunctions<T> | Record<string, (...args: any[]) => any>,
) {
const chain: Predicate[] = [];
const target: Partial<T> = {};
let lazyMessage: Maybe<LazyMessage> = undefined;
const add = (p: Predicate): T => {
chain.push(p);
return proxy;
};
const prepend = (p: Predicate): T => {
chain.unshift(p);
return proxy;
};
const resolveMessage = (
result: ReturnType<typeof executeChain>,
value: unknown,
): string => {
const defaultMessage = result.message || 'Validation failed';
if (!lazyMessage) {
return defaultMessage;
}
return dynamicValue(lazyMessage, value, result.message) ?? defaultMessage;
};
const validate: T['validate'] = ((...args: any[]) => {
const result = executeChain(chain, args[0]);
if (result.pass) {
return { value: result.type };
}
return {
issues: [
{
message: resolveMessage(result, args[0]),
path: result.path || [],
},
],
};
}) as T['validate'];
const test: T['test'] = ((...args: any[]) => {
const result = validate(...args);
return !result.issues;
}) as T['test'];
// Internal compatibility method - converts StandardSchema Result to RuleRunReturn
const parse: T['parse'] = ((...args: any[]) => {
const result = validate(...args);
if (!result.issues) {
return result.value;
}
const [firstIssue] = result.issues;
throw new TypeError(firstIssue?.message || 'Validation failed');
}) as T['parse'];
const run: T['run'] = ((...args: any[]) => {
const result = executeChain(chain, args[0]);
if (!result.pass && lazyMessage) {
return {
...result,
message:
dynamicValue(lazyMessage, args[0], result.message) ?? result.message,
};
}
return result;
}) as T['run'];
const message = (msg: Stringable): T => {
if (msg) {
lazyMessage = msg;
}
return proxy;
};
const proxy: T = new Proxy(
target as T,
createChainProxyHandlers(rules, {
'~standard': {
types: {
input: undefined!,
output: undefined!,
},
validate,
vendor: 'n4s',
version: 1 as const,
} as StandardSchemaV1.Props<any, any>,
add,
message,
parse,
prepend,
run,
test,
validate,
}),
);
return { add, proxy } as const;
}