From ee7f36216c68482b96f59ea3564c41cb050347f4 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Wed, 16 Nov 2022 23:04:02 +0200 Subject: [PATCH 1/6] deny unexpected keys @ `ZodObject.omit(...)` & `ZodObject.pick(...)`. --- src/types.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/types.ts b/src/types.ts index 847014fe8..622de4ac0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1772,6 +1772,12 @@ export type SomeZodObject = ZodObject< any >; +export type objectKeyMask = { [k in keyof Obj]?: true }; + +export type optionalPickWith = { + [k in keyof Obj]?: k extends keyof Shape ? Obj[k] : never; +}; + function deepPartialify(schema: ZodTypeAny): any { if (schema instanceof ZodObject) { const newShape: any = {}; @@ -2015,8 +2021,8 @@ export class ZodObject< }) as any; } - pick( - mask: Mask + pick>( + mask: optionalPickWith> ): ZodObject>, UnknownKeys, Catchall> { const shape: any = {}; util.objectKeys(mask).map((key) => { @@ -2029,8 +2035,8 @@ export class ZodObject< }) as any; } - omit( - mask: Mask + omit>( + mask: optionalPickWith> ): ZodObject, UnknownKeys, Catchall> { const shape: any = {}; util.objectKeys(this.shape).map((key) => { From 95104bf86ba680b028e5c9f995e6820d46977824 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Wed, 16 Nov 2022 23:09:58 +0200 Subject: [PATCH 2/6] make runtime unit tests ignore unexpected key ts error. --- src/__tests__/pickomit.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/__tests__/pickomit.test.ts b/src/__tests__/pickomit.test.ts index f5baa10c6..b512d3303 100644 --- a/src/__tests__/pickomit.test.ts +++ b/src/__tests__/pickomit.test.ts @@ -65,13 +65,13 @@ test("nonstrict inference", () => { }); test("nonstrict parsing - pass", () => { - const laxfish = fish.nonstrict().pick({ name: true }); + const laxfish = fish.passthrough().pick({ name: true }); laxfish.parse({ name: "asdf", whatever: "asdf" }); laxfish.parse({ name: "asdf", age: 12, nested: {} }); }); test("nonstrict parsing - fail", () => { - const laxfish = fish.nonstrict().pick({ name: true }); + const laxfish = fish.passthrough().pick({ name: true }); const bad = () => laxfish.parse({ whatever: "asdf" } as any); expect(bad).toThrow(); }); @@ -84,6 +84,7 @@ test("pick a nonexistent key", () => { const pickedSchema = schema.pick({ a: true, + // @ts-expect-error doesntExist: true, }); From bd0c3003651c1d60dfe18159aa56febcaf96f696 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Wed, 16 Nov 2022 23:14:58 +0200 Subject: [PATCH 3/6] forgot to run yarn build:deno. --- deno/lib/__tests__/pickomit.test.ts | 5 +++-- deno/lib/types.ts | 14 ++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/deno/lib/__tests__/pickomit.test.ts b/deno/lib/__tests__/pickomit.test.ts index f19129995..ac308ad33 100644 --- a/deno/lib/__tests__/pickomit.test.ts +++ b/deno/lib/__tests__/pickomit.test.ts @@ -66,13 +66,13 @@ test("nonstrict inference", () => { }); test("nonstrict parsing - pass", () => { - const laxfish = fish.nonstrict().pick({ name: true }); + const laxfish = fish.passthrough().pick({ name: true }); laxfish.parse({ name: "asdf", whatever: "asdf" }); laxfish.parse({ name: "asdf", age: 12, nested: {} }); }); test("nonstrict parsing - fail", () => { - const laxfish = fish.nonstrict().pick({ name: true }); + const laxfish = fish.passthrough().pick({ name: true }); const bad = () => laxfish.parse({ whatever: "asdf" } as any); expect(bad).toThrow(); }); @@ -85,6 +85,7 @@ test("pick a nonexistent key", () => { const pickedSchema = schema.pick({ a: true, + // @ts-expect-error doesntExist: true, }); diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 34164b734..7e8e5be23 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -1772,6 +1772,12 @@ export type SomeZodObject = ZodObject< any >; +export type objectKeyMask = { [k in keyof Obj]?: true }; + +export type optionalPickWith = { + [k in keyof Obj]?: k extends keyof Shape ? Obj[k] : never; +}; + function deepPartialify(schema: ZodTypeAny): any { if (schema instanceof ZodObject) { const newShape: any = {}; @@ -2015,8 +2021,8 @@ export class ZodObject< }) as any; } - pick( - mask: Mask + pick>( + mask: optionalPickWith> ): ZodObject>, UnknownKeys, Catchall> { const shape: any = {}; util.objectKeys(mask).map((key) => { @@ -2029,8 +2035,8 @@ export class ZodObject< }) as any; } - omit( - mask: Mask + omit>( + mask: optionalPickWith> ): ZodObject, UnknownKeys, Catchall> { const shape: any = {}; util.objectKeys(this.shape).map((key) => { From c6e7b0826c8b6cfee2b261ad86cc0ea410ecfeb8 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Wed, 16 Nov 2022 23:56:02 +0200 Subject: [PATCH 4/6] apply same restrictions for `required(...)` & `partial(...)` masks. --- deno/lib/types.ts | 8 ++++---- src/types.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 7e8e5be23..685f940f5 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -2059,8 +2059,8 @@ export class ZodObject< UnknownKeys, Catchall >; - partial( - mask: Mask + partial>( + mask: optionalPickWith> ): ZodObject< objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? ZodOptional : T[k]; @@ -2100,8 +2100,8 @@ export class ZodObject< UnknownKeys, Catchall >; - required( - mask: Mask + required>( + mask: optionalPickWith> ): ZodObject< objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? deoptional : T[k]; diff --git a/src/types.ts b/src/types.ts index 622de4ac0..c7f11f588 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2059,8 +2059,8 @@ export class ZodObject< UnknownKeys, Catchall >; - partial( - mask: Mask + partial>( + mask: optionalPickWith> ): ZodObject< objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? ZodOptional : T[k]; @@ -2100,8 +2100,8 @@ export class ZodObject< UnknownKeys, Catchall >; - required( - mask: Mask + required>( + mask: optionalPickWith> ): ZodObject< objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? deoptional : T[k]; From 796dd6b083a93193bcc497977bd02c67308b71a6 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Fri, 16 Dec 2022 17:08:41 +0200 Subject: [PATCH 5/6] add "non existent key" tests @ partials & pickomit. --- deno/lib/__tests__/partials.test.ts | 30 +++++++++++++++++++++++++++++ deno/lib/__tests__/pickomit.test.ts | 15 ++++++++++++++- src/__tests__/partials.test.ts | 30 +++++++++++++++++++++++++++++ src/__tests__/pickomit.test.ts | 15 ++++++++++++++- 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/deno/lib/__tests__/partials.test.ts b/deno/lib/__tests__/partials.test.ts index 3cbc00ed4..ba555d65b 100644 --- a/deno/lib/__tests__/partials.test.ts +++ b/deno/lib/__tests__/partials.test.ts @@ -185,6 +185,21 @@ test("required with mask", () => { expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional); }); +test("required with mask containing a nonexistent key", () => { + const object = z.object({ + name: z.string(), + age: z.number().optional(), + field: z.string().optional().default("asdf"), + country: z.string().optional(), + }); + + object.required({ + age: true, + // @ts-expect-error should not accept unexpected keys. + doesntExist: true, + }); +}); + test("partial with mask", async () => { const object = z.object({ name: z.string(), @@ -203,3 +218,18 @@ test("partial with mask", async () => { masked.parse({}); await masked.parseAsync({}); }); + +test("partial with mask containing a nonexistent key", () => { + const object = z.object({ + name: z.string(), + age: z.number().optional(), + field: z.string().optional().default("asdf"), + country: z.string().optional(), + }); + + object.partial({ + age: true, + // @ts-expect-error should not accept unexpected keys. + doesntExist: true, + }); +}); diff --git a/deno/lib/__tests__/pickomit.test.ts b/deno/lib/__tests__/pickomit.test.ts index ac308ad33..ef3ac5371 100644 --- a/deno/lib/__tests__/pickomit.test.ts +++ b/deno/lib/__tests__/pickomit.test.ts @@ -85,7 +85,7 @@ test("pick a nonexistent key", () => { const pickedSchema = schema.pick({ a: true, - // @ts-expect-error + // @ts-expect-error should not accept unexpected keys. doesntExist: true, }); @@ -93,3 +93,16 @@ test("pick a nonexistent key", () => { a: "value", }); }); + +test("omit a nonexistent key", () => { + const schema = z.object({ + a: z.string(), + b: z.number(), + }); + + schema.omit({ + a: true, + // @ts-expect-error should not accept unexpected keys. + doesntExist: true, + }); +}); diff --git a/src/__tests__/partials.test.ts b/src/__tests__/partials.test.ts index bf90791ea..0e642176a 100644 --- a/src/__tests__/partials.test.ts +++ b/src/__tests__/partials.test.ts @@ -184,6 +184,21 @@ test("required with mask", () => { expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional); }); +test("required with mask containing a nonexistent key", () => { + const object = z.object({ + name: z.string(), + age: z.number().optional(), + field: z.string().optional().default("asdf"), + country: z.string().optional(), + }); + + object.required({ + age: true, + // @ts-expect-error should not accept unexpected keys. + doesntExist: true, + }); +}); + test("partial with mask", async () => { const object = z.object({ name: z.string(), @@ -202,3 +217,18 @@ test("partial with mask", async () => { masked.parse({}); await masked.parseAsync({}); }); + +test("partial with mask containing a nonexistent key", () => { + const object = z.object({ + name: z.string(), + age: z.number().optional(), + field: z.string().optional().default("asdf"), + country: z.string().optional(), + }); + + object.partial({ + age: true, + // @ts-expect-error should not accept unexpected keys. + doesntExist: true, + }); +}); diff --git a/src/__tests__/pickomit.test.ts b/src/__tests__/pickomit.test.ts index b512d3303..0c593f60c 100644 --- a/src/__tests__/pickomit.test.ts +++ b/src/__tests__/pickomit.test.ts @@ -84,7 +84,7 @@ test("pick a nonexistent key", () => { const pickedSchema = schema.pick({ a: true, - // @ts-expect-error + // @ts-expect-error should not accept unexpected keys. doesntExist: true, }); @@ -92,3 +92,16 @@ test("pick a nonexistent key", () => { a: "value", }); }); + +test("omit a nonexistent key", () => { + const schema = z.object({ + a: z.string(), + b: z.number(), + }); + + schema.omit({ + a: true, + // @ts-expect-error should not accept unexpected keys. + doesntExist: true, + }); +}); From 6c79e02de5a597b4a3c29aa253df33fe8a800616 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Sat, 24 Dec 2022 02:17:49 -0800 Subject: [PATCH 6/6] Naming tweak --- deno/lib/types.ts | 12 ++++++------ playground.ts | 19 ++++--------------- src/types.ts | 12 ++++++------ 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 685f940f5..1996e6903 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -1774,8 +1774,8 @@ export type SomeZodObject = ZodObject< export type objectKeyMask = { [k in keyof Obj]?: true }; -export type optionalPickWith = { - [k in keyof Obj]?: k extends keyof Shape ? Obj[k] : never; +export type noUnrecognized = { + [k in keyof Obj]: k extends keyof Shape ? Obj[k] : never; }; function deepPartialify(schema: ZodTypeAny): any { @@ -2022,7 +2022,7 @@ export class ZodObject< } pick>( - mask: optionalPickWith> + mask: noUnrecognized ): ZodObject>, UnknownKeys, Catchall> { const shape: any = {}; util.objectKeys(mask).map((key) => { @@ -2036,7 +2036,7 @@ export class ZodObject< } omit>( - mask: optionalPickWith> + mask: noUnrecognized> ): ZodObject, UnknownKeys, Catchall> { const shape: any = {}; util.objectKeys(this.shape).map((key) => { @@ -2060,7 +2060,7 @@ export class ZodObject< Catchall >; partial>( - mask: optionalPickWith> + mask: noUnrecognized> ): ZodObject< objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? ZodOptional : T[k]; @@ -2101,7 +2101,7 @@ export class ZodObject< Catchall >; required>( - mask: optionalPickWith> + mask: noUnrecognized> ): ZodObject< objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? deoptional : T[k]; diff --git a/playground.ts b/playground.ts index 72615f672..11c2c058a 100644 --- a/playground.ts +++ b/playground.ts @@ -1,16 +1,5 @@ -import { z, ZodFormattedError } from "./src"; +import { z } from "./src"; -enum Color { - RED, - GREEN, - BLUE, -} - -console.log(Color[1]); -async function main() { - const schema = z.string().catch("1234"); - const result = await schema.parse(1234); - - console.log(Object.keys(Color)); -} -main(); +z.object({ name: z.string() }).pick({ + name: true, +}); diff --git a/src/types.ts b/src/types.ts index c7f11f588..e923d07c8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1774,8 +1774,8 @@ export type SomeZodObject = ZodObject< export type objectKeyMask = { [k in keyof Obj]?: true }; -export type optionalPickWith = { - [k in keyof Obj]?: k extends keyof Shape ? Obj[k] : never; +export type noUnrecognized = { + [k in keyof Obj]: k extends keyof Shape ? Obj[k] : never; }; function deepPartialify(schema: ZodTypeAny): any { @@ -2022,7 +2022,7 @@ export class ZodObject< } pick>( - mask: optionalPickWith> + mask: noUnrecognized ): ZodObject>, UnknownKeys, Catchall> { const shape: any = {}; util.objectKeys(mask).map((key) => { @@ -2036,7 +2036,7 @@ export class ZodObject< } omit>( - mask: optionalPickWith> + mask: noUnrecognized> ): ZodObject, UnknownKeys, Catchall> { const shape: any = {}; util.objectKeys(this.shape).map((key) => { @@ -2060,7 +2060,7 @@ export class ZodObject< Catchall >; partial>( - mask: optionalPickWith> + mask: noUnrecognized> ): ZodObject< objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? ZodOptional : T[k]; @@ -2101,7 +2101,7 @@ export class ZodObject< Catchall >; required>( - mask: optionalPickWith> + mask: noUnrecognized> ): ZodObject< objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? deoptional : T[k];