-
-
Notifications
You must be signed in to change notification settings - Fork 220
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 error message of missing object entries changed in v1.0.0-beta.13 #1034
Comments
I have investigated this issue. Theoretically, we can easily support the old behavior and return an issue of the nested field instead of an object issue for required fields that are missing. But there are some drawbacks. The new and current behavior is objectively more correct. There is a difference between a missing property and a present but undefined property. A missing property is something that the object schema should detect, because only the object schema has this information and knows its structure. Therefore, it makes sense that an object issue is returned and not the nested schema issue. This becomes especially clear when using import * as v from 'valibot';
const Schema = v.object({ key1: v.any(), key2: v.unknown() });
// ERROR: Type '{}' is missing the following properties from type '{ key1: any; key2: unknown; }': key1, key2
const input: v.InferInput<typeof Schema> = {}; In this case, our schema should return an issue for both properties if they are missing to match the behavior of TypeScript. Our old implementation would ignore this case and treat an empty object as a valid input for the given schema, which is wrong according to the given TypeScript error message. The tricky part is that we can't return an These concerns made me think that the "problem" of this issue might not be our new internal implementation, but the user-defined schema. I may be wrong, but could it be that, for example, you want to validate that the input passed is not an empty string whether the object property is provided or not? If so, you could define that by changing your schema: import * as v from 'valibot';
const Schema = v.object({
field: v.optional(v.pipe(v.string(), v.nonEmpty('My custom error')), ''),
}); Now the schema defaults to an empty string for missing properties and returns an issue if this does not match any further validation rules. I know that this is a lot more code to write, and that the old solution was much simpler. That's why I'm interested in hearing your feedback and investigating this issue further. |
v1.0.0-beta.13
I think the new and current implementation should be kept. We should figure out how to let users customize the error message for each missing key in the object. In the last code block of your last message, the users won't be able to customize the "missing key" messages, as any custom message inside an entry's schema would indicate "the input has the key and validation failed" instead of "the input did not contain the key". Maybe there are cases where the author of the schema wants to provide a different message for the "missing key" and "validation failed" cases easily. What if we add a way to customize each "missing key" error message like shown in the example below? import * as v from 'valibot';
const Schema = v.object({
key1: [
v.pipe(
v.string('`key1` should be a string'),
v.nonEmpty('`key1` cannot be empty'),
),
'`key1` is required',
],
key2: [
v.pipe(
v.string('`key2` should be a string'),
v.nonEmpty('`key2` cannot be empty'),
),
'`key2` is required',
],
// If `key3` is missing, it will use object's error message
key3: v.number('`key3` should be a number')
}); |
It seems like we have 3 options:
Options 2 and 3 make things more complicated and increase the bundle size. I would probably wait for more feedback before changing the behavior or API. |
Hi, |
Thank you for your feedback! Can you provide more details and maybe some example code? |
i prefer the old behavior, i think most agree with me |
I like the new behavior, i also think this is the most correct to have. The first would require a capitalize function call to display in frontend app (Maybe we could have an utility function like const ObjectSchema = v.strictObject(
{
name: v.string(),
email: v.string(),
},
(issue) => (issue.path ? `${issue.path[0].key} is required` : 'Unknown error')
); And this one require us to keep in sync this object and the schema (But it may be build in to ensure type safety ?): import * as v from "valibot";
function message(messages: Record<string, string>) {
return (issue: v.BaseIssue<unknown>): string => {
return messages[(issue.path?.[0].key as string) ?? ''] ?? issue.message;
};
}
const ObjectSchema = v.strictObject(
{
name: v.string(),
email: v.string(),
},
message({
name: 'Name is required',
email: 'Email is required',
})
); That said, the old behavior have the advantage to keep it simple for the author so it makes difficult to me to choose between option 1 or 2... |
I will look again at ways to get the best of both worlds. I have an idea in mind. I will get back to all of you here as soon as possible. |
I am still investigating this issue. Please provide a 👍 or 👎 if the following workaround is an option for you. I only ask because I am currently investigating several changes to the current behavior, but they all have other drawbacks. A lot of cases where you might want to set a specific custom message for a missing object property is when working with forms. The reason for this is that you may not have control over how untouched but required form fields are handled. Some form libraries don't initialize them, resulting in a missing object property when representing the form state as an object. A workaround could be to set the form field as optional in your Valibot schema, but provide an initial value, for example an empty string, as the default value to be used as input to the schema whenever the actual object property is missing or undefined. Now you can use a validation action like import * as v from 'valibot';
const Schema = v.object({
email: v.pipe(
v.optional(v.string(), ''), // <- Empty string as default value
v.nonEmpty('Please enter your email.'),
v.email('The email is badly formatted.'),
),
}); |
Zod provides 2 kinds of messages.
z.object({
name: z.string({ required_error: "Please let me know your name.", invalid_type_error: "The name should be string." }),
birth_year: z.number().optional()
}) The suggested way will be able to cover the former, but how can or should we easily customize the latter in Valibot? |
Here is a Valibot schema with a similar behavior. You can try it out in this playground.
import * as v from 'valibot';
const Schema = v.object({
name: v.pipe(
v.optional(v.string('The name should be string.'), () => undefined),
v.string('Please let me know your name.'),
),
birth_year: v.optional(v.number()),
}); Also keep in mind that Valibot allows you to read the actual input when generating an error message.
import * as v from 'valibot';
const Schema = v.object({
name: v.string(({ input }) =>
input ? 'The name should be string.' : 'Please let me know your name.'
),
}); |
This works nicely but I find the use of v.optional to not be very readable/clear. It indicates to the reader that this property is now optional. I realize it may not fit correctly with the rest of the API design but a better option for readability would be |
Is your idea that |
No, my comment was solely about the readability of the code rather than the implementation. I don't know if it's possible to resolve this issue while still maintaining readability but thought I'd mention it. If I'm a developer who is new to valibot then EDIT: Having read https://valibot.dev/guides/mental-model/ I see this is likely not possible. Thanks for the great library by the way, it's awesome :) |
The error message returned for missing object entries has changed in
v1.0.0-beta.13
. Previously, the custom error message of the nested field was used. Now it uses the custom error message of theobject
schema. With this issue we want to investigate if we should use the custom message of the nested field instead. Here is a playground and here is a code example:The text was updated successfully, but these errors were encountered: