Validate correlated sibling fields
It's quite common to have a logic in validation where we want to validate field based on values in other fields, or ensure that all fields are either filled or empty.
- it can be implemented by creating custom validator on schema level, but then I don't see an option to create an error with correct field (python marshmallow have an option to pass
field_nameto theValidationError). There would be super helpful to have an access togarde::Reportinside custom validator. We can usevalidate_intobut it's not working with#[derive(garde::Validate)]
struct User {
password: String,
repeat_password: String,
}
impl garde::Validate for User {
fn validate_into(
&self,
ctx: &Self::Context,
mut parent: &mut dyn FnMut() -> garde::Path,
report: &mut garde::Report
) {
if self.password != self.repeat_password {
let mut path = parent().join("repeate_password");
report.append(path, garde::Error::new("passwords are not equal"));
}
}
}
- allow to pass
selfvalues tocustomvalidator like:
#[derive(garde::Validate)]
struct User {
#[garde(length(min = 1, max = 255))]
password: String,
#[garde(length(min = 1, max = 255), custom(validate_equal_passwords, password=self.password))]
repeat_password: String,
}
fn validate_equal_passwords(value: &str, other: &str) -> garde::Result {
if value != other {
return Err(garde::Error::new("passwords are not equal"));
}
Ok(())
}
- Use closures to get access to
self:
pub struct Context {}
#[derive(garde::Validate)]
#[garde(context(Context))]
pub struct User {
#[garde(length(min = 1, max = 255))]
password: String,
#[garde(length(min = 1, max = 255))]
#[garde(custom(|value: &String, _ctx: &Context| {
if value != &self.password {
return Err(garde::Error::new("passwords are not equal"));
}
Ok(())
}))]
repeat_password: String,
}
Option 3 looks like the best solution but I can't find any confirmation in README that it's supported and recommended way of accessing struct siblings.
I can't find any confirmation in README that it's supported and recommended way of accessing struct siblings.
The self.field and ctx.field syntax is definitely part of the public API, and there's a mention of it in the top-level docs and README here, but there's no usage of self in the example, which should fixed.
For equality between two fields, we could add an equals rule that would use PartialEq:
#[derive(garde::Validate)]
struct User {
#[garde(length(min=1, max=255))]
password: String,
#[garde(equals(self.password))]
password2: String,
}
Adding equals() sounds good but it would solve only one use case, it would be good to have more general solution. Using closure with access to self sounds good, but for shared functions we would need to do somehing like:
#[derive(garde::Validate)]
pub struct User {
#[garde(length(min = 1, max = 255))]
password: String,
#[garde(length(min = 1, max = 255))]
#[garde(custom(|value: &String, _ctx: &()| {
some_shared_validator(value, self.repeat_password)
}))]
repeat_password: String,
}
fn some_shared_validator(val: &String, other: &String) {}
instead of
#[derive(garde::Validate)]
pub struct User {
#[garde(length(min = 1, max = 255))]
password: String,
#[garde(length(min = 1, max = 255))]
#[garde(custom(some_shared_validator, other = self.repeat_password))]
repeat_password: String,
}
fn some_shared_validator(val: &String, other: &String, _ctx: &()) {}
The general solution is custom. It doesn't always result in the most aesthetically pleasing solution, but you can use it to do pretty much anything you can think of. It accepts any expression that evaluates to impl FnOnce(&T, &Ctx) -> garde::Result, so you can use a higher-order function, or a macro that evaluates to a closure:
#[derive(garde::Validate)]
struct User {
#[garde(length(min = 1, max = 255))]
password: String,
#[garde(custom(some_shared_validator(&self.password2)))]
password2: String,
}
fn some_shared_validator(other: &str) -> impl FnOnce(&str, &()) -> garde::Result {
|value, ctx| todo!()
}
higher-order function works very well, I think this issue might be close but some extra example in README will be very welcomed.
I added an example to the README in https://github.com/jprochazk/garde/commit/5b80e50203dcc91de8ffc2608e91813878107e57, but I'm keeping this open for an equals rule.
Perhaps I'm misunderstanding but is the equals described here the same as matches added in #110?