Skip to content

Commit

Permalink
Merge pull request #20 from cbeck88/multiple-validation-predicate
Browse files Browse the repository at this point in the history
allow setting multiple validation_predicate
  • Loading branch information
cbeck88 authored Oct 14, 2024
2 parents 5fa7d83 + f7ddc58 commit 222580e
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 8 deletions.
2 changes: 2 additions & 0 deletions REFERENCE_derive_conf.md
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,8 @@ works on that struct. Attributes that are not "top-level only" will still have a
Creates a validation constraint that must be satisfied after parsing this struct succeeds, from a user-defined function.
The function should have signature `fn(&T) -> Result<(), impl Display>`.

The `validation_prediate = ...` attribute is allowed to repeat multiple times, to set multiple validation prediates.


[^1]: Actually, the *tokens* of the type are used, so e.g. it must be `bool` and not an alias for `bool`.

Expand Down
15 changes: 7 additions & 8 deletions conf_derive/src/proc_macro_options/struct_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub struct StructItem {
pub env_prefix: Option<LitStr>,
pub serde: Option<StructSerdeItem>,
pub one_of_fields: Vec<(Ordering, List<Ident>)>,
pub validation_predicate: Option<Expr>,
pub validation_predicates: Vec<Expr>,
pub doc_string: Option<String>,
}

Expand All @@ -66,7 +66,7 @@ impl StructItem {
env_prefix: None,
serde: None,
one_of_fields: Vec::default(),
validation_predicate: None,
validation_predicates: Vec::default(),
doc_string: None,
};

Expand Down Expand Up @@ -99,11 +99,10 @@ impl StructItem {
} else if path.is_ident("serde") {
set_once(&path, &mut result.serde, Some(StructSerdeItem::new(meta)?))
} else if path.is_ident("validation_predicate") {
set_once(
&path,
&mut result.validation_predicate,
Some(parse_required_value::<Expr>(meta)?),
)
result
.validation_predicates
.push(parse_required_value::<Expr>(meta)?);
Ok(())
} else if path.is_ident("one_of_fields") {
let idents: List<Ident> = meta.input.parse()?;
if idents.elements.len() < 2 {
Expand Down Expand Up @@ -288,7 +287,7 @@ impl StructItem {
}

// Apply user-provided validation predicate, if any
if let Some(user_validation_predicate) = self.validation_predicate.as_ref() {
for user_validation_predicate in self.validation_predicates.iter() {
predicate_evaluations.push(quote! {
{
fn __validation_predicate__(
Expand Down
72 changes: 72 additions & 0 deletions tests/test_validation_predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,75 @@ fn test_validate_predicate_two_of_parsing() {
assert!(result.b);
assert!(result.c);
}

#[derive(Conf, Debug)]
#[conf(validation_predicate = MultiConstraint::b_required_if, validation_predicate = MultiConstraint::c_required_if)]
struct MultiConstraint {
#[arg(short)]
a: Option<String>,
#[arg(short)]
b: Option<String>,
#[arg(short)]
c: Option<String>,
}

impl MultiConstraint {
fn b_required_if(&self) -> Result<(), &'static str> {
if self.a == Some("b".to_owned()) && self.b.is_none() {
return Err("b is required if a = 'b'");
}
Ok(())
}

fn c_required_if(&self) -> Result<(), &'static str> {
if self.b == Some("c".to_owned()) && self.c.is_none() {
return Err("c is required if b = 'c'");
}
Ok(())
}
}

#[test]
fn test_multiple_validate_predicates() {
let result = MultiConstraint::try_parse_from::<&str, &str, &str>(vec!["."], vec![]).unwrap();
assert_eq!(result.a, None);
assert_eq!(result.b, None);
assert_eq!(result.c, None);

let result =
MultiConstraint::try_parse_from::<&str, &str, &str>(vec![".", "-a", "x"], vec![]).unwrap();
assert_eq!(result.a, Some("x".to_owned()));
assert_eq!(result.b, None);
assert_eq!(result.c, None);

assert_error_contains_text!(
MultiConstraint::try_parse_from::<&str, &str, &str>(vec![".", "-a", "b"], vec![]),
["b is required if a = 'b'"]
);

let result = MultiConstraint::try_parse_from::<&str, &str, &str>(
vec![".", "-a", "b", "-b", "x"],
vec![],
)
.unwrap();
assert_eq!(result.a, Some("b".to_owned()));
assert_eq!(result.b, Some("x".to_owned()));
assert_eq!(result.c, None);

assert_error_contains_text!(
MultiConstraint::try_parse_from::<&str, &str, &str>(
vec![".", "-a", "b", "-b", "c"],
vec![]
),
["c is required if b = 'c'"]
);

let result = MultiConstraint::try_parse_from::<&str, &str, &str>(
vec![".", "-a", "b", "-b", "c", "-c", "x"],
vec![],
)
.unwrap();
assert_eq!(result.a, Some("b".to_owned()));
assert_eq!(result.b, Some("c".to_owned()));
assert_eq!(result.c, Some("x".to_owned()));
}

0 comments on commit 222580e

Please sign in to comment.