Skip to content
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

Zod: Union of literals is hard to process #4694

Closed
xak2000 opened this issue Mar 8, 2024 · 2 comments
Closed

Zod: Union of literals is hard to process #4694

xak2000 opened this issue Mar 8, 2024 · 2 comments

Comments

@xak2000
Copy link

xak2000 commented Mar 8, 2024

Is your feature request related to a problem? Please describe.

I'm trying to validate simple union of literals with zod:

const schema = z.union([z.literal('MALE'), z.literal('FEMALE')]);
const result = schema.parse(undefined);

The result of such parse is this error:

[
  {
    "code": "invalid_union",
    "unionErrors": [
      {
        "issues": [
          {
            "code": "invalid_literal",
            "expected": "MALE",
            "path": [],
            "message": "Invalid literal value, expected \"MALE\""
          }
        ],
        "name": "ZodError"
      },
      {
        "issues": [
          {
            "code": "invalid_literal",
            "expected": "FEMALE",
            "path": [],
            "message": "Invalid literal value, expected \"FEMALE\""
          }
        ],
        "name": "ZodError"
      }
    ],
    "path": [],
    "message": "Invalid input"
  }
]

The result of VeeValidate is the combined list of all validation errors for every literal + the error for the union itself.
E.g.

['Invalid literal value, expected "MALE"', 'Invalid literal value, expected "FEMALE"', 'Invalid input']

It doesn't make sense to show any errors for invalid literals in the UI. The only error that makes sense is "The field is required" or "The field must be one of: MALE, FEMALE" (depending on the value of the field: empty or not).

It is even less sense to show 3 errors instead of 1 for this case. Logically this is just 1 of 2 possible errors from the user's point of view: the gender value is not selected or selected value is invalid (the last case is actually not possible, as the UI is <select> component with predefined values in this case, but it is not relevant to the issue). But only 1 of these 2 errors can be active at any time.

Describe the solution you'd like

I have no idea how to solve this. Just started to use both zod and VeeValidate. Probably, I just need to ignore every "literal" error and show just the last error from the union (I could redefine the message). But it looks like it is impossible to ignore inner literal errors, as they are flatmapped into the array of errors (implemented by #4204).

The only workaround I see now is to do something like this:

const genderErrorMap = () => ({ message: 'Required' });
const schema = z.union(
  [
    z.literal('MALE', { errorMap: genderErrorMap }),
    z.literal('FEMALE', { errorMap: genderErrorMap }),
  ],
  { errorMap: genderErrorMap }
);
const result = schema.parse(undefined);

So, basically, show the same error message for every literal and for the union itself. The problem of showing 3 errors instead of 1 (if an UI supports this) still persist, though.

At this point it looks like the only real workaround is to not use union at all and use string() instead with refine() to check if selected value is one of allowed values. I.e. like this:

const schema = z
  .string()
  .min(1, 'Required')
  .refine((s) => ['MALE', 'FEMALE'].includes(s), 'Must be MALE or FEMALE');

What do you think? Is it really the best option for such case?

Describe alternatives you've considered

I considered switching to yup from zod if this would solve this problem elegantly. So, if you think yup can solve this (without falling back to refine() or similar thing), I would consider to use it instead.

@logaretm
Copy link
Owner

logaretm commented Mar 9, 2024

It doesn't make sense to show any errors for invalid literals in the UI

In your case maybe, however vee-validate cannot make that assumption. It is up to you to make a Zod schema that can parse the form values in order for it to become valid. vee-validate is in no position to decide which errors to ignore and which to keep.

I use refines most of the time as unions tend to be a hit or miss due to it not exactly expressing the form nature of the values and Zod being TypeScript influenced.

I use yup since it has better error messages and overall feels suitable to forms more than Zod to be honest. I don't think this is a bug vee-validate should fix.

@logaretm logaretm closed this as not planned Won't fix, can't repro, duplicate, stale Mar 9, 2024
@xak2000
Copy link
Author

xak2000 commented Mar 9, 2024

In your case maybe, however vee-validate cannot make that assumption.

I can't imageine a case when you need more than 1 error for a union of literals. Union of literals literally means that the value should be one of X, Y, Z. It doesn't make sense to have a separate error message for each literal. Even if value is not X, it could still be Y or Z. So, literal X should not generate error if value is not X. Because it is a union after all.

But this is another topic, of course. I would rather call it a problem of zod, not VeeValidate.

I also don't think it is a bug of vee-validte. Rather, it is usability problem caused by mismatch of concepts. In current state a zod schema, that represents union of literals is basically unusable with vee-validate.

Thank you for the response! I will stick with refine for now. Also I quickly researched how this problem could be solved with yup and found out that yup doesn't support unions at all. So, probably migrating to yup will not help as the solution will be similar to zods refine anyway.

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

2 participants