From 932cc472d2e66430d368a409b8d251909d7d8d21 Mon Sep 17 00:00:00 2001 From: ddurschlag Date: Tue, 15 Aug 2023 15:38:55 -0400 Subject: [PATCH] Initial prototype fix for issue #2651 (#2652) * Initial prototype fix * Add implementation/test for async * Fix formatting --- deno/lib/__tests__/function.test.ts | 26 ++++++++++++++++++++++++++ deno/lib/types.ts | 24 ++++++++++++++++-------- src/__tests__/function.test.ts | 26 ++++++++++++++++++++++++++ src/types.ts | 24 ++++++++++++++++-------- 4 files changed, 84 insertions(+), 16 deletions(-) diff --git a/deno/lib/__tests__/function.test.ts b/deno/lib/__tests__/function.test.ts index 4737afb72..45031fb09 100644 --- a/deno/lib/__tests__/function.test.ts +++ b/deno/lib/__tests__/function.test.ts @@ -29,6 +29,32 @@ test("function inference 1", () => { util.assertEqual number>(true); }); +test("method parsing", () => { + const methodObject = z.object({ + property: z.number(), + method: z.function().args(z.string()).returns(z.number()) + }); + const methodInstance = { + property: 3, + method: function(s: string) { return s.length + this.property; } + }; + const parsed = methodObject.parse(methodInstance); + expect(parsed.method('length=8')).toBe(11); // 8 length + 3 property +}); + +test("async method parsing", async () => { + const methodObject = z.object({ + property: z.number(), + method: z.function().args(z.string()).returns(z.promise(z.number())) + }); + const methodInstance = { + property: 3, + method: async function(s: string) { return s.length + this.property; } + }; + const parsed = methodObject.parse(methodInstance); + expect(await parsed.method('length=8')).toBe(11); // 8 length + 3 property +}); + test("args method", () => { const t1 = z.function(); type t1 = z.infer; diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 0740927fa..8743f4154 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -3736,17 +3736,21 @@ export class ZodFunction< const fn = ctx.data; if (this._def.returns instanceof ZodPromise) { - return OK(async (...args: any[]) => { + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(async function (this: any, ...args: any[]) { const error = new ZodError([]); - const parsedArgs = await this._def.args + const parsedArgs = await me._def.args .parseAsync(args, params) .catch((e) => { error.addIssue(makeArgsIssue(args, e)); throw error; }); - const result = await fn(...(parsedArgs as any)); + const result = await Reflect.apply(fn, this, parsedArgs as any); const parsedReturns = await ( - this._def.returns as unknown as ZodPromise + me._def.returns as unknown as ZodPromise )._def.type .parseAsync(result, params) .catch((e) => { @@ -3756,13 +3760,17 @@ export class ZodFunction< return parsedReturns; }); } else { - return OK((...args: any[]) => { - const parsedArgs = this._def.args.safeParse(args, params); + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(function (this: any, ...args: any[]) { + const parsedArgs = me._def.args.safeParse(args, params); if (!parsedArgs.success) { throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); } - const result = fn(...(parsedArgs.data as any)); - const parsedReturns = this._def.returns.safeParse(result, params); + const result = Reflect.apply(fn, this, parsedArgs.data); + const parsedReturns = me._def.returns.safeParse(result, params); if (!parsedReturns.success) { throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); } diff --git a/src/__tests__/function.test.ts b/src/__tests__/function.test.ts index 3767e867c..b6a572700 100644 --- a/src/__tests__/function.test.ts +++ b/src/__tests__/function.test.ts @@ -28,6 +28,32 @@ test("function inference 1", () => { util.assertEqual number>(true); }); +test("method parsing", () => { + const methodObject = z.object({ + property: z.number(), + method: z.function().args(z.string()).returns(z.number()) + }); + const methodInstance = { + property: 3, + method: function(s: string) { return s.length + this.property; } + }; + const parsed = methodObject.parse(methodInstance); + expect(parsed.method('length=8')).toBe(11); // 8 length + 3 property +}); + +test("async method parsing", async () => { + const methodObject = z.object({ + property: z.number(), + method: z.function().args(z.string()).returns(z.promise(z.number())) + }); + const methodInstance = { + property: 3, + method: async function(s: string) { return s.length + this.property; } + }; + const parsed = methodObject.parse(methodInstance); + expect(await parsed.method('length=8')).toBe(11); // 8 length + 3 property +}); + test("args method", () => { const t1 = z.function(); type t1 = z.infer; diff --git a/src/types.ts b/src/types.ts index 81a5e9f8a..bdcf16662 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3736,17 +3736,21 @@ export class ZodFunction< const fn = ctx.data; if (this._def.returns instanceof ZodPromise) { - return OK(async (...args: any[]) => { + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(async function (this: any, ...args: any[]) { const error = new ZodError([]); - const parsedArgs = await this._def.args + const parsedArgs = await me._def.args .parseAsync(args, params) .catch((e) => { error.addIssue(makeArgsIssue(args, e)); throw error; }); - const result = await fn(...(parsedArgs as any)); + const result = await Reflect.apply(fn, this, parsedArgs as any); const parsedReturns = await ( - this._def.returns as unknown as ZodPromise + me._def.returns as unknown as ZodPromise )._def.type .parseAsync(result, params) .catch((e) => { @@ -3756,13 +3760,17 @@ export class ZodFunction< return parsedReturns; }); } else { - return OK((...args: any[]) => { - const parsedArgs = this._def.args.safeParse(args, params); + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(function (this: any, ...args: any[]) { + const parsedArgs = me._def.args.safeParse(args, params); if (!parsedArgs.success) { throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); } - const result = fn(...(parsedArgs.data as any)); - const parsedReturns = this._def.returns.safeParse(result, params); + const result = Reflect.apply(fn, this, parsedArgs.data); + const parsedReturns = me._def.returns.safeParse(result, params); if (!parsedReturns.success) { throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); }