ecyrbe / zodios

typescript http client and server with zod validation
https://www.zodios.org/
MIT License
1.71k stars 46 forks source link

Zodios request parameter type inference not working as expected #315

Closed vmgarcia closed 1 year ago

vmgarcia commented 1 year ago

The type inference generated from the code below does not work as expected.

export const parameters = makeParameters([
  {
    name: 'userId',
    description: "The user's id.",
    schema: userSchema.pick({ id: true }),
    type: 'Body',
  },
  {
    name: 'newTask',
    description: 'The definition of the new task being created.',
    schema: taskSchema.omit({ id: true, createdAt: true }),
    type: 'Body',
  },
]);

export const createTask = makeEndpoint({
  method: 'post',
  path: '/tasks',
  requestFormat: 'json',
  response: taskSchema,
  alias: 'createTask',
  description: 'Create a new task for a user',
  parameters,
});

export const taskRouter = zodiosRouter(taskApi);

taskRouter.post('/tasks', async (req, res) => {
  const { userId, newTask } = req.body;
  const nextId = uuidv4();

  const task: Task = {
    ...newTask,
    id: nextId,
  };
  res.json(task);
});

I expect req.body to be equivalent to

type body = {
  userId: z.infer<userSchema.pick({ id: true })>;
  newTask: z.infer<taskSchema.omit({ id: true, createdAt: true })>
 };

Instead it's

type body = z.infer<userSchema.pick({ id: true })> | z.infer<taskSchema.omit({ id: true, createdAt: true })>

I'm not sure if this is the intended behavior, it's not intuitive or explained in the docs.

As a workaround I'm just using

export const parameters = makeParameters([
  {
    name: 'body',
    description: 'The body of the create task request.',
    schema: z.object({
      userId: userSchema.pick({ id: true }),
      newTask: taskSchema.omit({ id: true, createdAt: true }),
    }),
    type: 'Body',
  },
]);
ecyrbe commented 1 year ago

Oh, body should only be declared once if you want to have them merged. The double declaration is for unions (one body or another). You can declare it like this :

export const parameters = makeParameters([
  {
    name: 'UserNewTask',
    description: "'The definition of the new task being created",
    schema: z.object({ userId: z.number()}).merge(askSchema.omit({ id: true, createdAt: true })),
    type: 'Body',
  }
]);

would you like to propose an improvement to documentation to explain this behaviour with more exemples ? Improving the docs is straightforward. All is in markdown in /website directory. I would love to have users help improve the docs with concrete exemples.