From a046881f947d01f09b178517626fdb914d9948fa Mon Sep 17 00:00:00 2001 From: Denis Frezzato Date: Mon, 25 Oct 2021 08:25:51 +0200 Subject: [PATCH] Improve refineto be used with type guards --- deno/lib/__tests__/refine.test.ts | 33 +++++++++++++++++++++++++++++++ deno/lib/types.ts | 24 ++++++++++++++++++---- src/__tests__/refine.test.ts | 33 +++++++++++++++++++++++++++++++ src/types.ts | 24 ++++++++++++++++++---- 4 files changed, 106 insertions(+), 8 deletions(-) diff --git a/deno/lib/__tests__/refine.test.ts b/deno/lib/__tests__/refine.test.ts index f2da2c820..c85f94201 100644 --- a/deno/lib/__tests__/refine.test.ts +++ b/deno/lib/__tests__/refine.test.ts @@ -2,6 +2,7 @@ import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; const test = Deno.test; +import { util } from "../helpers/util.ts"; import * as z from "../index.ts"; import { ZodIssueCode } from "../ZodError.ts"; @@ -49,6 +50,38 @@ test("refinement 2", () => { ).toThrow(); }); +test("refinement type guard", () => { + const validationSchema = z.object({ + a: z.string().refine((s): s is "a" => s === "a"), + }); + type Schema = z.infer; + + const f1: util.AssertEqual<"a", Schema["a"]> = true; + f1; + const f2: util.AssertEqual<"string", Schema["a"]> = false; + f2; +}); + +test("refinement Promise", async () => { + const validationSchema = z + .object({ + email: z.string().email(), + password: z.string(), + confirmPassword: z.string(), + }) + .refine( + (data) => + Promise.resolve().then(() => data.password === data.confirmPassword), + "Both password and confirmation must match" + ); + + await validationSchema.parseAsync({ + email: "aaaa@gmail.com", + password: "password", + confirmPassword: "password", + }); +}); + test("custom path", async () => { const result = await z .object({ diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 889723997..9b94b8792 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -216,8 +216,16 @@ export abstract class ZodType< /** The .check method has been removed in Zod 3. For details see https://github.com/colinhacks/zod/tree/v3. */ check!: never; - refine any>( - check: Func, + refine( + check: (arg: Output) => arg is RefinedOutput, + message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams) + ): ZodEffects; + refine( + check: (arg: Output) => unknown | Promise, + message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams) + ): ZodEffects; + refine( + check: (arg: Output) => unknown, message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams) ): ZodEffects { const getIssueProperties: any = (val: Output) => { @@ -255,8 +263,16 @@ export abstract class ZodType< }); } + refinement( + check: (arg: Output) => arg is RefinedOutput, + refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData) + ): ZodEffects; refinement( - check: (arg: Output) => any, + check: (arg: Output) => boolean, + refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData) + ): ZodEffects; + refinement( + check: (arg: Output) => unknown, refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData) ): ZodEffects { return this._refinement((val, ctx) => { @@ -280,7 +296,7 @@ export abstract class ZodType< schema: this, typeName: ZodFirstPartyTypeKind.ZodEffects, effect: { type: "refinement", refinement }, - }) as any; + }); } superRefine = this._refinement; diff --git a/src/__tests__/refine.test.ts b/src/__tests__/refine.test.ts index d54b0fbb1..e678a5c80 100644 --- a/src/__tests__/refine.test.ts +++ b/src/__tests__/refine.test.ts @@ -1,6 +1,7 @@ // @ts-ignore TS6133 import { expect, test } from "@jest/globals"; +import { util } from "../helpers/util"; import * as z from "../index"; import { ZodIssueCode } from "../ZodError"; @@ -48,6 +49,38 @@ test("refinement 2", () => { ).toThrow(); }); +test("refinement type guard", () => { + const validationSchema = z.object({ + a: z.string().refine((s): s is "a" => s === "a"), + }); + type Schema = z.infer; + + const f1: util.AssertEqual<"a", Schema["a"]> = true; + f1; + const f2: util.AssertEqual<"string", Schema["a"]> = false; + f2; +}); + +test("refinement Promise", async () => { + const validationSchema = z + .object({ + email: z.string().email(), + password: z.string(), + confirmPassword: z.string(), + }) + .refine( + (data) => + Promise.resolve().then(() => data.password === data.confirmPassword), + "Both password and confirmation must match" + ); + + await validationSchema.parseAsync({ + email: "aaaa@gmail.com", + password: "password", + confirmPassword: "password", + }); +}); + test("custom path", async () => { const result = await z .object({ diff --git a/src/types.ts b/src/types.ts index be5a030b0..c29a8c3ce 100644 --- a/src/types.ts +++ b/src/types.ts @@ -216,8 +216,16 @@ export abstract class ZodType< /** The .check method has been removed in Zod 3. For details see https://github.com/colinhacks/zod/tree/v3. */ check!: never; - refine any>( - check: Func, + refine( + check: (arg: Output) => arg is RefinedOutput, + message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams) + ): ZodEffects; + refine( + check: (arg: Output) => unknown | Promise, + message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams) + ): ZodEffects; + refine( + check: (arg: Output) => unknown, message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams) ): ZodEffects { const getIssueProperties: any = (val: Output) => { @@ -255,8 +263,16 @@ export abstract class ZodType< }); } + refinement( + check: (arg: Output) => arg is RefinedOutput, + refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData) + ): ZodEffects; refinement( - check: (arg: Output) => any, + check: (arg: Output) => boolean, + refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData) + ): ZodEffects; + refinement( + check: (arg: Output) => unknown, refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData) ): ZodEffects { return this._refinement((val, ctx) => { @@ -280,7 +296,7 @@ export abstract class ZodType< schema: this, typeName: ZodFirstPartyTypeKind.ZodEffects, effect: { type: "refinement", refinement }, - }) as any; + }); } superRefine = this._refinement;