From babd9387f26edb7d2d6bfcaa68e3f9a5b7dde923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sat, 9 Jul 2022 11:00:43 +0200 Subject: [PATCH 01/16] implemented validation trait for length --- validator/src/validation/length.rs | 66 +++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index 759c6810..01c1cde4 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::traits::HasLen; /// Validates the length of the value given. @@ -32,11 +34,33 @@ pub fn validate_length( true } +pub trait ValidateLenght: HasLen { + fn validate_length(self, min: Option, max: Option, equal: Option) -> bool; +} + +impl ValidateLenght for String { + fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { + validate_length(self, min, max, equal) + } +} + +impl ValidateLenght for Cow<'static, str> { + fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { + validate_length(self, min, max, equal) + } +} + +impl ValidateLenght for Vec { + fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { + validate_length(self, min, max, equal) + } +} + #[cfg(test)] mod tests { use std::borrow::Cow; - use super::validate_length; + use crate::{validate_length, validation::length::ValidateLenght}; #[test] fn test_validate_length_equal_overrides_min_max() { @@ -76,4 +100,44 @@ mod tests { fn test_validate_length_unicode_chars() { assert!(validate_length("日本", None, None, Some(2))); } + + + #[test] + fn test_validate_length_trait_equal_overrides_min_max() { + assert!(String::from("hello").validate_length(Some(1), Some(2), Some(5))); + } + + #[test] + fn test_validate_length_trait_string_min_max() { + assert!(String::from("hello").validate_length(Some(1), Some(10), None)); + } + + #[test] + fn test_validate_length_trait_string_min_only() { + assert!(!String::from("hello").validate_length(Some(10), None, None)); + } + + #[test] + fn test_validate_length_trait_string_max_only() { + assert!(!String::from("hello").validate_length(None, Some(1), None)); + } + + #[test] + fn test_validate_length_trait_cow() { + let test: Cow<'static, str> = "hello".into(); + assert!(test.validate_length(None, None, Some(5))); + + let test: Cow<'static, str> = String::from("hello").into(); + assert!(test.validate_length(None, None, Some(5))); + } + + #[test] + fn test_validate_length_trait_vec() { + assert!(vec![1, 2, 3].validate_length(None, None, Some(3))); + } + + #[test] + fn test_validate_length_trait_unicode_chars() { + assert!(String::from("日本").validate_length(None, None, Some(2))); + } } From 02bdd69dd47b2d7f1fffc2f6ebbfcd55b3852fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sat, 9 Jul 2022 11:06:27 +0200 Subject: [PATCH 02/16] converted identation to spaces --- validator/src/validation/length.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index 01c1cde4..d745de9f 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -40,20 +40,20 @@ pub trait ValidateLenght: HasLen { impl ValidateLenght for String { fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { - validate_length(self, min, max, equal) + validate_length(self, min, max, equal) } } impl ValidateLenght for Cow<'static, str> { - fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { - validate_length(self, min, max, equal) - } + fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { + validate_length(self, min, max, equal) + } } impl ValidateLenght for Vec { - fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { - validate_length(self, min, max, equal) - } + fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { + validate_length(self, min, max, equal) + } } #[cfg(test)] @@ -102,12 +102,12 @@ mod tests { } - #[test] - fn test_validate_length_trait_equal_overrides_min_max() { - assert!(String::from("hello").validate_length(Some(1), Some(2), Some(5))); - } + #[test] + fn test_validate_length_trait_equal_overrides_min_max() { + assert!(String::from("hello").validate_length(Some(1), Some(2), Some(5))); + } - #[test] + #[test] fn test_validate_length_trait_string_min_max() { assert!(String::from("hello").validate_length(Some(1), Some(10), None)); } From 5836898837020ba86a1b16da5ea8bc130095951a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sat, 9 Jul 2022 17:33:00 +0200 Subject: [PATCH 03/16] changed the trait to not require HasLen --- validator/src/validation/length.rs | 51 +++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index d745de9f..dae84279 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -34,25 +34,52 @@ pub fn validate_length( true } -pub trait ValidateLenght: HasLen { - fn validate_length(self, min: Option, max: Option, equal: Option) -> bool; +// A temporary fn so the crate can still be built. +// Same functionality as the above function, it just accepts a length instead of +// calculating the length by itself +pub fn validate_length_for_trait( + length: u64, + min: Option, + max: Option, + equal: Option, +) -> bool { + if let Some(eq) = equal { + return length == eq; + } else { + if let Some(m) = min { + if length < m { + return false; + } + } + if let Some(m) = max { + if length > m { + return false; + } + } + } + + true +} + +pub trait ValidateLength { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool; } -impl ValidateLenght for String { - fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { - validate_length(self, min, max, equal) +impl ValidateLength for String { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) } } -impl ValidateLenght for Cow<'static, str> { - fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { - validate_length(self, min, max, equal) +impl ValidateLength for Cow<'static, str> { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) } } -impl ValidateLenght for Vec { - fn validate_length(self, min: Option, max: Option, equal: Option) -> bool { - validate_length(self, min, max, equal) +impl ValidateLength for Vec { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } @@ -60,7 +87,7 @@ impl ValidateLenght for Vec { mod tests { use std::borrow::Cow; - use crate::{validate_length, validation::length::ValidateLenght}; + use crate::{validate_length, validation::length::ValidateLength}; #[test] fn test_validate_length_equal_overrides_min_max() { From bd9e89fbdfbab7e1c4d1bccf269bd6d6aea4e4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sat, 9 Jul 2022 21:40:49 +0200 Subject: [PATCH 04/16] added macro for generating impls --- validator/src/validation/length.rs | 42 +++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index dae84279..7781fa3b 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use crate::traits::HasLen; +use crate::impl_validate_length; /// Validates the length of the value given. /// If the validator has `equal` set, it will ignore any `min` and `max` value. @@ -65,17 +66,23 @@ pub trait ValidateLength { fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool; } -impl ValidateLength for String { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) - } -} +impl_validate_length!( + String, + &str, + Cow<'static, str> +); -impl ValidateLength for Cow<'static, str> { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) - } -} +// impl ValidateLength for String { +// fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { +// validate_length_for_trait(self.chars().count() as u64, min, max, equal) +// } +// } + +// impl ValidateLength for Cow<'static, str> { +// fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { +// validate_length_for_trait(self.chars().count() as u64, min, max, equal) +// } +// } impl ValidateLength for Vec { fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { @@ -83,6 +90,21 @@ impl ValidateLength for Vec { } } + + +#[macro_export] +macro_rules! impl_validate_length { + ( $( $type: ty ),* ) => { + $( + impl ValidateLength for $type { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) + } + } + )* + }; +} + #[cfg(test)] mod tests { use std::borrow::Cow; From 8bd86e892de94307a8fc45824e36aec1e7623eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sun, 10 Jul 2022 12:48:01 +0200 Subject: [PATCH 05/16] implemented ValidateLength for some types --- validator/src/validation/length.rs | 129 ++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 28 deletions(-) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index 7781fa3b..781f39bb 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -1,7 +1,8 @@ -use std::borrow::Cow; +use std::{borrow::Cow, collections::{HashMap, HashSet, BTreeMap, BTreeSet}}; + +use indexmap::{IndexMap, IndexSet}; use crate::traits::HasLen; -use crate::impl_validate_length; /// Validates the length of the value given. /// If the validator has `equal` set, it will ignore any `min` and `max` value. @@ -66,23 +67,29 @@ pub trait ValidateLength { fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool; } -impl_validate_length!( - String, - &str, - Cow<'static, str> -); +impl ValidateLength for String { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) + } +} -// impl ValidateLength for String { -// fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { -// validate_length_for_trait(self.chars().count() as u64, min, max, equal) -// } -// } +impl<'a> ValidateLength for &'a String { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) + } +} -// impl ValidateLength for Cow<'static, str> { -// fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { -// validate_length_for_trait(self.chars().count() as u64, min, max, equal) -// } -// } +impl<'a> ValidateLength for &'a str { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) + } +} + +impl<'a> ValidateLength for Cow<'a, str> { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) + } +} impl ValidateLength for Vec { fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { @@ -90,19 +97,85 @@ impl ValidateLength for Vec { } } +impl<'a, T> ValidateLength for &'a Vec { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} +impl ValidateLength for &[T] { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} -#[macro_export] -macro_rules! impl_validate_length { - ( $( $type: ty ),* ) => { - $( - impl ValidateLength for $type { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) - } - } - )* - }; +impl ValidateLength for [T; N] { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(N as u64, min, max, equal) + } +} + +impl ValidateLength for &[T; N] { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(N as u64, min, max, equal) + } +} + +impl<'a, K, V, S> ValidateLength for &'a HashMap { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} + +impl ValidateLength for HashMap { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} + +impl<'a, T, S> ValidateLength for &'a HashSet { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} + +impl<'a, K, V> ValidateLength for &'a BTreeMap { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} + +impl<'a, T> ValidateLength for &'a BTreeSet { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} + +impl ValidateLength for BTreeSet { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} + +#[cfg(feature = "indexmap")] +impl<'a, K, V> ValidateLength for &'a IndexMap { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} + +#[cfg(feature = "indexmap")] +impl<'a, T> ValidateLength for &'a IndexSet { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } +} + +#[cfg(feature = "indexmap")] +impl ValidateLength for IndexSet { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) + } } #[cfg(test)] From de20a9460206310802ef571d753e31ee736b2900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sun, 10 Jul 2022 13:33:19 +0200 Subject: [PATCH 06/16] using trait validation instead of the function --- validator/src/lib.rs | 2 +- validator/src/validation/length.rs | 28 ++--------- validator_derive_tests/tests/length.rs | 64 ++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 8a7a0c63..c80ad7d3 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -75,7 +75,7 @@ pub use validation::contains::validate_contains; pub use validation::does_not_contain::validate_does_not_contain; pub use validation::email::validate_email; pub use validation::ip::{validate_ip, validate_ip_v4, validate_ip_v6}; -pub use validation::length::validate_length; +pub use validation::length::{validate_length, ValidateLength}; pub use validation::must_match::validate_must_match; #[cfg(feature = "unic")] pub use validation::non_control_character::validate_non_control_character; diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index 781f39bb..4d6d3503 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -2,44 +2,22 @@ use std::{borrow::Cow, collections::{HashMap, HashSet, BTreeMap, BTreeSet}}; use indexmap::{IndexMap, IndexSet}; -use crate::traits::HasLen; - /// Validates the length of the value given. /// If the validator has `equal` set, it will ignore any `min` and `max` value. /// /// If you apply it on String, don't forget that the length can be different /// from the number of visual characters for Unicode #[must_use] -pub fn validate_length( +pub fn validate_length( value: T, min: Option, max: Option, equal: Option, ) -> bool { - let val_length = value.length(); - - if let Some(eq) = equal { - return val_length == eq; - } else { - if let Some(m) = min { - if val_length < m { - return false; - } - } - if let Some(m) = max { - if val_length > m { - return false; - } - } - } - - true + value.validate_length(min, max, equal) } -// A temporary fn so the crate can still be built. -// Same functionality as the above function, it just accepts a length instead of -// calculating the length by itself -pub fn validate_length_for_trait( +fn validate_length_for_trait( length: u64, min: Option, max: Option, diff --git a/validator_derive_tests/tests/length.rs b/validator_derive_tests/tests/length.rs index 317b2999..45745ea1 100644 --- a/validator_derive_tests/tests/length.rs +++ b/validator_derive_tests/tests/length.rs @@ -222,3 +222,67 @@ fn can_validate_set_ref_for_length() { assert_eq!(errs["val"][0].params["min"], 5); assert_eq!(errs["val"][0].params["max"], 10); } + +#[test] +fn can_validate_custom_impl_for_length() { + use serde::Serialize; + + #[derive(Debug, Serialize)] + struct CustomString(String); + + impl validator::ValidateLength for &CustomString { + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + let length = self.0.chars().count() as u64; + + if let Some(eq) = equal { + return length == eq; + } else { + if let Some(m) = min { + if length < m { + return false; + } + } + if let Some(m) = max { + if length > m { + return false; + } + } + } + + true + } + } + + #[derive(Debug, Validate)] + struct TestStruct { + #[validate(length(min = 5, max = 10))] + val: CustomString, + } + + #[derive(Debug, Validate)] + struct EqualsTestStruct { + #[validate(length(equal = 11))] + val: CustomString + } + + let too_short = TestStruct { + val: CustomString(String::from("oops")) + }; + + let too_long = TestStruct { + val: CustomString(String::from("too long for this")) + }; + + let ok = TestStruct { + val: CustomString(String::from("perfect")) + }; + + let equals_ok = EqualsTestStruct { + val: CustomString(String::from("just enough")) + }; + + assert!(too_short.validate().is_err()); + assert!(too_long.validate().is_err()); + assert!(ok.validate().is_ok()); + assert!(equals_ok.validate().is_ok()); +} \ No newline at end of file From ee04dd03a7d0d6a14fbfa6bfacee21f847e52205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sun, 10 Jul 2022 13:37:26 +0200 Subject: [PATCH 07/16] added cfg for indexmap import --- validator/src/validation/length.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index 4d6d3503..3f0b4307 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, collections::{HashMap, HashSet, BTreeMap, BTreeSet}}; +#[cfg(feature = "indexmap")] use indexmap::{IndexMap, IndexSet}; /// Validates the length of the value given. From a77bdc9297a65f9eb3dfa345c92e7cb1aee2525f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Mon, 11 Jul 2022 11:38:32 +0200 Subject: [PATCH 08/16] changed trait to require length --- validator/src/validation/length.rs | 235 ++++++++++++++++--------- validator_derive_tests/tests/length.rs | 57 +++--- 2 files changed, 170 insertions(+), 122 deletions(-) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index 3f0b4307..e0bf1a3c 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -15,15 +15,8 @@ pub fn validate_length( max: Option, equal: Option, ) -> bool { - value.validate_length(min, max, equal) -} + let length = value.length(); -fn validate_length_for_trait( - length: u64, - min: Option, - max: Option, - equal: Option, -) -> bool { if let Some(eq) = equal { return length == eq; } else { @@ -43,117 +36,190 @@ fn validate_length_for_trait( } pub trait ValidateLength { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool; + //fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool; + fn length(&self) -> u64; } impl ValidateLength for String { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.chars().count() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.chars().count() as u64 } } impl<'a> ValidateLength for &'a String { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.chars().count() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.chars().count() as u64 } } impl<'a> ValidateLength for &'a str { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.chars().count() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.chars().count() as u64 } } impl<'a> ValidateLength for Cow<'a, str> { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.chars().count() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.chars().count() as u64 } } impl ValidateLength for Vec { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } impl<'a, T> ValidateLength for &'a Vec { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } impl ValidateLength for &[T] { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } impl ValidateLength for [T; N] { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(N as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(N as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + N as u64 } } impl ValidateLength for &[T; N] { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(N as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(N as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + N as u64 } } impl<'a, K, V, S> ValidateLength for &'a HashMap { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } impl ValidateLength for HashMap { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } impl<'a, T, S> ValidateLength for &'a HashSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } impl<'a, K, V> ValidateLength for &'a BTreeMap { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } impl<'a, T> ValidateLength for &'a BTreeSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } impl ValidateLength for BTreeSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } #[cfg(feature = "indexmap")] impl<'a, K, V> ValidateLength for &'a IndexMap { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } #[cfg(feature = "indexmap")] impl<'a, T> ValidateLength for &'a IndexSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } #[cfg(feature = "indexmap")] impl ValidateLength for IndexSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) + // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + // validate_length_for_trait(self.len() as u64, min, max, equal) + // } + + fn length(&self) -> u64 { + self.len() as u64 } } @@ -161,7 +227,7 @@ impl ValidateLength for IndexSet { mod tests { use std::borrow::Cow; - use crate::{validate_length, validation::length::ValidateLength}; + use crate::{validate_length}; #[test] fn test_validate_length_equal_overrides_min_max() { @@ -202,43 +268,42 @@ mod tests { assert!(validate_length("日本", None, None, Some(2))); } - - #[test] - fn test_validate_length_trait_equal_overrides_min_max() { - assert!(String::from("hello").validate_length(Some(1), Some(2), Some(5))); - } - - #[test] - fn test_validate_length_trait_string_min_max() { - assert!(String::from("hello").validate_length(Some(1), Some(10), None)); - } - - #[test] - fn test_validate_length_trait_string_min_only() { - assert!(!String::from("hello").validate_length(Some(10), None, None)); - } - - #[test] - fn test_validate_length_trait_string_max_only() { - assert!(!String::from("hello").validate_length(None, Some(1), None)); - } - - #[test] - fn test_validate_length_trait_cow() { - let test: Cow<'static, str> = "hello".into(); - assert!(test.validate_length(None, None, Some(5))); - - let test: Cow<'static, str> = String::from("hello").into(); - assert!(test.validate_length(None, None, Some(5))); - } - - #[test] - fn test_validate_length_trait_vec() { - assert!(vec![1, 2, 3].validate_length(None, None, Some(3))); - } - - #[test] - fn test_validate_length_trait_unicode_chars() { - assert!(String::from("日本").validate_length(None, None, Some(2))); - } + // #[test] + // fn test_validate_length_trait_equal_overrides_min_max() { + // assert!(String::from("hello").validate_length(Some(1), Some(2), Some(5))); + // } + + // #[test] + // fn test_validate_length_trait_string_min_max() { + // assert!(String::from("hello").validate_length(Some(1), Some(10), None)); + // } + + // #[test] + // fn test_validate_length_trait_string_min_only() { + // assert!(!String::from("hello").validate_length(Some(10), None, None)); + // } + + // #[test] + // fn test_validate_length_trait_string_max_only() { + // assert!(!String::from("hello").validate_length(None, Some(1), None)); + // } + + // #[test] + // fn test_validate_length_trait_cow() { + // let test: Cow<'static, str> = "hello".into(); + // assert!(test.validate_length(None, None, Some(5))); + + // let test: Cow<'static, str> = String::from("hello").into(); + // assert!(test.validate_length(None, None, Some(5))); + // } + + // #[test] + // fn test_validate_length_trait_vec() { + // assert!(vec![1, 2, 3].validate_length(None, None, Some(3))); + // } + + // #[test] + // fn test_validate_length_trait_unicode_chars() { + // assert!(String::from("日本").validate_length(None, None, Some(2))); + // } } diff --git a/validator_derive_tests/tests/length.rs b/validator_derive_tests/tests/length.rs index 45745ea1..fd2bce4a 100644 --- a/validator_derive_tests/tests/length.rs +++ b/validator_derive_tests/tests/length.rs @@ -225,31 +225,14 @@ fn can_validate_set_ref_for_length() { #[test] fn can_validate_custom_impl_for_length() { - use serde::Serialize; + use serde::Serialize; - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize)] struct CustomString(String); impl validator::ValidateLength for &CustomString { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - let length = self.0.chars().count() as u64; - - if let Some(eq) = equal { - return length == eq; - } else { - if let Some(m) = min { - if length < m { - return false; - } - } - if let Some(m) = max { - if length > m { - return false; - } - } - } - - true + fn length(&self) -> u64 { + self.0.chars().count() as u64 } } @@ -259,30 +242,30 @@ fn can_validate_custom_impl_for_length() { val: CustomString, } - #[derive(Debug, Validate)] - struct EqualsTestStruct { - #[validate(length(equal = 11))] - val: CustomString - } + #[derive(Debug, Validate)] + struct EqualsTestStruct { + #[validate(length(equal = 11))] + val: CustomString + } let too_short = TestStruct { val: CustomString(String::from("oops")) }; - let too_long = TestStruct { - val: CustomString(String::from("too long for this")) - }; + let too_long = TestStruct { + val: CustomString(String::from("too long for this")) + }; - let ok = TestStruct { - val: CustomString(String::from("perfect")) - }; + let ok = TestStruct { + val: CustomString(String::from("perfect")) + }; - let equals_ok = EqualsTestStruct { - val: CustomString(String::from("just enough")) - }; + let equals_ok = EqualsTestStruct { + val: CustomString(String::from("just enough")) + }; assert!(too_short.validate().is_err()); assert!(too_long.validate().is_err()); - assert!(ok.validate().is_ok()); - assert!(equals_ok.validate().is_ok()); + assert!(ok.validate().is_ok()); + assert!(equals_ok.validate().is_ok()); } \ No newline at end of file From 0790deddc2f36e273ec998a3d2a2f491747e0503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Mon, 11 Jul 2022 16:05:28 +0200 Subject: [PATCH 09/16] Revert "changed trait to require length" This reverts commit a77bdc9297a65f9eb3dfa345c92e7cb1aee2525f. --- validator/src/validation/length.rs | 235 +++++++++---------------- validator_derive_tests/tests/length.rs | 57 +++--- 2 files changed, 122 insertions(+), 170 deletions(-) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index e0bf1a3c..3f0b4307 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -15,8 +15,15 @@ pub fn validate_length( max: Option, equal: Option, ) -> bool { - let length = value.length(); + value.validate_length(min, max, equal) +} +fn validate_length_for_trait( + length: u64, + min: Option, + max: Option, + equal: Option, +) -> bool { if let Some(eq) = equal { return length == eq; } else { @@ -36,190 +43,117 @@ pub fn validate_length( } pub trait ValidateLength { - //fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool; - fn length(&self) -> u64; + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool; } impl ValidateLength for String { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.chars().count() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.chars().count() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) } } impl<'a> ValidateLength for &'a String { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.chars().count() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.chars().count() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) } } impl<'a> ValidateLength for &'a str { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.chars().count() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.chars().count() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) } } impl<'a> ValidateLength for Cow<'a, str> { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.chars().count() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.chars().count() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.chars().count() as u64, min, max, equal) } } impl ValidateLength for Vec { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } impl<'a, T> ValidateLength for &'a Vec { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } impl ValidateLength for &[T] { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } impl ValidateLength for [T; N] { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(N as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - N as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(N as u64, min, max, equal) } } impl ValidateLength for &[T; N] { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(N as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - N as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(N as u64, min, max, equal) } } impl<'a, K, V, S> ValidateLength for &'a HashMap { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } impl ValidateLength for HashMap { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } impl<'a, T, S> ValidateLength for &'a HashSet { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } impl<'a, K, V> ValidateLength for &'a BTreeMap { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } impl<'a, T> ValidateLength for &'a BTreeSet { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } impl ValidateLength for BTreeSet { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } #[cfg(feature = "indexmap")] impl<'a, K, V> ValidateLength for &'a IndexMap { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } #[cfg(feature = "indexmap")] impl<'a, T> ValidateLength for &'a IndexSet { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } #[cfg(feature = "indexmap")] impl ValidateLength for IndexSet { - // fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - // validate_length_for_trait(self.len() as u64, min, max, equal) - // } - - fn length(&self) -> u64 { - self.len() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + validate_length_for_trait(self.len() as u64, min, max, equal) } } @@ -227,7 +161,7 @@ impl ValidateLength for IndexSet { mod tests { use std::borrow::Cow; - use crate::{validate_length}; + use crate::{validate_length, validation::length::ValidateLength}; #[test] fn test_validate_length_equal_overrides_min_max() { @@ -268,42 +202,43 @@ mod tests { assert!(validate_length("日本", None, None, Some(2))); } - // #[test] - // fn test_validate_length_trait_equal_overrides_min_max() { - // assert!(String::from("hello").validate_length(Some(1), Some(2), Some(5))); - // } - - // #[test] - // fn test_validate_length_trait_string_min_max() { - // assert!(String::from("hello").validate_length(Some(1), Some(10), None)); - // } - - // #[test] - // fn test_validate_length_trait_string_min_only() { - // assert!(!String::from("hello").validate_length(Some(10), None, None)); - // } - - // #[test] - // fn test_validate_length_trait_string_max_only() { - // assert!(!String::from("hello").validate_length(None, Some(1), None)); - // } - - // #[test] - // fn test_validate_length_trait_cow() { - // let test: Cow<'static, str> = "hello".into(); - // assert!(test.validate_length(None, None, Some(5))); - - // let test: Cow<'static, str> = String::from("hello").into(); - // assert!(test.validate_length(None, None, Some(5))); - // } - - // #[test] - // fn test_validate_length_trait_vec() { - // assert!(vec![1, 2, 3].validate_length(None, None, Some(3))); - // } - - // #[test] - // fn test_validate_length_trait_unicode_chars() { - // assert!(String::from("日本").validate_length(None, None, Some(2))); - // } + + #[test] + fn test_validate_length_trait_equal_overrides_min_max() { + assert!(String::from("hello").validate_length(Some(1), Some(2), Some(5))); + } + + #[test] + fn test_validate_length_trait_string_min_max() { + assert!(String::from("hello").validate_length(Some(1), Some(10), None)); + } + + #[test] + fn test_validate_length_trait_string_min_only() { + assert!(!String::from("hello").validate_length(Some(10), None, None)); + } + + #[test] + fn test_validate_length_trait_string_max_only() { + assert!(!String::from("hello").validate_length(None, Some(1), None)); + } + + #[test] + fn test_validate_length_trait_cow() { + let test: Cow<'static, str> = "hello".into(); + assert!(test.validate_length(None, None, Some(5))); + + let test: Cow<'static, str> = String::from("hello").into(); + assert!(test.validate_length(None, None, Some(5))); + } + + #[test] + fn test_validate_length_trait_vec() { + assert!(vec![1, 2, 3].validate_length(None, None, Some(3))); + } + + #[test] + fn test_validate_length_trait_unicode_chars() { + assert!(String::from("日本").validate_length(None, None, Some(2))); + } } diff --git a/validator_derive_tests/tests/length.rs b/validator_derive_tests/tests/length.rs index fd2bce4a..45745ea1 100644 --- a/validator_derive_tests/tests/length.rs +++ b/validator_derive_tests/tests/length.rs @@ -225,14 +225,31 @@ fn can_validate_set_ref_for_length() { #[test] fn can_validate_custom_impl_for_length() { - use serde::Serialize; + use serde::Serialize; - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize)] struct CustomString(String); impl validator::ValidateLength for &CustomString { - fn length(&self) -> u64 { - self.0.chars().count() as u64 + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + let length = self.0.chars().count() as u64; + + if let Some(eq) = equal { + return length == eq; + } else { + if let Some(m) = min { + if length < m { + return false; + } + } + if let Some(m) = max { + if length > m { + return false; + } + } + } + + true } } @@ -242,30 +259,30 @@ fn can_validate_custom_impl_for_length() { val: CustomString, } - #[derive(Debug, Validate)] - struct EqualsTestStruct { - #[validate(length(equal = 11))] - val: CustomString - } + #[derive(Debug, Validate)] + struct EqualsTestStruct { + #[validate(length(equal = 11))] + val: CustomString + } let too_short = TestStruct { val: CustomString(String::from("oops")) }; - let too_long = TestStruct { - val: CustomString(String::from("too long for this")) - }; + let too_long = TestStruct { + val: CustomString(String::from("too long for this")) + }; - let ok = TestStruct { - val: CustomString(String::from("perfect")) - }; + let ok = TestStruct { + val: CustomString(String::from("perfect")) + }; - let equals_ok = EqualsTestStruct { - val: CustomString(String::from("just enough")) - }; + let equals_ok = EqualsTestStruct { + val: CustomString(String::from("just enough")) + }; assert!(too_short.validate().is_err()); assert!(too_long.validate().is_err()); - assert!(ok.validate().is_ok()); - assert!(equals_ok.validate().is_ok()); + assert!(ok.validate().is_ok()); + assert!(equals_ok.validate().is_ok()); } \ No newline at end of file From 327768283b580a8c51c912b5baa54a8e9de17fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Mon, 11 Jul 2022 16:17:18 +0200 Subject: [PATCH 10/16] moved validation logic inside ValidateLength trait --- validator/src/validation/length.rs | 155 ++++++++++++------------- validator_derive_tests/tests/length.rs | 8 +- 2 files changed, 82 insertions(+), 81 deletions(-) diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index 3f0b4307..3f2b0075 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -18,143 +18,140 @@ pub fn validate_length( value.validate_length(min, max, equal) } -fn validate_length_for_trait( - length: u64, - min: Option, - max: Option, - equal: Option, -) -> bool { - if let Some(eq) = equal { - return length == eq; - } else { - if let Some(m) = min { - if length < m { - return false; - } - } - if let Some(m) = max { - if length > m { - return false; - } - } - } - - true -} - pub trait ValidateLength { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool; + fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { + let length = self.length(); + + if let Some(eq) = equal { + return length == eq; + } else { + if let Some(m) = min { + if length < m { + return false; + } + } + if let Some(m) = max { + if length > m { + return false; + } + } + } + + true + } + + fn length(&self) -> u64; } impl ValidateLength for String { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.chars().count() as u64 + } } impl<'a> ValidateLength for &'a String { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.chars().count() as u64 + } } impl<'a> ValidateLength for &'a str { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.chars().count() as u64 + } } impl<'a> ValidateLength for Cow<'a, str> { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.chars().count() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.chars().count() as u64 + } } impl ValidateLength for Vec { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } impl<'a, T> ValidateLength for &'a Vec { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } impl ValidateLength for &[T] { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } impl ValidateLength for [T; N] { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(N as u64, min, max, equal) - } + fn length(&self) -> u64 { + N as u64 + } } impl ValidateLength for &[T; N] { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(N as u64, min, max, equal) - } + fn length(&self) -> u64 { + N as u64 + } } impl<'a, K, V, S> ValidateLength for &'a HashMap { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } impl ValidateLength for HashMap { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } impl<'a, T, S> ValidateLength for &'a HashSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } impl<'a, K, V> ValidateLength for &'a BTreeMap { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } impl<'a, T> ValidateLength for &'a BTreeSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } impl ValidateLength for BTreeSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } #[cfg(feature = "indexmap")] impl<'a, K, V> ValidateLength for &'a IndexMap { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } #[cfg(feature = "indexmap")] impl<'a, T> ValidateLength for &'a IndexSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } #[cfg(feature = "indexmap")] impl ValidateLength for IndexSet { - fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - validate_length_for_trait(self.len() as u64, min, max, equal) - } + fn length(&self) -> u64 { + self.len() as u64 + } } #[cfg(test)] diff --git a/validator_derive_tests/tests/length.rs b/validator_derive_tests/tests/length.rs index 45745ea1..aa5de1fe 100644 --- a/validator_derive_tests/tests/length.rs +++ b/validator_derive_tests/tests/length.rs @@ -232,8 +232,8 @@ fn can_validate_custom_impl_for_length() { impl validator::ValidateLength for &CustomString { fn validate_length(&self, min: Option, max: Option, equal: Option) -> bool { - let length = self.0.chars().count() as u64; - + let length = self.length(); + if let Some(eq) = equal { return length == eq; } else { @@ -251,6 +251,10 @@ fn can_validate_custom_impl_for_length() { true } + + fn length(&self) -> u64 { + self.0.chars().count() as u64 + } } #[derive(Debug, Validate)] From 66380b6bab014951884129305941b8ad35dd7fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Tue, 2 Aug 2022 22:15:15 +0200 Subject: [PATCH 11/16] added trait validation for required --- validator/src/lib.rs | 2 +- validator/src/validation/required.rs | 16 ++++- validator_derive_tests/tests/required.rs | 84 ++++++++++++++++-------- 3 files changed, 73 insertions(+), 29 deletions(-) diff --git a/validator/src/lib.rs b/validator/src/lib.rs index c80ad7d3..0c19b0fc 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -83,7 +83,7 @@ pub use validation::non_control_character::validate_non_control_character; pub use validation::phone::validate_phone; pub use validation::range::validate_range; -pub use validation::required::validate_required; +pub use validation::required::{validate_required, ValidateRequired}; pub use validation::urls::validate_url; pub use traits::{Contains, HasLen, Validate, ValidateArgs}; diff --git a/validator/src/validation/required.rs b/validator/src/validation/required.rs index 80b06b04..863aeee3 100644 --- a/validator/src/validation/required.rs +++ b/validator/src/validation/required.rs @@ -1,5 +1,19 @@ /// Validates whether the given Option is Some #[must_use] -pub fn validate_required(val: &Option) -> bool { +pub fn validate_required(val: &T) -> bool { val.is_some() } + +pub trait ValidateRequired { + fn validate_required(&self) -> bool { + self.is_some() + } + + fn is_some(&self) -> bool; +} + +impl ValidateRequired for Option { + fn is_some(&self) -> bool { + self.is_some() + } +} diff --git a/validator_derive_tests/tests/required.rs b/validator_derive_tests/tests/required.rs index 81cd7d52..222a49d5 100644 --- a/validator_derive_tests/tests/required.rs +++ b/validator_derive_tests/tests/required.rs @@ -59,34 +59,64 @@ fn none_fails_required_nested() { #[test] fn can_specify_code_for_required() { - #[derive(Debug, Validate)] - struct TestStruct { - #[validate(required(code = "oops"))] - val: Option - } - let s = TestStruct { val: None }; - 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"); + #[derive(Debug, Validate)] + struct TestStruct { + #[validate(required(code = "oops"))] + val: Option, + } + let s = TestStruct { val: None }; + 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_required() { - #[derive(Debug, Validate)] - struct TestStruct { - #[validate(required(message = "oops"))] - val: Option - } - let s = TestStruct { val: None }; - 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"); -} \ No newline at end of file + #[derive(Debug, Validate)] + struct TestStruct { + #[validate(required(message = "oops"))] + val: Option, + } + let s = TestStruct { val: None }; + 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"); +} + +#[test] +fn can_validate_custom_impl_for_required() { + #[derive(Debug, Serialize)] + enum CustomOption { + Something(T), + Nothing, + } + + #[derive(Debug, Validate)] + struct TestStruct { + #[validate(required)] + val: CustomOption, + } + + impl validator::ValidateRequired for CustomOption { + fn is_some(&self) -> bool { + match self { + CustomOption::Something(_) => true, + CustomOption::Nothing => false, + } + } + } + + let something = TestStruct { val: CustomOption::Something("this is something".to_string()) }; + let nothing = TestStruct { val: CustomOption::Nothing }; + + assert!(something.validate().is_ok()); + assert!(nothing.validate().is_err()); +} From 34c12ee49adb05d15de5985a7ce01af328863747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Tue, 2 Aug 2022 23:05:59 +0200 Subject: [PATCH 12/16] added email trait validation --- validator/src/lib.rs | 2 +- validator/src/validation/email.rs | 97 ++++++++++++++++++--------- validator_derive/src/lib.rs | 5 +- validator_derive_tests/tests/email.rs | 28 ++++++++ 4 files changed, 96 insertions(+), 36 deletions(-) diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 0c19b0fc..29acc37e 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -73,7 +73,7 @@ mod validation; pub use validation::cards::validate_credit_card; pub use validation::contains::validate_contains; pub use validation::does_not_contain::validate_does_not_contain; -pub use validation::email::validate_email; +pub use validation::email::{validate_email, ValidateEmail}; pub use validation::ip::{validate_ip, validate_ip_v4, validate_ip_v6}; pub use validation::length::{validate_length, ValidateLength}; pub use validation::must_match::validate_must_match; diff --git a/validator/src/validation/email.rs b/validator/src/validation/email.rs index e5aab3b6..0fdd3f8f 100644 --- a/validator/src/validation/email.rs +++ b/validator/src/validation/email.rs @@ -21,39 +21,8 @@ lazy_static! { /// [RFC 5322](https://tools.ietf.org/html/rfc5322) is not practical in most circumstances and allows email addresses /// that are unfamiliar to most users. #[must_use] -pub fn validate_email<'a, T>(val: T) -> bool -where - T: Into>, -{ - let val = val.into(); - if val.is_empty() || !val.contains('@') { - return false; - } - let parts: Vec<&str> = val.rsplitn(2, '@').collect(); - let user_part = parts[1]; - let domain_part = parts[0]; - - // validate the length of each part of the email, BEFORE doing the regex - // according to RFC5321 the max length of the local part is 64 characters - // and the max length of the domain part is 255 characters - // https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.1 - if user_part.length() > 64 || domain_part.length() > 255 { - return false; - } - - if !EMAIL_USER_RE.is_match(user_part) { - return false; - } - - if !validate_domain_part(domain_part) { - // Still the possibility of an [IDN](https://en.wikipedia.org/wiki/Internationalized_domain_name) - return match domain_to_ascii(domain_part) { - Ok(d) => validate_domain_part(&d), - Err(_) => false, - }; - } - - true +pub fn validate_email(val: T) -> bool { + val.validate_email() } /// Checks if the domain is a valid domain and if not, check whether it's an IP @@ -73,6 +42,68 @@ fn validate_domain_part(domain_part: &str) -> bool { } } +pub trait ValidateEmail { + fn validate_email(&self) -> bool { + let val = self.to_email_string(); + + if val.is_empty() || !val.contains('@') { + return false; + } + + let parts: Vec<&str> = val.rsplitn(2, '@').collect(); + let user_part = parts[1]; + let domain_part = parts[0]; + + // validate the length of each part of the email, BEFORE doing the regex + // according to RFC5321 the max length of the local part is 64 characters + // and the max length of the domain part is 255 characters + // https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.1 + if user_part.length() > 64 || domain_part.length() > 255 { + return false; + } + + if !EMAIL_USER_RE.is_match(user_part) { + return false; + } + + if !validate_domain_part(domain_part) { + // Still the possibility of an [IDN](https://en.wikipedia.org/wiki/Internationalized_domain_name) + return match domain_to_ascii(domain_part) { + Ok(d) => validate_domain_part(&d), + Err(_) => false, + }; + } + + true + } + + fn to_email_string(&self) -> String; +} + +impl ValidateEmail for &str { + fn to_email_string(&self) -> String { + self.to_string() + } +} + +impl ValidateEmail for String { + fn to_email_string(&self) -> String { + self.to_string() + } +} + +impl ValidateEmail for &String { + fn to_email_string(&self) -> String { + self.to_string() + } +} + +impl<'a> ValidateEmail for Cow<'a, str> { + fn to_email_string(&self) -> String { + self.to_string() + } +} + #[cfg(test)] mod tests { use std::borrow::Cow; diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs index 1c3a536f..a9e60a82 100644 --- a/validator_derive/src/lib.rs +++ b/validator_derive/src/lib.rs @@ -146,7 +146,9 @@ fn construct_validator_argument_type( // This iterator only holds custom validations with a argument_type let mut customs: Vec<&mut CustomArgument> = fields_validations .iter_mut() - .flat_map(|x| x.validations.iter_mut().filter_map(|x| x.validator.get_custom_argument_mut())) + .flat_map(|x| { + x.validations.iter_mut().filter_map(|x| x.validator.get_custom_argument_mut()) + }) .collect(); let mut schemas: Vec<&mut CustomArgument> = @@ -406,7 +408,6 @@ fn find_validators_for_field( syn::Meta::Path(ref name) => { match name.get_ident().unwrap().to_string().as_ref() { "email" => { - assert_string_type("email", field_type, &field.ty); validators.push(FieldValidation::new(Validator::Email)); } "url" => { diff --git a/validator_derive_tests/tests/email.rs b/validator_derive_tests/tests/email.rs index 694b357f..6866a2f7 100644 --- a/validator_derive_tests/tests/email.rs +++ b/validator_derive_tests/tests/email.rs @@ -1,3 +1,4 @@ +use serde::Serialize; use validator::Validate; #[test] @@ -65,3 +66,30 @@ fn can_specify_message_for_email() { assert_eq!(errs["val"].len(), 1); assert_eq!(errs["val"][0].clone().message.unwrap(), "oops"); } + +#[test] +fn can_validate_custom_impl_for_email() { + #[derive(Debug, Serialize)] + struct CustomEmail { + user_part: String, + domain_part: String, + } + + #[derive(Debug, Validate)] + struct TestStruct { + #[validate(email)] + val: CustomEmail, + } + + impl validator::ValidateEmail for &CustomEmail { + fn to_email_string(&self) -> String { + format!("{}@{}", self.user_part, self.domain_part) + } + } + + let t = TestStruct { + val: CustomEmail { user_part: "username".to_string(), domain_part: "gmail.com".to_owned() }, + }; + + assert!(t.validate().is_ok()) +} From ba0e745cb460f41c6d3fac6ad6f83373ae5c01b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sun, 7 Aug 2022 13:09:48 +0200 Subject: [PATCH 13/16] fixed trait validation for email --- validator/src/validation/email.rs | 20 ++++++++++---------- validator_derive_tests/tests/email.rs | 15 +++++++++++---- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/validator/src/validation/email.rs b/validator/src/validation/email.rs index 0fdd3f8f..38b55f8a 100644 --- a/validator/src/validation/email.rs +++ b/validator/src/validation/email.rs @@ -77,30 +77,30 @@ pub trait ValidateEmail { true } - fn to_email_string(&self) -> String; + fn to_email_string<'a>(&'a self) -> Cow<'a, str>; } impl ValidateEmail for &str { - fn to_email_string(&self) -> String { - self.to_string() + fn to_email_string(&self) -> Cow<'_, str> { + Cow::from(*self) } } impl ValidateEmail for String { - fn to_email_string(&self) -> String { - self.to_string() + fn to_email_string(&self) -> Cow<'_, str> { + Cow::from(self) } } impl ValidateEmail for &String { - fn to_email_string(&self) -> String { - self.to_string() + fn to_email_string(&self) -> Cow<'_, str> { + Cow::from(*self) } } -impl<'a> ValidateEmail for Cow<'a, str> { - fn to_email_string(&self) -> String { - self.to_string() +impl ValidateEmail for Cow<'_, str> { + fn to_email_string(&self) -> Cow<'_, str> { + self.clone() } } diff --git a/validator_derive_tests/tests/email.rs b/validator_derive_tests/tests/email.rs index 6866a2f7..0eaf4ca6 100644 --- a/validator_derive_tests/tests/email.rs +++ b/validator_derive_tests/tests/email.rs @@ -69,6 +69,8 @@ fn can_specify_message_for_email() { #[test] fn can_validate_custom_impl_for_email() { + use std::borrow::Cow; + #[derive(Debug, Serialize)] struct CustomEmail { user_part: String, @@ -82,14 +84,19 @@ fn can_validate_custom_impl_for_email() { } impl validator::ValidateEmail for &CustomEmail { - fn to_email_string(&self) -> String { - format!("{}@{}", self.user_part, self.domain_part) + fn to_email_string(&self) -> Cow<'_, str> { + Cow::from(format!("{}@{}", self.user_part, self.domain_part)) } } - let t = TestStruct { + let valid = TestStruct { val: CustomEmail { user_part: "username".to_string(), domain_part: "gmail.com".to_owned() }, }; - assert!(t.validate().is_ok()) + let invalid = TestStruct { + val: CustomEmail { user_part: "abc".to_string(), domain_part: "".to_owned() }, + }; + + assert!(valid.validate().is_ok()); + assert!(invalid.validate().is_err()); } From 090abc5c792fd8ef946137139deb25b2767b4494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sun, 7 Aug 2022 19:34:02 +0200 Subject: [PATCH 14/16] added range trait validation --- validator/src/lib.rs | 2 +- validator/src/validation/range.rs | 47 ++++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 29acc37e..59356752 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -81,7 +81,7 @@ pub use validation::must_match::validate_must_match; pub use validation::non_control_character::validate_non_control_character; #[cfg(feature = "phone")] pub use validation::phone::validate_phone; -pub use validation::range::validate_range; +pub use validation::range::{validate_range, ValidateRange}; pub use validation::required::{validate_required, ValidateRequired}; pub use validation::urls::validate_url; diff --git a/validator/src/validation/range.rs b/validator/src/validation/range.rs index 3b3cf6ae..b44cde60 100644 --- a/validator/src/validation/range.rs +++ b/validator/src/validation/range.rs @@ -2,23 +2,50 @@ /// optional and will only be validated if they are not `None` /// #[must_use] -pub fn validate_range(value: T, min: Option, max: Option) -> bool +pub fn validate_range>(value: T, min: Option, max: Option) -> bool { + value.validate_range(min, max) +} + +pub trait ValidateRange { + fn validate_range(&self, min: Option, max: Option) -> bool { + if let Some(max) = max { + if self.greater_than(max) { + return false; + } + } + + if let Some(min) = min { + if self.less_than(min) { + return false; + } + } + + true + } + + fn greater_than(&self, max: T) -> bool; + fn less_than(&self, min: T) -> bool; +} + +impl ValidateRange for T where - T: PartialOrd + PartialEq, + T: PartialEq + PartialOrd, { - if let Some(max) = max { - if value > max { - return false; + fn greater_than(&self, max: T) -> bool { + if self > &intomax { + return true; } + + false } - if let Some(min) = min { - if value < min { - return false; + fn less_than(&self, min: T) -> bool { + if self < &min { + return true; } - } - true + false + } } #[cfg(test)] From 63559f8047cbdaebc1370714ac39c343b4331e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sun, 7 Aug 2022 19:34:21 +0200 Subject: [PATCH 15/16] fixed range trait --- validator/src/validation/range.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator/src/validation/range.rs b/validator/src/validation/range.rs index b44cde60..8f339caa 100644 --- a/validator/src/validation/range.rs +++ b/validator/src/validation/range.rs @@ -32,7 +32,7 @@ where T: PartialEq + PartialOrd, { fn greater_than(&self, max: T) -> bool { - if self > &intomax { + if self > &max { return true; } From 9aeb298b1b29c363eb6c01ab383b71ecfdd12e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilen=20Pintari=C4=8D?= Date: Sun, 7 Aug 2022 20:35:06 +0200 Subject: [PATCH 16/16] added url trait validation --- validator/src/lib.rs | 2 +- validator/src/validation/urls.rs | 39 +++++++++++++++++++--- validator_derive/src/lib.rs | 1 - validator_derive_tests/tests/url.rs | 50 +++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 7 deletions(-) diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 59356752..4bbf3033 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -84,7 +84,7 @@ pub use validation::phone::validate_phone; pub use validation::range::{validate_range, ValidateRange}; pub use validation::required::{validate_required, ValidateRequired}; -pub use validation::urls::validate_url; +pub use validation::urls::{validate_url, ValidateUrl}; pub use traits::{Contains, HasLen, Validate, ValidateArgs}; pub use types::{ValidationError, ValidationErrors, ValidationErrorsKind}; diff --git a/validator/src/validation/urls.rs b/validator/src/validation/urls.rs index 41774d1a..1528dcd4 100644 --- a/validator/src/validation/urls.rs +++ b/validator/src/validation/urls.rs @@ -3,11 +3,40 @@ use url::Url; /// Validates whether the string given is a url #[must_use] -pub fn validate_url<'a, T>(val: T) -> bool -where - T: Into>, -{ - Url::parse(val.into().as_ref()).is_ok() +pub fn validate_url(val: T) -> bool { + val.validate_url() +} + +pub trait ValidateUrl { + fn validate_url(&self) -> bool { + Url::parse(&self.to_url_string()).is_ok() + } + + fn to_url_string<'a>(&'a self) -> Cow<'a, str>; +} + +impl ValidateUrl for &str { + fn to_url_string(&self) -> Cow<'_, str> { + Cow::from(*self) + } +} + +impl ValidateUrl for String { + fn to_url_string(&self) -> Cow<'_, str> { + Cow::from(self) + } +} + +impl ValidateUrl for &String { + fn to_url_string(&self) -> Cow<'_, str> { + Cow::from(*self) + } +} + +impl ValidateUrl for Cow<'_, str> { + fn to_url_string(&self) -> Cow<'_, str> { + self.clone() + } } #[cfg(test)] diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs index a9e60a82..143fbd00 100644 --- a/validator_derive/src/lib.rs +++ b/validator_derive/src/lib.rs @@ -411,7 +411,6 @@ fn find_validators_for_field( validators.push(FieldValidation::new(Validator::Email)); } "url" => { - assert_string_type("url", field_type, &field.ty); validators.push(FieldValidation::new(Validator::Url)); } #[cfg(feature = "phone")] diff --git a/validator_derive_tests/tests/url.rs b/validator_derive_tests/tests/url.rs index 88d2f48b..fe8d4888 100644 --- a/validator_derive_tests/tests/url.rs +++ b/validator_derive_tests/tests/url.rs @@ -67,3 +67,53 @@ fn can_specify_message_for_url() { assert_eq!(errs["val"].len(), 1); assert_eq!(errs["val"][0].clone().message.unwrap(), "oops"); } + +#[test] +fn can_validate_custom_impl_for_url() { + use serde::Serialize; + use std::borrow::Cow; + + #[derive(Debug, Serialize)] + struct CustomUrl { + scheme: String, + subdomain: String, + domain: String, + top_level_domain: String, + } + + #[derive(Debug, Validate)] + struct TestStruct { + #[validate(url)] + val: CustomUrl, + } + + impl validator::ValidateUrl for &CustomUrl { + fn to_url_string(&self) -> Cow<'_, str> { + Cow::from(format!( + "{}://{}.{}.{}", + self.scheme, self.subdomain, self.domain, self.top_level_domain + )) + } + } + + let valid = TestStruct { + val: CustomUrl { + scheme: "http".to_string(), + subdomain: "www".to_string(), + domain: "google".to_string(), + top_level_domain: "com".to_string(), + }, + }; + + let invalid = TestStruct { + val: CustomUrl { + scheme: "".to_string(), + subdomain: "".to_string(), + domain: "google".to_string(), + top_level_domain: "".to_string(), + }, + }; + + assert!(valid.validate().is_ok()); + assert!(invalid.validate().is_err()); +}