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

Add .meta() to zod types #1718

Closed
iway1 opened this issue Dec 17, 2022 · 9 comments
Closed

Add .meta() to zod types #1718

iway1 opened this issue Dec 17, 2022 · 9 comments

Comments

@iway1
Copy link

iway1 commented Dec 17, 2022

Inspecting zod objects directly can be really useful for developing certain types of libraries. Of course tons of libs are already doing it - everything in the zod-to-x section of the documentation for example. Zod already supports some forms of meta data (custom error messages are a sort of metadata), which is extremely useful for things like building forms.

What if it were possible to attach arbitrary information, (or even just a string) via a meta function call? Having an explicit "meta" field could allow library developers to have their code react to zod schemas with much higher levels of customization, which could in turn allow us to improve developer experience in new ways.

The example I have in mind is this - Lets look at a tRPC procedure with a zod schema as an input validator:

getPosts: t.procedure.input(z.object({
  id: z.string().meta("The id of the post."),
  search: z.string().meta("Returns posts with this term in the body").
})).query(() => {
  //...
}),

Here we're attaching a description to each input parameter. Now we can do things like generating documentation directly from our validation schemas. Anyone who's developing a library that inspects zod schemas would have much more flexibility in the types of APIs they'd be able to develop.

It doesn't have to be a string, it could be any other typed object. One solution could be .meta<MetaType>(), or maybe an optional interface for generating a meta enabled client (probably a lot more difficult to create?) export const z = createZodClient<Meta>()

Selfishly, I'm developing a tool that inspects TRPC router and generates a manual testing / documentation UI automatically, which generates forms based on input zod schemas. With a meta function like this, it would allow developers to create documentation per input field with a minimal amount of effort. For me I'd just want a string but IDK if there are use cases for more complex objects or not

Would really love to add something like this, thoughts?

@jrmyio
Copy link

jrmyio commented Dec 19, 2022

I was just checking out a lib called https://github.com/StefanTerdell/zod-to-json-schema and it seems they are converting json schemas's description with using .describe(). .describe() seems to be undocumented but already there. The drawback is that it only seems to support strings atm. It would indeed be very cool if we can define our own meta types.

This issue also seems to request this, but maybe opinions on this have shifted.

This seems to work currently:

const test = z.string().describe(({
   test: "123"
 } as unknown) as string);

 console.log(test.description);

So I wonder if zod actually does anything internally with description other then setting it.

That being said, I think it would be awesome if we have some examples on how to extend the zod core to add our own version of for example .meta<MetaType>() . Are there any hooks or middleware ways to make this happen without having to fork the project?

@maxArturo
Copy link
Contributor

Hi all,

This doesn't seem like a big lift. However, I wonder how prevalent is this use for integrating into other APIs? I certainly see the benefit (esp since other libraries are already weaponizing the .describe()), but I mean specifically the integration path: adding objects to a schema?

If we're going to work on making API integration accessible, I'd love to look at the ecosystem at large and see how other top-shelf libraries open themselves up to extensibility. Is it enough to add arbitrary objects to a schema? Or perhaps is there another introspection utility (maybe off of schema._def) that could be built and would be better suited long term?

@AndreiLucas123
Copy link

AndreiLucas123 commented Dec 19, 2022

It supports .describe() to add a description

But you can add when are creating a schema too like that:

const zodNumber = z.number({
  description: 'My zod number',
})

For me I miss the ability to access if is required or not in the .transform() or in the .refine()

I think zod could fix that by creating a obj meta and add things like required, description to solve that

@samchungy
Copy link
Contributor

samchungy commented Dec 21, 2022

Yeah I understand the opinions in #273. It could very easily lead to anti-patterns however I can also see the benefits of adding a meta field. At the moment I'm using https://github.com/asteasolutions/zod-to-openapi which extends Zod by binding to methods. Wrapping the zodobject eg meta(zodObject, { examples: 1}) feels clunky compared to zodobject.meta()

Would be very useful to be able to add more metadata officially which could be widely used by doco tools. eg. examples, description (I know description is already supported).

@iway1
Copy link
Author

iway1 commented Dec 21, 2022

I'm sure it could be used in pretty dumb ways, but potentially in pretty cool ways as well.

As someone who's built and worked on zod-introspecting packages and who uses zod schemas constantly (for form validation, automatic form construction, and trpc input validation), I can at least attest that having it being natively supported anywhere the schema._def as being very convenient during introspecting, and .meta() being probably the most natural or end-user-developers.

Personally, I'd prefer a way to have .meta to be able to be any object but also be typed upfront, to avoid having to explicitly type each call to .meta, that seems like a great DX and would support any extensions. (Having to do .meta<Type>() would be pretty tedious). I do really like the idea of just being able to create a custom zod instance that comes with a typed meta.

But even just some extra pre-defined meta fields like @samchungy is suggesting would be very useful on their own without opening the door to too much craziness.

@AndreiLucas123
Copy link

Interesting, so zod has a meta like object which is _def

The problem is it loses information whenever the schema is wrapped (.optional(), .nullish(), etc...)

@maxArturo
Copy link
Contributor

@AndreiLucas123 each successive .optional(), et al. call "wraps" the underlying schema in a chain of nested children. So, its all in there but you would have to recurse manually to get to any of it - or build an API around it proper.

In any case it seems @colinhacks came down on one side under #273 (and I can't say I really disagree). Less is more and all that. FWIW, I don't think I'd ever see any other name brand "utility" packages (think of io-ts, lodash, caolan's async etc) catering to downstream package plugins and so on - especially when wrapping them is very easy.

Although! There could be great value in documenting the available internals of zod so that other devs can easily introspect/extend the schemas.

@iway1
Copy link
Author

iway1 commented Dec 23, 2022

@maxArturo I think docs are a great idea here! I sure could've used them. I may write a short-but-potentially-useful guide up soon

Anyways - closing this, thanks for the discussion. Doesn't seem super clear whether meta() is a good idea or not

@iway1 iway1 closed this as completed Dec 23, 2022
@SkyaTura
Copy link

SkyaTura commented Feb 9, 2023

I wish to add some thoughts on this subject, should I do it in this issue, on #273 or create another one?

🤔

RobinTail added a commit to RobinTail/express-zod-api that referenced this issue Jan 10, 2024
I was dreaming about it.
Unfortunately `zod` does not provide any way to make a custom or branded
or third-party schema that can be identified as one programmatically.
Developer of `zod` also does not want to make any method to store
metadata in schemas, instead, recommends to wrap schemas in some other
structures, which is not suitable for the purposes of `express-zod-api`.
There are many small inconvenient things in making custom schema
classes, that I'd like to replace into native methods, and use
`withMeta` wrapper for storing proprietary identifier, so the generators
and walkers could still handle it.

Related issues:

```
colinhacks/zod#1718
colinhacks/zod#2413
colinhacks/zod#273
colinhacks/zod#71
colinhacks/zod#37
```

PR I've been waiting for months to merged (programmatically
distinguishable branding):

```
colinhacks/zod#2860
```
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

6 participants