Hi.
Thank you for yup. We love yup much more than joi)
But we stacked with a problem which raised many times:
https://github.com/jquense/yup/issues/113
https://github.com/jquense/yup/issues/251
https://github.com/jquense/yup/issues/256
https://github.com/jquense/yup/issues/499
https://github.com/jquense/yup/issues/503
https://github.com/jquense/yup/issues/602
Simple example:
We have an object with field user. This field is required, should be valid MongoDB id and a user with this id should exist in the database.
const schema = yup.object({
user: yup.string()
.required()
.test(someMongoIdValidator)
.test({
message: () => 'user is not exists',
test: async (id) => {
const user = await User.findById(id);
return !!user;
},
}),
});
No sense to check if mongo id is valid for empty string and of course no sense to make database request (yes, it's possible to return false if id is empty).
We have cases with more reference fields and with arrays of references.
So, we just want to know - are there any plans to implement an option to keep the validation order?
Is this consistent with the core idea of yup? Maybe it's already planned and help is needed.
Or we just have to manage it by multiple schemas and something like "validation waterfall" - apply next schema with only valid fields from the previous schema and merge results at the end.
Have a similar use case(this is just one of the examples) - validation of date of birth:
The date is inputted as a string:
1 first there should be the date
2 second it should be 8 characters length
3 third it should be a valid date
4 fourth - the user should be more than 18 years old.
The fourth validator depends on the third one(on the code above I had to repeat the code from validator 3).
dateOfBirth: Yup.string()
.required('Required')
.test(
'length',
'Date should be in format mm/dd/yyyy, ex: 02121998',
(date: string) => date.length === 8
)
.test('notDate', 'Wrong date', (date: string) => {
let dateInDate: Date;
try {
dateInDate = turnDateInUSFormattedStringToDate(date);
return isDateValid(dateInDate);
} catch (error) {
return false;
}
})
.test(
'moreThan18Years',
'Should be more than 18 years old',
(date: string) => {
let dateInDate: Date;
try {
dateInDate = turnDateInUSFormattedStringToDate(date);
if (!isDateValid(dateInDate)) {
return false;
}
const age = calculateAge(dateInDate);
return age >= 18;
} catch (error) {
return false;
}
}
)
So chain order is very important.
@AndriiGitHub there is some workaround for your case:
You need to have only one test which returns true if dateOfBirth is not defined (required validator will make the job).
Next, it checks the date length, the date is a valid date and the date is <= today - 18 years.
After each check, you can throw an error with custom messages with this.createError method.
https://github.com/jquense/yup#mixedtestname-string-message-string--function-test-function-schema
But why do you have to write own length validator while yup has one?
it's the way to one big .test() with all handcrafted validations inside. That's why chain order is needed.
Hey folks. I tend think that depending on validation order is generally a bad pattern, since it tightly couples all validations together and is slower. It's also unclear how things like schema.concat should work if order is important but sorting would be awkward. Overall with cases like @andriinebylovych s date example that is better solved by using a single test and customizing the message as needed using this.createError
That said, I'm open to a nicer API for doing that a bit more simply. I've just not had the time or need to explore that space for such an API. If someone wants to propose a casual RFC for an approach where the complexity is commiserate with the margin benefit of the feature i'd be more than happy to help get it in.
I think @codepunkt suggestion was elegant, with .sequence(): https://github.com/jquense/yup/issues/256#issuecomment-406807706
That way it has to be explicitly enabled per rule group:
const schema = yup.object({
user: yup
.sequence() // Enabled only for schema.user
.string()
.required()
.test(someMongoIdValidator)
.test({
message: () => 'user does not exist',
test: async (id) => {
const user = await User.findById(id);
return !!user;
},
}),
password: yup
.string()
.required()
.min(8)
.test(someSpecialPasswordTest),
});
In the above case, user and password would validate in parallel, as would the specific rules for password, but the specific rules for user would execute sequentially. If .sequence() was also added to the object schema, then user and password would also validate sequentially.
Alternative names could be sync(), synchronous() or sequenced().
This option would be awesome. Simple and efficient and it would cover 99% of the uses case related to validation chain.
Another one here that would find sequence beneficial to solve if phone,username or email is already taken. For now I'm using https://github.com/jquense/yup/issues/256#issuecomment-520520736
Update: Realized this didn't work as I had async validation on more than one field. Once it hit a valid case for hitting the test async function it kept doing it when editing other fields. Using formik.
Hey folks, there isn't a need to voice your approval of the feature generally. I'm fine with adding something that does this, what we need is a proposal for an API that works well.
sequence is generally ok but its use seems either limited or confusing.
sequence().string().required().min() I think reads awkwardly, and doesn't allow any parallelism in the schema, but does remove ambiguity around which parts are sequential.
string.sequence().required().min() is better to read but more confusing, what parts are sequential? what about tests before the sequence()
I don't have a solid proposal but something like
yup.string()
.required()
.with(
string().test(firstThis).test(thenThis)
)
Where you pass an inner schema might be clearer? or maybe just easier to to implement.
Ultimately i think this is a good example where a more functional API generally would be useful, as proposed: https://twitter.com/monasticpanic/status/1219635860368449536
you could do string.test(serial(firstThis, thenThat), required)
Ultimately i think this is a good example where a more functional API generally would be useful, as proposed: https://twitter.com/monasticpanic/status/1219635860368449536
you could do
string.test(serial(firstThis, thenThat), required)
Yeah, agreed, the Twitter post seems like a cleaner solution and seems easier to read than the current implementation. It clearly separates tests from other operations such as transformations, simplifies the implementation (I suspect) because all tests are encapsulated in a single function and promotes a more modular approach to writing custom tests, rather than in-lining them into the schema.
It should even be possible to do more advanced operations with that syntax, such as:
string.test(
sequenced(
firstThis,
thenThat,
parallel(butThese, canRun, inParallel),
thenFinally
),
required, // also runs in parallel with the 'sequenced' block as that's the default
)
Plus you can have a shortcut for when you want the default to be sequenced using string.testSequenced(...)
Ultimately i think this is a good example where a more functional API generally would be useful, as proposed: https://twitter.com/monasticpanic/status/1219635860368449536
Looks good, especially for cases where we need only validation without any transformations.
parallel and series (as one more name option) - like Gulp have.
const schema = yup.object({
user: yup.string()
.series(
required,
test(someMongoIdValidator)
test({
message: () => 'user is not exists',
test: async (id) => {
const user = await User.findById(id);
return !!user;
},
}),
)
});
Very good
Or perhaps, if we want to maintain the current syntax, we could let the series function return the parent schema to allow chaining. This would be also useful when schemas are built dynamically and they are passed around.
const schema = yup.object({
user: yup.string()
.series(schema => schema
.required()
.test(someMongoIdValidator)
.test({
message: () => 'user is not exists',
test: async (id) => {
const user = await User.findById(id);
return !!user;
},
}),
)
});
Hey guys, I'm trying to chain the validation order but it's not working. Please check #952
I don't think Yup is a good tool for solving validation once for all. Instead of spending a lot of time google for the things that Yup might not be able to do, it's better to use Yup in a large validation function to validate individual values as needed, then you have full control of the logical of validation. For example, I want to validate userName and force it not empty and has not been used. And when validating whether it has been used, I will show a spin beside the input. Obviously, it is very strange that a spin show and then the error is the user name is required. extremely, the network request for checking the user name may faild, however test only has true or false. In reality, there are too many things that Yup can't handle, but if I use it in a big validate function, It will save me the time of writing duplicate verify code.
Most helpful comment
I think @codepunkt suggestion was elegant, with
.sequence(): https://github.com/jquense/yup/issues/256#issuecomment-406807706That way it has to be explicitly enabled per rule group:
In the above case,
userandpasswordwould validate in parallel, as would the specific rules forpassword, but the specific rules foruserwould execute sequentially. If.sequence()was also added to the object schema, thenuserandpasswordwould also validate sequentially.Alternative names could be
sync(),synchronous()orsequenced().