Suite of AT Protocol TypeScript libraries built on web standards
1import {
2 type Infer,
3 IssueCustom,
4 type PropertyKey,
5 type ValidationResult,
6 type Validator,
7 type ValidatorContext,
8} from "../validation.ts";
9import type { CustomAssertionContext } from "./custom.ts";
10
11export type RefinementCheck<T> = {
12 check: (value: T, ctx: CustomAssertionContext) => boolean;
13 message: string;
14 path?: PropertyKey | readonly PropertyKey[];
15};
16
17export type RefinementAssertion<T, Out extends T> = {
18 check: (this: null, value: T, ctx: CustomAssertionContext) => value is Out;
19 message: string;
20 path?: PropertyKey | readonly PropertyKey[];
21};
22
23export type InferRefinement<R> = R extends RefinementCheck<infer T> ? T
24 : R extends RefinementAssertion<infer T, any> ? T
25 : never;
26
27export type Refinement<T = any, Out extends T = T> =
28 | RefinementCheck<T>
29 | RefinementAssertion<T, Out>;
30
31export function refine<S extends Validator, Out extends Infer<S>>(
32 schema: S,
33 refinement: RefinementAssertion<Infer<S>, Out>,
34): S & Validator<Out>;
35export function refine<S extends Validator>(
36 schema: S,
37 refinement: RefinementCheck<Infer<S>>,
38): S;
39export function refine<
40 R extends Refinement,
41 S extends Validator<InferRefinement<R>>,
42>(schema: S, refinement: R): S;
43export function refine<S extends Validator>(
44 schema: S,
45 refinement: Refinement<Infer<S>>,
46): S {
47 return Object.create(schema, {
48 validateInContext: {
49 value: validateInContextUnbound.bind({ schema, refinement }),
50 enumerable: false,
51 writable: false,
52 configurable: true,
53 },
54 });
55}
56
57function validateInContextUnbound<S extends Validator>(
58 this: { schema: S; refinement: Refinement<Infer<S>> },
59 input: unknown,
60 ctx: ValidatorContext,
61): ValidationResult<Infer<S>> {
62 const result = ctx.validate(input, this.schema);
63 if (!result.success) return result;
64
65 const checkResult = this.refinement.check.call(null, result.value, ctx);
66 if (!checkResult) {
67 const path = ctx.concatPath(this.refinement.path);
68 return ctx.failure(new IssueCustom(path, input, this.refinement.message));
69 }
70
71 return result;
72}