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

Validate correlated sibling fields #73

Open
kamilglod opened this issue Oct 27, 2023 · 6 comments
Open

Validate correlated sibling fields #73

kamilglod opened this issue Oct 27, 2023 · 6 comments

Comments

@kamilglod
Copy link

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.

  1. 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_name to the ValidationError). There would be super helpful to have an access to garde::Report inside custom validator. We can use validate_into but 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"));
        }
    }
}
  1. allow to pass self values to custom validator 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(())
}
  1. 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.

@jprochazk
Copy link
Owner

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,
}

@kamilglod
Copy link
Author

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: &()) {}

@jprochazk
Copy link
Owner

jprochazk commented Oct 27, 2023

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!()
}

@kamilglod
Copy link
Author

higher-order function works very well, I think this issue might be close but some extra example in README will be very welcomed.

@jprochazk
Copy link
Owner

I added an example to the README in 5b80e50, but I'm keeping this open for an equals rule.

@Rolv-Apneseth
Copy link
Contributor

Perhaps I'm misunderstanding but is the equals described here the same as matches added in #110?

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

No branches or pull requests

3 participants