Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,10 @@ const MIN_CONSTANT: i32 = 0;

```

### must_match
Tests whether the 2 fields are equal. `must_match` takes 1 string argument. It will error if the field
### must_match and must_not_match
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we can use must_match rather than create a new validator. Something like must_match(expected=false) with a default of true (bad name but just to illustrate)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you didn't approve generic not() (Add not_match validator #141) and we have contains and does_not_contain I thought that we need a new one

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, can we move on?

`must_match` tests whether the 2 fields are equal
`must_not_match` tests whether the 2 fields are not equal
They take 1 string argument. It will error if the field
mentioned is missing or has a different type than the field the attribute is on.

Examples:
Expand All @@ -222,6 +224,10 @@ Examples:
#[validate(must_match(other = "password2"))]
```

```rust
#[validate(must_not_match(other = "id"))]
```

### contains
Tests whether the string contains the substring given or if a key is present in a hashmap. `contains` takes
1 string argument.
Expand Down
2 changes: 2 additions & 0 deletions validator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
//! | `length` | |
//! | `range` | |
//! | `must_match` | |
//! | `must_not_match` | |
//! | `contains` | |
//! | `does_not_contain` | |
//! | `custom` | |
Expand Down Expand Up @@ -73,6 +74,7 @@ pub use validation::email::ValidateEmail;
pub use validation::ip::ValidateIp;
pub use validation::length::ValidateLength;
pub use validation::must_match::validate_must_match;
pub use validation::must_not_match::validate_must_not_match;
pub use validation::non_control_character::ValidateNonControlCharacter;
pub use validation::range::ValidateRange;
pub use validation::regex::{AsRegex, ValidateRegex};
Expand Down
1 change: 1 addition & 0 deletions validator/src/validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod email;
pub mod ip;
pub mod length;
pub mod must_match;
pub mod must_not_match;
// pub mod nested;
pub mod non_control_character;
pub mod range;
Expand Down
63 changes: 63 additions & 0 deletions validator/src/validation/must_not_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/// Validates that the 2 given fields do not match.
/// Both fields are optionals
#[must_use]
pub fn validate_must_not_match<T: PartialEq>(a: T, b: T) -> bool {
a != b
}

#[cfg(test)]
mod tests {
use std::borrow::Cow;

use super::validate_must_not_match;

#[test]
fn test_validate_must_not_match_strings_valid() {
assert!(validate_must_not_match("hey".to_string(), "ho".to_string()))
}

#[test]
fn test_validate_must_not_match_cows_valid() {
let left: Cow<'static, str> = "hey".into();
let right: Cow<'static, str> = String::from("ho").into();
assert!(validate_must_not_match(left, right))
}

#[test]
fn test_validate_must_not_match_numbers() {
assert!(validate_must_not_match(2, 3))
}

#[test]
fn test_validate_must_not_match_numbers_false() {
assert_eq!(false, validate_must_not_match(2, 2));
}

#[test]
fn test_validate_must_not_match_numbers_option_false() {
assert_eq!(false, validate_must_not_match(Some(2), Some(2)));
}

#[test]
fn test_validate_must_not_match_numbers_option_true() {
assert!(validate_must_not_match(Some(6), Some(7)));
}

#[test]
fn test_validate_must_not_match_none_some() {
assert!(validate_must_not_match(None, Some(3)));
}

#[test]
fn test_validate_must_not_match_some_none() {
assert!(validate_must_not_match(Some(3), None));
}

#[test]
fn test_validate_must_not_match_none_none_false() {
// We need to define one of the values here as rust
// can not infer the generic type from None and None
let a: Option<u64> = None;
assert_eq!(false, validate_must_not_match(a, None));
}
}
14 changes: 14 additions & 0 deletions validator_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use tokens::email::email_tokens;
use tokens::ip::ip_tokens;
use tokens::length::length_tokens;
use tokens::must_match::must_match_tokens;
use tokens::must_not_match::must_not_match_tokens;
use tokens::nested::nested_tokens;
use tokens::non_control_character::non_control_char_tokens;
use tokens::range::range_tokens;
Expand Down Expand Up @@ -178,6 +179,18 @@ impl ToTokens for ValidateField {
quote!()
};

// Must not match validation
let must_not_match = if let Some(must_not_match) = self.must_not_match.clone() {
wrapper_closure(must_not_match_tokens(
&self.crate_name,
must_not_match,
&actual_field,
&field_name_str,
))
} else {
quote!()
};

