Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3 function schemas don't show error for missing fields on returns #341

Closed
mmahalwy opened this issue Mar 19, 2021 · 9 comments
Closed

v3 function schemas don't show error for missing fields on returns #341

mmahalwy opened this issue Mar 19, 2021 · 9 comments

Comments

@mmahalwy
Copy link

mmahalwy commented Mar 19, 2021

Given code:
image

The function returns additional fields that are not on the returns schema. The expected behavior is that TS would throw an error due to key2 being an additional field on the type. Adding strict() does not fix this either

@colinhacks
Copy link
Owner

This is unfortunately an inevitable side-effect of how implement is...implemented. It doesn't force your function to conform to the exact type signature you've defined. Instead it allows any function that extends that type signature. The distinction is subtle and requires understanding of how type inference works in TS. This is necessary to allow type inference of the function's return type if you don't provide a .returns() schema:

z.function().args(z.string()).implement(arg => arg.length);
// inferred type: (arg: string)=>number

@mmahalwy
Copy link
Author

mmahalwy commented Mar 23, 2021

@colinhacks Can there be a implementWithReturns function that would match the returns?

@colinhacks
Copy link
Owner

colinhacks commented Mar 23, 2021

I just played around with this ideas and I can't get it to work. Here are the implementations of implement and strictImplement (my preferred name for implementWithReturns):

  implement = <F extends InnerTypeOfFunction<Args, Returns>>(func: F): F => {
    const validatedFunc = this.parse(func);
    return validatedFunc as any;
  };

  strictImplement = (
    func: InnerTypeOfFunction<Args, Returns>
  ): InnerTypeOfFunction<Args, Returns> => {
    const validatedFunc = this.parse(func);
    return validatedFunc as any;
  };

Even using strictImplement, your issue isn't resolved:

const func = z
  .function()
  .args(z.string())
  .returns(z.object({ name: z.string() }))
  .strictImplement((val) => {
    return { name: val, asdf: 1234 };
  });

This compiles just fine:
Screen Shot 2021-03-23 at 12 31 34 PM

I'm afraid you're hitting against a fundamental property of structural type systems like TypeScript. Consider this:

type MyFunc = (arg: string) => { name: string };
const myFunc: MyFunc = (arg) => {
  return { name: arg, extraKey: 1234 };
};

This also compiles without error, even with the strictest compilation settings. I don't think this is possible.

@mmahalwy
Copy link
Author

@colinhacks oh yeah, that's strange. Your example at the bottom should error, but TS allows it :S. Let me do some digging to whether there is an issue on the TS side for this.

@mmahalwy
Copy link
Author

mmahalwy commented Mar 25, 2021

This does error:

type CustomType = { name: string };

const myFunc = (arg): CustomType => {
  return { name: arg, extraKey: 1234 };
};

image

Tracked here: microsoft/TypeScript#43365

@mmahalwy
Copy link
Author

@colinhacks reporting back here, this issue is getting some 👀 from the TS type: microsoft/TypeScript#43365.

TLDR; have to explicitly set the return type for this to work ATM

type CustomType = { name: string };

const myFunc = (arg): CustomType => {
  return { name: arg, extraKey: 1234 };
};

A middle ground to work for this lib is to infer and set the return type for the function inside of implement. Kinda meh about that :S

@colinhacks
Copy link
Owner

Yeah not ideal. This isn't terrible though:

const func = z
  .function()
  .args(z.string())
  .returns(z.object({ name: z.string() }));

func.implement((arg): ReturnType<z.infer<typeof func>> => {
  // stuff
});

@mmahalwy
Copy link
Author

Ah interesting. I was thinking of this:

export const func = z
  .function()
  .args(z.string())
  .returns(TestSchema)
  .implement((arg1): z.infer<typeof TestSchema> => {
    // write code
  })

@colinhacks
Copy link
Owner

Yep either one is good.

Note that if TestSchema contains transforms you'll want to use z.input instead of z.infer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants