n4s
Version:
typed schema validation version of enforce
86 lines (78 loc) • 2.2 kB
text/typescript
import { StringObject, assign, invariant, mapFirst } from 'vest-utils';
import { ctx } from './enforceContext';
import { RuleInstance } from './utils/RuleInstance';
import { RuleRunReturn } from './utils/RuleRunReturn';
type ComposeResult<T = any> = RuleInstance<T, [T]> & {
(value: T): void;
};
/**
* Composes multiple validation rules into a single reusable rule.
* The composed rule executes rules in order and fails on the first failing rule.
* Returns a RuleInstance that can be used with both eager and lazy APIs.
*
* @template T - The type of value to validate
* @param composites - Validation rules to compose
* @returns A composed rule that can be run with values or called directly
*
* @example
* ```typescript
* // Create a reusable adult age validation
* const isAdult = compose(
* enforce.isNumber(),
* enforce.greaterThanOrEquals(18),
* enforce.lessThan(150)
* );
*
* // Use with lazy API
* isAdult.test(25); // true
* isAdult.test(16); // false
*
* // Use with eager API
* enforce(30).run(isAdult); // passes
*
* // Call directly (throws on failure)
* isAdult(25); // ok
* isAdult(16); // throws
*
* // Compose with other rules
* const userSchema = enforce.shape({
* age: isAdult,
* name: enforce.isString()
* });
* ```
*/
export function compose<T = any>(
...composites: RuleInstance<any, [any]>[]
): ComposeResult<T> {
const composedFn = assign(
(value: T) => {
const res = run(value);
invariant(res.pass, StringObject(res.message));
},
{
run,
test: (value: T) => run(value).pass,
infer: {} as T,
},
);
return composedFn as ComposeResult<T>;
function run(value: T): RuleRunReturn<T> {
return ctx.run({ value }, () => {
let result: RuleRunReturn<T> = RuleRunReturn.Passing(value);
mapFirst(
composites,
(
composite: RuleInstance<any, [any]>,
breakout: (conditional: boolean, res: RuleRunReturn<any>) => void,
) => {
const res = composite.run(value);
if (!res.pass) {
result = res;
breakout(true, res);
}
},
);
return result;
});
}
}