fabian-hiller / valibot

The modular and type safe schema library for validating structural data 🤖
https://valibot.dev
MIT License
5.88k stars 181 forks source link

Complex error messages using enums #722

Closed shossk closed 1 month ago

shossk commented 1 month ago

Hello, @fabian-hiller

I am very fond of Valibot and am very pleased that it can be integrated into various projects.

There is one thing I don't understand how to do, so I would appreciate it if you could help me.

I want to define an enum so that only certain values pass the check. For other values, I want to display different error messages based on the enum values. Is this possible?

For example:

That’s the kind of image I have in mind.

Here is the code that is close to what I tried.

const EnumSchema = union([
  literal('ValueA'),
  literal('ValueB'),
  literal('ValueC'),
]);

const Schema = object({
  value: pipe(
    EnumSchema,
    check((input) => input === 'ValueA'),
    check((input) => input !== 'ValueB', 'Did you think it was ValueB?'),
    check((input) => input !== 'ValueC', 'Why did you think it was ValueC?'),
  ),
});
devcaeg commented 1 month ago

Check:

https://valibot.dev/api/enum/ https://valibot.dev/api/picklist/

shossk commented 1 month ago

@devcaeg Thank you! I thought the way I was using Enum might be incorrect, but I believed that turning this into a Picklist or Enum wouldn’t directly solve the issue of handling error messages individually. I also checked the documentation, but it seemed there was no method to display error messages separately.

devcaeg commented 1 month ago

You can do it in these two ways, if I am not mistaken, you are on the right track.

enum EnumSchema {
  ValueA = 'ValueA',
  ValueB = 'ValueB',
  ValueC = 'ValueC',
}

const Schema = object({
  value: pipe(
    enum_(EnumSchema),
    check((input) => input === EnumSchema.ValueA),
    check(
      (input) => input !== EnumSchema.ValueB,
      'Did you think it was ValueB?',
    ),
    check(
      (input) => input !== EnumSchema.ValueC,
      'Why did you think it was ValueC?',
    ),
  ),
});

OR

const EnumSchema = ['ValueA', 'ValueB', 'ValueC'] as const;

const Schema = object({
  value: pipe(
    picklist(EnumSchema),
    check((input) => input === 'ValueA'),
    check((input) => input !== 'ValueB', 'Did you think it was ValueB?'),
    check((input) => input !== 'ValueC', 'Why did you think it was ValueC?'),
  ),
});
fabian-hiller commented 1 month ago

Thanks for your help @devcaeg!

shossk commented 1 month ago

@devcaeg

const EnumSchema = ['ValueA', 'ValueB', 'ValueC'] as const;

const Schema = object({
  value: pipe(
    picklist(EnumSchema),
    check((input) => input === 'ValueA'),
    check((input) => input !== 'ValueB', 'Did you think it was ValueB?'),
    check((input) => input !== 'ValueC', 'Why did you think it was ValueC?'),
  ),
});

If you do this, you will get the following message in the case of ValueB message: "Invalid input: Received \"ValueB\"", error should appear. Is there a cleaner answer?

Maybe what I'm looking for is something like zod's ZodErrorMap where you can customize the error message with return.

For some reason, I think the question is coming across as asking how to use Enum. What I want to know is not how to use Enum, but how to separate error messages from correct values when Enum is used. If you have difficulty understanding what I am saying, please let me know. I will describe it in more detail.

fabian-hiller commented 1 month ago

For example:

  • Pass if it is ValueA
  • Do not pass if it is ValueB, and display the error message “Did you think it was ValueB?”
  • Do not pass if it is ValueC, and display the error message “Why did you think it was ValueC?”

If your schema only marks "ValueA" as valid, there is no need for an enum. Check out this playground.

import * as v from 'valibot';

const Schema = v.object({
  value: v.literal('ValueA', (issue) =>
    issue.input === 'ValueB'
      ? 'Did you think it was ValueB?'
      : issue.input === 'ValueC'
        ? 'Why did you think it was ValueC?'
        : issue.message,
  ),
});
shossk commented 1 month ago

@fabian-hiller Thank you very much! It looks like we can make it happen! Ideally, I would like to use enum for literal as well, so that I can't do literal(“ValueD”), and I would like the completion of ValueB and ValueC to work for issue.input as well, is this possible? Also, for future reference, what is in issue.message?

fabian-hiller commented 1 month ago

The function in the above example is executed whenever the input is not "ValueA" to generate the error message Valibot returns.

Also, for future reference, what is in issue.message?

This is the default error message that Valibot creates internally. It is used as a fallback in this schema for any other value.

Ideally, I would like to use enum for literal as well, so that I can't do literal(“ValueD”), and I would like the completion of ValueB and ValueC to work for issue.input as well, is this possible?

I do not fully understand you? Can you give an example or describe the behavior of the schema when different inputs are used?

shossk commented 1 month ago

@fabian-hiller Sorry for the confusion, but I would like to solve the problem of issue.input being unknown in this case, is it impossible?

const EnumSchema = ['ValueA', 'ValueB', 'ValueC'] as const;

const Schema = v.object({
  value: v.pipe(
    v.picklist(EnumSchema),
    v.literal('ValueA', (issue) =>
    issue.input === 'ValueB' // this issue.input type is unkwnon
      ? 'Did you think it was ValueB?'
      : issue.input === 'ValueC'
        ? 'Why did you think it was ValueC?'
        : issue.message,
  )
  ),
});

playground

fabian-hiller commented 1 month ago

Yes, this is possible, but I am not sure if it really makes sense to use picklist or enum in this case. Checkout this playground.

import * as v from 'valibot';

const Schema = v.object({
  value: v.pipe(
    v.picklist(['ValueA', 'ValueB', 'ValueC']),
    v.value('ValueA', (issue) =>
      issue.input === 'ValueB'
        ? 'Did you think it was ValueB?'
        : 'Why did you think it was ValueC?',
    ),
  ),
});
shossk commented 1 month ago

@fabian-hiller @devcaeg Thank you very much. This will now be resolved. Thank you for responding to my question.