ecyrbe / zodios-express

zodios express server
MIT License
55 stars 8 forks source link

transform flag is needed to use refine / superRefine for schema validation. #303

Open DToppingWW opened 1 week ago

DToppingWW commented 1 week ago

Hi,

I've just been debugging why my Zod wasn't working with the zodios-express library. And it looks like by default zodios-express removes any validation added by refine and superRefine callbacks.

I was wondering if this was intentional? And if it was, can it be documented please?

For me the fix was to enable the transform flag on the zodiosApp function options so that the original "request" schema is used without any modifications by the library.

This turned out to be a trivial fix, but it wasn't very intuitive why it worked this way in the library as I did need to dig into why my validation schema didn't work out of the box.

Example:

Dependency versions:

zod = 3.23.8 @zodios/core = 10.9.6 @zodios/express = 10.6.1

// schema.ts
import { z } from 'zod';

export const myRequestSchema = z
  .object({
    needsA: z.boolean(),
    data: z.record(z.string(), z.string()),
  })
  .refine(({ needsA, data }) => {
    return !needsA || Object.keys(data).includes('a');
  }, "Property 'a' is expected within 'data' record when 'needsA' is enabled.");
// api.ts
import { myRequestSchema } from './schema';
import { z } from 'zod';
import { makeApi } from '@zodios/core';
import { zodiosApp } from '@zodios/express';

const app = zodiosApp(
  makeApi([
    {
      method: 'post',
      path: '/my-request',
      response: z.any(),
      alias: 'myRequest',
      parameters: [
        {
          name: 'body',
          type: 'Body',
          schema: myRequestSchema,
        },
      ],
    },
  ])
);

app.post('/my-request', (req, res) => {
  if (req.body.needsA) {
    // Note: this error shouldn't happen, if caught by the validation logic in the Zod schema.
    if (req.body.data.a == null) throw new Error('This has broken');
  }

  res.json({ message: 'All good' });
});

const PORT = 3001;
app.listen(PORT, () => {
  console.log(`Listening on port ${PORT}...`);
});
{
   "needsA": true,
   "data": {}
}

should fail validation. But it doesn't, unless options of {transform: true} are passed to zodiosApp.