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

Lack of flexibility and escape hatches #210

Closed
krzkaczor opened this issue Nov 2, 2020 · 4 comments
Closed

Lack of flexibility and escape hatches #210

krzkaczor opened this issue Nov 2, 2020 · 4 comments

Comments

@krzkaczor
Copy link

I am in the process of turning huge dynamically loaded config (that wasn't validated before and was simply huge interface) into a zod schema. I am running into some issues related to the fundamental lack of flexibility in zod. It seems like in some scenarios I cannot turn off validation in zod entirely.

Code like this:

interface Worker {
	doWork: (services: Service): Promise<void>;
}

services here is a complicated object with a variety of dependencies and I simply don't want to validate it in any way. I am fine with assuming that function provided by the user is correct. Also I don't want to suffer a performance impact by using args and returns. It seems like it's not possible to express this in any way in zod.

  1. Using z.function() will end up being inffered as () => unknown.
z.object({doWork: z.function()})
  1. Using z.any() ends up being inferred as optional property which is not what I wanted ie.
z.object({doWork: z.any().refine(v => v instanceof Function)})

is:

{
	doWork?: any
}

It would be cool to have some way to override type inferred by zod to anything else (it would also allow simple implementation for opaque types). For example:

z.object({doWork: z.function().cast<((services: Service) => Promise<void>)>()})
@colinhacks
Copy link
Owner

You can use cast to z.Schema<T> like this:

const schema: z.Schema<Service> = z.any();

This will allow any input to pass validation and still work with type inference. I'll try to document this better soon. 🤙

@krzkaczor
Copy link
Author

This is perfect! Thanks! I ended up using something like this:

const AnyFunc: z.Schema<(...args: any[]) => any> = z.any().refine((o) => o instanceof Function)

@lucaspiller
Copy link

The same functionality can be used for tagged / branded types. It's not so obvious that you need to refine the z.any() type. For example:

type Tagged<T, Tag> = T & { __tag: Tag };
type UUID = Tagged<string, 'UUID'>;

const uuid: z.Schema<UUID> = z.any().refine(z.string().uuid().parse);

uuid.safeParse('ae6cd9c2-f2e0-43c5-919c-0640b719aacf'); // Success, data has type UUID
uuid.safeParse(5); // Fail
uuid.safeParse('foo'); // Fail

@colinhacks colinhacks mentioned this issue Nov 18, 2020
@colinhacks
Copy link
Owner

@lucaspiller @jraoult

A few things here. First, this can be abbreviated somewhat using z.custom() which is intended for this exact use case. See the code below for usage details.

There's also an error in that implementation. You shouldn't pass z.string().uuid().parse as a refinement function because it throws an error when the input is invalid. Currently errors that occur inside refinements are not caught by Zod. I recommend this approach:

const uuid = z.custom<UUID>(
  val =>
    z
      .string()
      .uuid()
      .safeParse(val).success,
);

console.log(uuid.safeParse('ae6cd9c2-f2e0-43c5-919c-0640b719aacf')); // Success, data has type UUID
console.log(uuid.safeParse(5)); // Fail
console.log(uuid.safeParse('foo')); // Fail

Everything works as expected:

Screen Shot 2020-11-18 at 1 20 16 PM

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

3 participants