-
-
Notifications
You must be signed in to change notification settings - Fork 214
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
Add an advanced refine
method (similar to Zod's superRefine
)
#597
Comments
Just wanna say that this is the thing that would allow me to completely switch over to valibot :) |
I plan to work on it next week 🙌 |
Awesome 🤩 |
Hello @fabian-hiller, from what I can tell, the codemod worked well to me, but I cannot finalize the migration because of this feature request. I don't want to rush you, I just wanted to know if you have an estimate of when it will be ready. Thanks :) |
Probably next week, as I should migrate some old docs pages first. Sorry for the delay. |
It's ok, just to be aware ;) |
What should we call this super action? One idea is to call it |
I'm not sure, |
I'd call it refine |
Yeah, |
|
Action gives me the impression of a single action or a single purpose thing but idk 😄 |
What about |
I will think about it. Thank you for your feedback! |
I like the names For example, we could add two functions with the same name but a prefix in front. Since both give you access to the raw implementation a prefix like |
The So I think the |
import * as v from 'valibot';
const Schema1 = v.pipe(
v.number(),
v.rawCheck(({ dataset, addIssue }) => {
if (dataset.typed && dataset.value <= 0) {
addIssue({ message: 'Error!!!' });
}
})
);
const Schema2 = v.pipe(
v.string(),
v.rawTransform(({ dataset, addIssue, NEVER }) => {
const length = dataset.value.length;
if (length < 3) {
addIssue({ message: 'Error!!!' });
return NEVER;
}
return length;
})
); |
I will try soon, but I have a question, if I apply |
You can still wrap |
Oh, I didn't remember the Using the second options: const Schema = v.pipe(
v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
}),
v.rawCheck(({ dataset, addIssue }) => {
if (dataset.typed && dataset.value.email.length < 10) {
addIssue({
input: dataset.value.email,
message: 'Error!!!',
path: [
{
input: dataset.value,
key: 'email',
origin: 'value',
type: 'object',
value: dataset.value.email,
},
],
});
}
}),
); It's a bit bad in DX having to populate all those values in |
Otherwise, I find that while you don't have the const Schema = v.pipe(
v.object({
email: v.pipe(v.string(), v.email()),
emails: v.array(v.pipe(v.string(), v.minLength(8))),
}),
v.rawCheck(({ dataset, addIssue }) => {
if (dataset.typed) {
const repeatedIndexes: number[] = [];
dataset.value.emails.forEach((email, index) => {
if (email === dataset.value.email) {
repeatedIndexes.push(index);
}
});
if (repeatedIndexes.length > 0) {
repeatedIndexes.forEach((index) => {
addIssue({
input: dataset.value.emails,
message: `Repeated ${index}`,
path: [
{
input: dataset.value.emails,
key: index,
origin: 'value',
type: 'array',
value: dataset.value.emails[index],
},
],
});
});
}
}
}),
);
const result = v.safeParse(Schema, {
email: 'jane@example.com',
emails: ['jane1@example.com', 'jane@example.com', 'jane3@example.com'],
}); I'll try that with |
Thank you for your feedback!
I agree, but the other option would be to increase the bundle size and make it less powerful with less control by using a key path as with |
I can see that, it's ok, I think this method will be less used, so probably it won't hurt much. Thank you for this addition. |
Thank you! Feel free to share more feedback in the long run. In the end, I am interested in the best solution and feedback helps me a lot to make the best decisions. |
@fabian-hiller good job on the progress! One question, how does this play out with i18n in general? What about specifying custom error messages using i18next in different scenarios using this new function? |
I usually create a namespace only for forms, and then use it in the schemas. {
"validation": {
"email": "The email is not valid."
...
},
"required": {
"email": "The email is required."
...
},
} Then in the schemas const Schema = v.object({
email: v.pipe(v.string(), v.nonEmpty('form:required.email'), v.email('form:validation.email'))
}) And then, using react I pass the message from valibot to I could share a sandbox with this example if you want. |
@ruiaraujo012 thank you for the example. Coming from zod-i18next, what's honestly truly the best is that you just pass |
Oh, I see, I looked into zod-i18next, actually it'll be a nice addition. I think you could open an issue describing that and. |
Will do :) |
Have you read this guide? Paraglide JS looks nice. I am happy to answer questions about i18n. |
@fabian-hiller Yeah, i know about Paraglide, however we don't really have the time to migrate to paraglide a i had some issues with it 🙈 We have (probably) thousands of translations keys right now and the migration would take a lot of time. In valibot i use v.setSpecificMessage(v.url, (issue) =>
t("url", { received: issue.received, ns: "valibot" }),
); In z.setErrorMap(
makeZodI18nMap({
// @ts-ignore
t: t,
ns: ["zod", "custom-zod"],
}),
); and then when you're doing validation (like with function createSchema(
intent: Intent | null,
constraints?: {
isEmailUnique?: (email: string) => Promise<boolean>;
},
) {
return z
.object({
email: EmailSchema.pipe(
z.string().superRefine((val, ctx) => {
return constraints.isEmailUnique(val).then((isUnique) => {
if (!isUnique) {
ctx.addIssue({
code: "custom",
params: {
i18n: "custom-zod:emailIsTaken",
},
});
}
});
}),
),
})
} In this example it's not bound to any existing validator, which is really useful. I can just type Is there a similar way in valibot now? As of now, i call a global function like setValibotTranslations(t); which sets global messages for existing validators export function setValibotTranslations(t: TFunction<["valibot"]>) {
v.setSpecificMessage(v.bic, (issue) =>
t("bic", { received: issue.received, ns: "valibot" }),
);
// Other translations...
} |
I'm wondering if there's a way around that. From the type definition of Just throwing ideas around Example: function setValibotTranslations(t: TFunction<['valibot']>) {
// Or set a string type? ↓↓↓↓↓
v.setSpecificMessage(v.custom, (issue) =>
{
const isTranslationKeyRegex = /[a-zA-Z0-9_-]+:[a-zA-Z0-9_-]+$/;
if (isTranslationKeyRegex.test(issue.message)) {
return t(issue.message, { received: issue.received, ns: "valibot", defaultValue: issue.message });
}
return t("custom", { received: issue.received, ns: "valibot" })},
);
} |
This will not work because the You could try to create a function that takes a import * as v from 'valibot';
type TranslationCode =
| 'email:invalid'
| 'email:format'
| 'password:invalid'
| 'password:length';
const translations: Record<string, Record<TranslationCode, string>> = {
en: {
'email:invalid': 'The email has the wrong data type',
'email:format': 'The email is badly formatted',
'password:invalid': 'The password has the wrong data type',
'password:length': 'The password is too short',
},
de: {
'email:invalid': 'Die E-Mail hat den falschen Datentyp',
'email:format': 'Die E-Mail ist falsch formatiert',
'password:invalid': 'Das Passwort hat den falschen Datentyp',
'password:length': 'Das Passwort ist zu kurz',
},
};
function t(code: TranslationCode): v.ErrorMessage<v.BaseIssue<unknown>> {
return (issue) => translations[issue.lang || 'en']?.[code] ?? issue.message;
}
const Schema = v.object({
email: v.pipe(v.string(t('email:invalid')), v.email(t('email:format'))),
password: v.pipe(
v.string(t('password:invalid')),
v.minLength(8, t('password:length')),
),
}); |
@fabian-hiller Just to let you know that I've migrated today all the codebase of a project I'm working on to version v0.32.0, and it went well with the help of Thank you. |
|
Np, the important one was |
Currently, there is no way to directly add custom/global issues to the validation, for example:
To validate that the
primaryEmail
is not present inotherEmails
there is no method that allows it, because I need some actions that add issues globally.The
forward
action doesn't work because I cannot put the key and index of the duplicated email, in this caseotherEmails.1
.One possible solution is a method that allow us to add issues to the list of issues already created by valibot validations. Something like:
Related: #487, #546
The text was updated successfully, but these errors were encountered: