zod icon indicating copy to clipboard operation
zod copied to clipboard

wrap internal errors from `preprocess` and `refine`

Open Malkiz opened this issue 2 years ago • 2 comments

As explained here zod intentionally does not catch errors from within preprocess and refine.

This is behaving as intended currently. An error in a refinement is considered an application-level bug, not a validation error. The thinking is that catching this error in Zod would make it harder to debug actual real-life bugs, and you can always use try/catch. But if Zod catches these errors, there's no way to opt-out of this and allow errors to bubble up.

However, consider this schema:

z.object({ a: z.array(z.object({ b: z.preprocess(data => { throw new Error('asdf') }, z.date()) }))}).parse({a: [{}]})

The error will be:

Uncaught Error: asdf
    at Object.transform (REPL12:1:66)
    at ZodEffects._parse (/node_modules/zod/lib/types.js:2137:38)
    at ZodObject._parse (/node_modules/zod/lib/types.js:1070:37)
    at ZodObject._parseSync (/node_modules/zod/lib/types.js:109:29)

This makes it difficult to know where the error came from. Consider an object schema with many properties (that may also be nested) - if I get this error I don't know which property caused it.

In contrast, the regular zod parsing errors are amazing:

z.object({ a: z.array(z.object({ b: z.date()})) }).parse({a: [{}]})

Error:

[
  {
    "code": "invalid_type",
    "expected": "date",
    "received": "undefined",
    "path": [
      "a",
      0,
      "b"
    ],
    "message": "Required"
  }
]

It tells me exactly the path to the problem in my object.

Proposed solution

I propose that zod will catch my internal error and wrap it in a zod error (and re-throw it) that will at least tell me where the error occurred in the object.

For example something like:

z.object({ a: z.array(z.object({ b: z.preprocess(data => { throw new Error('asdf') }, z.date()) }))}).parse({a: [{}]})

Expected error:

[
  {
    "code": "preprocess_error",
    "path": [
      "a",
      0,
      "b"
    ],
    "message": "Uncaught Error: asdf",
    "originalError": <the original error object>
  }
]

Malkiz avatar Sep 11 '23 06:09 Malkiz

But allow the behaviour of the "rethrow" to be customizable. I.e. personally I wouldn't want to use the ZodError class at all, and to use my own errors.

zackarydev avatar Nov 23 '23 23:11 zackarydev

I would like to suggest a above PR (https://github.com/colinhacks/zod/pull/3314) as a possible solution for this issue. We could create a similar implementation for refinement as well.

shaharke avatar Mar 09 '24 09:03 shaharke