Skip to content

Commit

Permalink
Initial prototype fix for issue #2651 (#2652)
Browse files Browse the repository at this point in the history
* Initial prototype fix

* Add implementation/test for async

* Fix formatting
  • Loading branch information
ddurschlag committed Aug 15, 2023
1 parent fba438c commit 932cc47
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 16 deletions.
26 changes: 26 additions & 0 deletions deno/lib/__tests__/function.test.ts
Expand Up @@ -29,6 +29,32 @@ test("function inference 1", () => {
util.assertEqual<func1, (k: string) => 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<typeof t1>;
Expand Down
24 changes: 16 additions & 8 deletions deno/lib/types.ts
Expand Up @@ -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<ZodTypeAny>
me._def.returns as unknown as ZodPromise<ZodTypeAny>
)._def.type
.parseAsync(result, params)
.catch((e) => {
Expand All @@ -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)]);
}
Expand Down
26 changes: 26 additions & 0 deletions src/__tests__/function.test.ts
Expand Up @@ -28,6 +28,32 @@ test("function inference 1", () => {
util.assertEqual<func1, (k: string) => 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<typeof t1>;
Expand Down
24 changes: 16 additions & 8 deletions src/types.ts
Expand Up @@ -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<ZodTypeAny>
me._def.returns as unknown as ZodPromise<ZodTypeAny>
)._def.type
.parseAsync(result, params)
.catch((e) => {
Expand All @@ -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)]);
}
Expand Down

0 comments on commit 932cc47

Please sign in to comment.