trpc-openapi icon indicating copy to clipboard operation
trpc-openapi copied to clipboard

RFC: Support `number`, `boolean`, `Date` in query params

Open jqhoogland opened this issue 2 years ago • 8 comments

Right now it's not possible to have z.number()'s in your input schema for GET requests. That makes sense since query params are strings (so you get an error like Input parser key: "limit" must be ZodString).

But often (I'm thinking especially of pagination), you need numbers. Right now, you can recreate similar functionality with z.string().transform((s) => parseInt(s), but it's not ideal since you lose, for example, Zod's built-in min/max validation.

At a minimum, this deserves a mention in the documentation. If we're feeling more ambitious, we can introduce autoconversions to number/date for common types.

I'm happy to open a PR, but first wanted to hear what you think, @jlalmes.

jqhoogland avatar Jul 05 '22 07:07 jqhoogland

Hi @jqhoogland!

You can use z.preprocess (https://github.com/colinhacks/zod#preprocess) to transform an input type while keeping Zod's built-in validations (i.e. min/max).

I do like the proposal to support number, Date AND boolean out of the box - this will help with frictionless trpc-openapi adoption.

I suggest using the following preprocessors. Let me know if you're keen to open a PR 🙌

// number
z.preprocess((arg) => {
  if (typeof arg === 'string') {
    return parseInt(arg);
  }
  return arg;
}, z.number() /* 👈 you can use .min()/max() validations here */)

// Date
z.preprocess((arg) => {
  if (typeof arg === "string") {
    return new Date(arg);
  }
  return arg;
}, z.date())

// boolean
z.preprocess((arg) => {
  if (typeof arg === "string") {
    if (arg === 'true' || arg === '1') {
      return true;
    }
    if (arg === 'false' || arg === '0') {
      return false;
    }
  }
  return arg;
}, z.boolean())

jlalmes avatar Jul 05 '22 09:07 jlalmes

Awesome, I'll take a look this weekend!

jqhoogland avatar Jul 06 '22 21:07 jqhoogland

Wait I see you've already opened a branch and gone crazy. You are a hero with those edge cases!

jqhoogland avatar Jul 06 '22 21:07 jqhoogland

So I've taken a stab at implementing this (not entirely sure I want to ship this feature yet). Would be great if you could take a look at the PR + code review.

https://github.com/jlalmes/trpc-openapi/pull/53

I have published the current state, you can try it out by upgrading to [email protected]

jlalmes avatar Jul 07 '22 18:07 jlalmes

Hey there! Loving this project and it's already been super useful for me.

I wanted to comment that I think this would be an awesome feature - I was very confused coming in that these other types were not supported out of the box. The z.preprocess workaround will work great in the meantime though, I'm happy to have some way around it.

Thanks for all that you're doing!

ahoopes16 avatar Jul 15 '22 16:07 ahoopes16

The z.preprocess needs to be inside the resolve or in the input schema?

drenther avatar Jul 22 '22 11:07 drenther

Example:

.query('getUsers', {
  input: z.object({
    limit: z.preprocess((arg) => (typeof arg === 'string' ? parseInt(arg) : arg), z.number().min(1).max(50)),
  }),
  /* ... */
})

jlalmes avatar Jul 22 '22 11:07 jlalmes

Note to self: https://twitter.com/ecyrbedev/status/1567953183120699392

jlalmes avatar Sep 08 '22 21:09 jlalmes

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Nov 07 '22 22:11 stale[bot]