import { type Infer, IssueCustom, type PropertyKey, type ValidationResult, type Validator, type ValidatorContext, } from "../validation.ts"; import type { CustomAssertionContext } from "./custom.ts"; export type RefinementCheck = { check: (value: T, ctx: CustomAssertionContext) => boolean; message: string; path?: PropertyKey | readonly PropertyKey[]; }; export type RefinementAssertion = { check: (this: null, value: T, ctx: CustomAssertionContext) => value is Out; message: string; path?: PropertyKey | readonly PropertyKey[]; }; export type InferRefinement = R extends RefinementCheck ? T : R extends RefinementAssertion ? T : never; export type Refinement = | RefinementCheck | RefinementAssertion; export function refine>( schema: S, refinement: RefinementAssertion, Out>, ): S & Validator; export function refine( schema: S, refinement: RefinementCheck>, ): S; export function refine< R extends Refinement, S extends Validator>, >(schema: S, refinement: R): S; export function refine( schema: S, refinement: Refinement>, ): S { return Object.create(schema, { validateInContext: { value: validateInContextUnbound.bind({ schema, refinement }), enumerable: false, writable: false, configurable: true, }, }); } function validateInContextUnbound( this: { schema: S; refinement: Refinement> }, input: unknown, ctx: ValidatorContext, ): ValidationResult> { const result = ctx.validate(input, this.schema); if (!result.success) return result; const checkResult = this.refinement.check.call(null, result.value, ctx); if (!checkResult) { const path = ctx.concatPath(this.refinement.path); return ctx.failure(new IssueCustom(path, input, this.refinement.message)); } return result; }