8000 Zod 4 enum error messages omitting "received X" part · Issue #4339 · colinhacks/zod · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Zod 4 enum error messages omitting "received X" part #4339

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

Closed
dirkluijk opened this issue May 8, 2025 · 3 comments
Closed

Zod 4 enum error messages omitting "received X" part #4339

dirkluijk opened this issue May 8, 2025 · 3 comments

Comments

@dirkluijk
Copy link
dirkluijk commented May 8, 2025

I wanted to check out the new prettifyError utility of Zod 4. When checking its implementation in @zod/core, I realized it is also compatible with Zod 3; except for a type-cast.

To prove it, I ran the following script:

import { z, ZodError } from "zod";
import { prettifyError } from "@zod/core";

const MyEnum = z.enum(["FOO", "BAR"]);
const MyNativeEnum = z.nativeEnum({ FOO: "FOO", BAR: "BAR" } as const);
const MyUnion = z.union([z.literal("FOO"), z.literal("BAR")]);

const TEST_CASES = [
  [`Enum 'INVALID' (pretty-printed)`, async () => MyEnum.parse("INVALID")],
  [`NativeEnum 'INVALID' (pretty-printed)`, async () => MyNativeEnum.parse("INVALID")],
  [`Union 'INVALID' (pretty-printed)`, async () => MyUnion.parse("INVALID")],
] as const;

async function prettify<T extends string>(fn: () => T | PromiseLike<T>): Promise<T | string> {
  try {
    return await fn();
  } catch (error) {
    if (error instanceof ZodError) {
      return prettifyError(error as any); // type-cast needed when using Zod 3
    }

    throw error;
  }
}

for (const [name, fn] of TEST_CASES) {
  console.log(`${name}:`);
  console.log(await prettify(() => fn()));
  console.log("---");
}

I noticed a small behavior difference.

Zod 3:

Enum 'INVALID' (pretty-printed):
✖ Invalid enum value. Expected 'FOO' | 'BAR', received 'INVALID'
---
NativeEnum 'INVALID' (pretty-printed):
✖ Invalid enum value. Expected 'FOO' | 'BAR', received 'INVALID'
---
Union 'INVALID' (pretty-printed):
✖ Invalid input
---

Zod 4:

Enum 'INVALID' (pretty-printed):
✖ Invalid option: expected one of "FOO"|"BAR"
---
NativeEnum 'INVALID' (pretty-printed):
✖ Invalid option: expected one of "FOO"|"BAR"
---
Union 'INVALID' (pretty-printed):
✖ Invalid input
---

My questions:

  1. Is this difference intended?
  2. Is there a way to get rid of the type-cast?
  3. What is the recommended way to type things and be compatible with both Zod 3 and 4 as peer dependency, or is that simply not recommended?
@colinhacks
Copy link
Owner

The change you're referring to was intentional. Zod has a policy of not logging any user input by default, which is (partially) why there's no input field in issues.

As a compromise, you're now able to access the input inside custom error maps:

const myEnum = z.enum(["a", "b", "c"], {
  error: (iss): string => `Expected ${Object.values(myEnum.def.entries)}, received ${iss.input}`,
});

myEnum.parse("d");
// => 'Expected a,b,c, received d'

Thanks for the idea! I've loosened the input type for prettifyError to make it compatible with both v3 and v4. Very nice :)

Image

--

Re compat: The most recent betas have been significantly restructured to make it easier for projects/libraries to incrementally migrate to Zod 4. Details here: #4364

@dirkluijk
Copy link
Author

Thanks @colinhacks ! ❤

@sharunkumar
Copy link

@colinhacks thanks for the workaround with:

const myEnum = z.enum(["a", "b", "c"], {
  error: (iss): string => `Expected ${Object.values(myEnum.def.entries)}, received ${iss.input}`,
});

is there a way, somehow to apply this globally in v4? with this method it looks like we'd have to assign this param to every enum definition for the intended functionality

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

3 participants
0