Skip to content

Commit fdd7084

Browse files
authored
Merge pull request #727 from DenisFrezzato/improve-refinement-type
Improve refine with type guards
2 parents a774526 + a046881 commit fdd7084

4 files changed

Lines changed: 106 additions & 8 deletions

File tree

deno/lib/__tests__/refine.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";
33
const test = Deno.test;
44

5+
import { util } from "../helpers/util.ts";
56
import * as z from "../index.ts";
67
import { ZodIssueCode } from "../ZodError.ts";
78

@@ -49,6 +50,38 @@ test("refinement 2", () => {
4950
).toThrow();
5051
});
5152

53+
test("refinement type guard", () => {
54+
const validationSchema = z.object({
55+
a: z.string().refine((s): s is "a" => s === "a"),
56+
});
57+
type Schema = z.infer<typeof validationSchema>;
58+
59+
const f1: util.AssertEqual<"a", Schema["a"]> = true;
60+
f1;
61+
const f2: util.AssertEqual<"string", Schema["a"]> = false;
62+
f2;
63+
});
64+
65+
test("refinement Promise", async () => {
66+
const validationSchema = z
67+
.object({
68+
email: z.string().email(),
69+
password: z.string(),
70+
confirmPassword: z.string(),
71+
})
72+
.refine(
73+
(data) =>
74+
Promise.resolve().then(() => data.password === data.confirmPassword),
75+
"Both password and confirmation must match"
76+
);
77+
78+
await validationSchema.parseAsync({
79+
email: "aaaa@gmail.com",
80+
password: "password",
81+
confirmPassword: "password",
82+
});
83+
});
84+
5285
test("custom path", async () => {
5386
const result = await z
5487
.object({

deno/lib/types.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,16 @@ export abstract class ZodType<
225225
/** The .check method has been removed in Zod 3. For details see https://github.com/colinhacks/zod/tree/v3. */
226226
check!: never;
227227

228-
refine<Func extends (arg: Output) => any>(
229-
check: Func,
228+
refine<RefinedOutput extends Output>(
229+
check: (arg: Output) => arg is RefinedOutput,
230+
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
231+
): ZodEffects<this, RefinedOutput, RefinedOutput>;
232+
refine(
233+
check: (arg: Output) => unknown | Promise<unknown>,
234+
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
235+
): ZodEffects<this, Output, Input>;
236+
refine(
237+
check: (arg: Output) => unknown,
230238
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
231239
): ZodEffects<this, Output, Input> {
232240
const getIssueProperties: any = (val: Output) => {
@@ -264,8 +272,16 @@ export abstract class ZodType<
264272
});
265273
}
266274

275+
refinement<RefinedOutput extends Output>(
276+
check: (arg: Output) => arg is RefinedOutput,
277+
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
278+
): ZodEffects<this, RefinedOutput, RefinedOutput>;
267279
refinement(
268-
check: (arg: Output) => any,
280+
check: (arg: Output) => boolean,
281+
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
282+
): ZodEffects<this, Output, Input>;
283+
refinement(
284+
check: (arg: Output) => unknown,
269285
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
270286
): ZodEffects<this, Output, Input> {
271287
return this._refinement((val, ctx) => {
@@ -289,7 +305,7 @@ export abstract class ZodType<
289305
schema: this,
290306
typeName: ZodFirstPartyTypeKind.ZodEffects,
291307
effect: { type: "refinement", refinement },
292-
}) as any;
308+
});
293309
}
294310
superRefine = this._refinement;
295311

src/__tests__/refine.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @ts-ignore TS6133
22
import { expect, test } from "@jest/globals";
33

4+
import { util } from "../helpers/util";
45
import * as z from "../index";
56
import { ZodIssueCode } from "../ZodError";
67

@@ -48,6 +49,38 @@ test("refinement 2", () => {
4849
).toThrow();
4950
});
5051

52+
test("refinement type guard", () => {
53+
const validationSchema = z.object({
54+
a: z.string().refine((s): s is "a" => s === "a"),
55+
});
56+
type Schema = z.infer<typeof validationSchema>;
57+
58+
const f1: util.AssertEqual<"a", Schema["a"]> = true;
59+
f1;
60+
const f2: util.AssertEqual<"string", Schema["a"]> = false;
61+
f2;
62+
});
63+
64+
test("refinement Promise", async () => {
65+
const validationSchema = z
66+
.object({
67+
email: z.string().email(),
68+
password: z.string(),
69+
confirmPassword: z.string(),
70+
})
71+
.refine(
72+
(data) =>
73+
Promise.resolve().then(() => data.password === data.confirmPassword),
74+
"Both password and confirmation must match"
75+
);
76+
77+
await validationSchema.parseAsync({
78+
email: "aaaa@gmail.com",
79+
password: "password",
80+
confirmPassword: "password",
81+
});
82+
});
83+
5184
test("custom path", async () => {
5285
const result = await z
5386
.object({

src/types.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,16 @@ export abstract class ZodType<
225225
/** The .check method has been removed in Zod 3. For details see https://github.com/colinhacks/zod/tree/v3. */
226226
check!: never;
227227

228-
refine<Func extends (arg: Output) => any>(
229-
check: Func,
228+
refine<RefinedOutput extends Output>(
229+
check: (arg: Output) => arg is RefinedOutput,
230+
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
231+
): ZodEffects<this, RefinedOutput, RefinedOutput>;
232+
refine(
233+
check: (arg: Output) => unknown | Promise<unknown>,
234+
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
235+
): ZodEffects<this, Output, Input>;
236+
refine(
237+
check: (arg: Output) => unknown,
230238
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
231239
): ZodEffects<this, Output, Input> {
232240
const getIssueProperties: any = (val: Output) => {
@@ -264,8 +272,16 @@ export abstract class ZodType<
264272
});
265273
}
266274

275+
refinement<RefinedOutput extends Output>(
276+
check: (arg: Output) => arg is RefinedOutput,
277+
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
278+
): ZodEffects<this, RefinedOutput, RefinedOutput>;
267279
refinement(
268-
check: (arg: Output) => any,
280+
check: (arg: Output) => boolean,
281+
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
282+
): ZodEffects<this, Output, Input>;
283+
refinement(
284+
check: (arg: Output) => unknown,
269285
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
270286
): ZodEffects<this, Output, Input> {
271287
return this._refinement((val, ctx) => {
@@ -289,7 +305,7 @@ export abstract class ZodType<
289305
schema: this,
290306
typeName: ZodFirstPartyTypeKind.ZodEffects,
291307
effect: { type: "refinement", refinement },
292-
}) as any;
308+
});
293309
}
294310
superRefine = this._refinement;
295311

0 commit comments

Comments
 (0)