// Regex validation
let regex = if let Some(regex) = self.regex.clone() {
wrapper_closure(regex_tokens(&self.crate_name, regex, &actual_field, &field_name_str))
Expand Down Expand Up @@ -231,6 +244,7 @@ impl ToTokens for ValidateField {
#contains
#does_not_contain
#must_match
#must_not_match
#regex
#custom
#nested
Expand Down
1 change: 1 addition & 0 deletions validator_derive/src/tokens/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod email;
pub mod ip;
pub mod length;
pub mod must_match;
pub mod must_not_match;
pub mod nested;
pub mod non_control_character;
pub mod range;
Expand Down
28 changes: 28 additions & 0 deletions validator_derive/src/tokens/must_not_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use quote::quote;

use crate::types::MustNotMatch;
use crate::utils::{quote_code, quote_message, CrateName};

pub fn must_not_match_tokens(
crate_name: &CrateName,
must_not_match: MustNotMatch,
field_name: &proc_macro2::TokenStream,
field_name_str: &str,
) -> proc_macro2::TokenStream {
let o = must_not_match.other;
let (other, other_err) =
(quote!(self.#o), quote!(err.add_param(::std::borrow::Cow::from("other"), &self.#o);));

let message = quote_message(must_not_match.message);
let code = quote_code(crate_name, must_not_match.code, "must_not_match");

quote! {
if !#crate_name::validate_must_not_match(&#field_name, &#other) {
#code
#message
#other_err
err.add_param(::std::borrow::Cow::from("value"), &#field_name);
errors.add(#field_name_str, err);
}
}
}
8 changes: 8 additions & 0 deletions validator_derive/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub struct ValidateField {
pub ip: Option<Override<Ip>>,
pub length: Option<Length>,
pub must_match: Option<MustMatch>,
pub must_not_match: Option<MustNotMatch>,
pub non_control_character: Option<Override<NonControlCharacter>>,
pub range: Option<Range>,
pub required: Option<Override<Required>>,
Expand Down Expand Up @@ -276,6 +277,13 @@ pub struct MustMatch {
pub code: Option<String>,
}

#[derive(Debug, Clone, FromMeta)]
pub struct MustNotMatch {
pub other: Path,
pub message: Option<String>,
pub code: Option<String>,
}

#[derive(Debug, Clone, FromMeta, Default)]
pub struct NonControlCharacter {
pub message: Option<String>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use validator::Validate;

#[derive(Validate)]
struct Test {
#[validate(must_not_match(other = password2))]
password: String,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
error[E0609]: no field `password2` on type `&Test`
--> tests/compile-fail/must_not_match/field_doesnt_exist.rs:5:39
|
5 | #[validate(must_not_match(other = password2))]
| ^^^^^^^^^ unknown field
|
help: a field with a similar name exists
|
5 | #[validate(must_not_match(other = password))]
| ~~~~~~~~
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use validator::Validate;

#[derive(Validate)]
struct Test {
#[validate(must_not_match(other = "password2"))]
password: String,
password2: i32,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error[E0308]: mismatched types
--> tests/compile-fail/must_not_match/field_type_doesnt_match.rs:3:10
|
3 | #[derive(Validate)]
| ^^^^^^^^
| |
| expected `&String`, found `&i32`
| arguments to this function are incorrect
|
= note: expected reference `&String`
found reference `&i32`
note: function defined here
--> $WORKSPACE/validator/src/validation/must_not_match.rs
|
| pub fn validate_must_not_match<T: PartialEq>(a: T, b: T) -> bool {
| ^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the derive macro `Validate` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use validator::Validate;

#[derive(Validate)]
struct Email {
#[validate(not_a(other = "validator"))]
email: String,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: Unknown field: `not_a`
--> tests/compile-fail/must_not_match/unexpected_name_value.rs:5:16
|
5 | #[validate(not_a(other = "validator"))]
| ^^^^^
73 changes: 73 additions & 0 deletions validator_derive_tests/tests/must_not_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use validator::Validate;

#[test]
fn can_validate_valid_must_not_match() {
#[derive(Debug, Validate)]
struct TestStruct {
#[validate(must_not_match(other = "val2"))]
val: String,
val2: String,
}

let s = TestStruct { val: "bob".to_string(), val2: "alice".to_string() };

assert!(s.validate().is_ok());
}

#[test]
fn matching_fails_validation() {
#[derive(Debug, Validate)]
struct TestStruct {
#[validate(must_not_match(other = "val2"))]
val: String,
val2: String,
}

let s = TestStruct { val: "bob".to_string(), val2: "bob".to_string() };

let res = s.validate();
assert!(res.is_err());
let err = res.unwrap_err();
let errs = err.field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "must_not_match");
assert_eq!(errs["val"][0].params["value"], "bob");
assert_eq!(errs["val"][0].params["other"], "bob");
}

#[test]
fn can_specify_code_for_must_not_match() {
#[derive(Debug, Validate)]
struct TestStruct {
#[validate(must_not_match(other = "val2", code = "oops"))]
val: String,
val2: String,
}
let s = TestStruct { val: "bob".to_string(), val2: "bob".to_string() };
let res = s.validate();
assert!(res.is_err());
let err = res.unwrap_err();
let errs = err.field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
}

#[test]
fn can_specify_message_for_must_not_match() {
#[derive(Debug, Validate)]
struct TestStruct {
#[validate(must_not_match(other = "val2", message = "oops"))]
val: String,
val2: String,
}
let s = TestStruct { val: "bob".to_string(), val2: "bob".to_string() };
let res = s.validate();
assert!(res.is_err());
let err = res.unwrap_err();
let errs = err.field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
}
14 changes: 14 additions & 0 deletions validator_derive_tests/tests/run-pass/must_not_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use validator::Validate;

#[derive(Validate)]
struct Test {
#[validate(must_not_match(other = s2))]
s: String,
s2: String,

#[validate(must_not_match(other = "s4"))]
s3: usize,
s4: usize,
}

fn main() {}
Loading