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

Custom (runtime) error message #647

Closed
IlyaSemenov opened this issue Jun 12, 2024 · 6 comments
Closed

Custom (runtime) error message #647

IlyaSemenov opened this issue Jun 12, 2024 · 6 comments
Assignees
Labels
question Further information is requested

Comments

@IlyaSemenov
Copy link
Contributor

As a developer using Valibot, I often want to generate validation error messages in runtime based on user input or other data. I didn't find a documented way to do so. In all examples, the error messages are predefined/hardcoded.

Consider the simplified schema:

import * as v from "valibot"

const schema = v.pipe(v.string(), v.check((x) => {
  const [c1, c2, c3] = x
  if (c1 === "f" && c2 !== c3) {
    const message = `Since the first letter is "${c1}", the two next letters ("${c2}" and "${c3}") should equal.`
    // <--- How to pass this message to res.issues?
    return false
  }
  return true
}))

const res = v.safeParse(schema, "fox")
console.log(res)

Throwing Error or v.ValiError doesn't work, the error is not captured and safeParse throws itself. Besides, ValiError is not developer-friendly at all (one is supposed to pass 6 (!) non-optional non-obvious arguments).

What I would expect, is some helper available in v.check and v.transform callbacks, such as:

throw v.createError({ message, expected, ... }) // only message is required
throw v.createError(message) // shortcut for string message only
@fabian-hiller
Copy link
Owner

fabian-hiller commented Jun 12, 2024

Instead of a string, you can also pass a function to any schema and validation action to dynamically generate the error message. The first argument of this function gives you access to the issue object. Also, I will soon start working on a refine action (issue #597) that will give you full control for such advanced cases.

const Schema = v.string((issue) => `Expected: ${issue.expected}`);

@fabian-hiller fabian-hiller self-assigned this Jun 12, 2024
@fabian-hiller fabian-hiller added the question Further information is requested label Jun 12, 2024
@IlyaSemenov
Copy link
Contributor Author

Thanks you for the heads up. However, this way of dynamically generating messages is quite limited. What I mean is, arguably, more often than not these dynamic messages will be based on validation context. In my example above, the message depends on extracted characters. This way, I basically need to repeat the logic twice, first in check and then in the message generator.

In my real life use case, I am validating a textarea value which is supposed to store the whitespace delimited list of wallets where each wallet must be a valid base58-encoded public key. The validation is supposed to report the particular malformed string, or which keys are duplicate, or when it's not enough or too many of them and why — all and all, that are mostly context-dependent messages.

I hope refine will make this possible!

@fabian-hiller
Copy link
Owner

Please have a look if rawCheck solves this issue for you: #597 (comment)

@IlyaSemenov
Copy link
Contributor Author

It does:

const schema = v.pipe(v.string(), v.rawCheck(({ dataset, addIssue }) => {
  if (dataset.typed) {
    const [c1, c2, c3] = dataset.value
    if (c1 === "f" && c2 !== c3) {
      addIssue({ message: `Since the first letter is "${c1}", the second and third letters ("${c2}" and "${c3}") should equal.` })
    }
  }
}))

I'm still not really following why do we need the obligatory if (dataset.typed) boilerplate in each and every check, but at least it gets things done, thank you.

@fabian-hiller
Copy link
Owner

It does

I think I was able to simplify your scheme. However, your version is probably a bit more readable.

import * as v from 'valibot';

const Schema = v.pipe(
  v.string(),
  v.check(
    ([c1, c2, c3]) => c1 !== 'f' || c2 == c3,
    ({ input: [c1, c2, c3] }) =>
      `Since the first letter is "${c1}", the second and third letters ("${c2}" and "${c3}") should equal.`,
  ),
);

I'm still not really following why do we need the obligatory if (dataset.typed) boilerplate in each and every check, but at least it gets things done, thank you.

This is because I am working on validation actions that allow you to validate untyped data if the part of the data you want to validate is typed. This is really important for form validation. Without this functionality, we can't show errors that depend on multiple fields if any other field is untyped.

@IlyaSemenov
Copy link
Contributor Author

Thank you for the reply. Anyhow, this issue is fully resolved so I'm closing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants