From 16979d0728dcf37677a6da468044731d6c4364b9 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 31 Oct 2024 01:08:32 +0900 Subject: [PATCH 1/6] fix not error message --- src/rule/composer/not.rs | 9 +++++++-- src/rule/non_empty/non_empty_map.rs | 6 ++++-- src/rule/non_empty/non_empty_set.rs | 5 +++-- src/rule/non_empty/non_empty_string.rs | 12 ++++++++++-- src/rule/non_empty/non_empty_vec.rs | 5 +++-- src/rule/non_empty/non_empty_vec_deque.rs | 5 +++-- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/rule/composer/not.rs b/src/rule/composer/not.rs index 8515a4f..0fc9c9c 100644 --- a/src/rule/composer/not.rs +++ b/src/rule/composer/not.rs @@ -1,5 +1,6 @@ use crate::result::Error; use crate::rule::Rule; +use std::fmt::Debug; use std::marker::PhantomData; /// `Not` reverses the definition of a certain `Rule`. @@ -18,7 +19,7 @@ pub struct Not { _rule: PhantomData, } -impl<'a, T, RULE> Rule for Not +impl<'a, T: Debug, RULE> Rule for Not where RULE: Rule + 'a, { @@ -26,7 +27,11 @@ where fn validate(target: Self::Item) -> crate::Result { let bounded_rule = |t: T| match RULE::validate(t) { - Ok(value) => Err(Error::new(value, "Target satisfies the `Not` rule")), + Ok(value) => { + let type_name = std::any::type_name::(); + let message = format!("{value:?} does not satisfy Not<{type_name}>"); + Err(Error::new(value, message)) + } Err(err) => Ok(err.into_value()), }; bounded_rule(target) diff --git a/src/rule/non_empty/non_empty_map.rs b/src/rule/non_empty/non_empty_map.rs index aaf9051..4b3bf11 100644 --- a/src/rule/non_empty/non_empty_map.rs +++ b/src/rule/non_empty/non_empty_map.rs @@ -4,6 +4,7 @@ use std::borrow::Borrow; use std::collections::hash_map::RandomState; use std::collections::hash_map::{IntoKeys, IntoValues, Keys, Values}; use std::collections::HashMap; +use std::fmt::Debug; use std::hash::{BuildHasher, Hash}; /// A type that holds a value satisfying the `NonEmptyHashMapRule` @@ -29,7 +30,7 @@ pub type NonEmptyHashMap = Refined = NonEmptyRule>; -impl NonEmptyHashMap { +impl NonEmptyHashMap { #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> NonEmpty> { Refined::new_unchecked(self.into_value().into_iter()) @@ -75,7 +76,8 @@ impl NonEmptyHashMap { impl NonEmptyHashMap where - K: Eq + Hash, + K: Eq + Hash + Debug, + V: Debug, S: BuildHasher, { pub fn get(&self, k: &Q) -> Option<&V> diff --git a/src/rule/non_empty/non_empty_set.rs b/src/rule/non_empty/non_empty_set.rs index 3351fda..a2811f1 100644 --- a/src/rule/non_empty/non_empty_set.rs +++ b/src/rule/non_empty/non_empty_set.rs @@ -5,6 +5,7 @@ use std::collections::hash_set::Difference; use std::collections::hash_map::RandomState; use std::collections::HashSet; +use std::fmt::Debug; use std::hash::{BuildHasher, Hash}; /// A type that holds a value satisfying the `NonEmptyHashSetRule` @@ -26,7 +27,7 @@ pub type NonEmptyHashSet = Refined = NonEmptyRule>; -impl NonEmptyHashSet { +impl NonEmptyHashSet { #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> NonEmpty> { Refined::new_unchecked(self.into_value().into_iter()) @@ -56,7 +57,7 @@ impl NonEmptyHashSet { impl NonEmptyHashSet where - T: Eq + Hash, + T: Eq + Hash + Debug, S: BuildHasher, { pub fn insert(self, value: T) -> Self { diff --git a/src/rule/non_empty/non_empty_string.rs b/src/rule/non_empty/non_empty_string.rs index 814d1fa..7f496fc 100644 --- a/src/rule/non_empty/non_empty_string.rs +++ b/src/rule/non_empty/non_empty_string.rs @@ -115,13 +115,21 @@ mod test { #[test] fn test_non_empty_string() { assert!(NonEmptyStringRule::validate("hello".to_string()).is_ok()); - assert!(NonEmptyStringRule::validate("".to_string()).is_err()); + assert_eq!( + NonEmptyStringRule::validate("".to_string()) + .unwrap_err() + .to_string(), + r#""" does not satisfy Not>"# + ); } #[test] fn test_non_empty_str() { assert!(NonEmptyStrRule::validate("hello").is_ok()); - assert!(NonEmptyStrRule::validate("").is_err()); + assert_eq!( + NonEmptyStrRule::validate("").unwrap_err().to_string(), + r#""" does not satisfy Not>"# + ); } #[test] diff --git a/src/rule/non_empty/non_empty_vec.rs b/src/rule/non_empty/non_empty_vec.rs index 1156152..8a1f90b 100644 --- a/src/rule/non_empty/non_empty_vec.rs +++ b/src/rule/non_empty/non_empty_vec.rs @@ -1,5 +1,6 @@ use crate::rule::{NonEmpty, NonEmptyRule}; use crate::Refined; +use std::fmt::Debug; use std::ops::Add; @@ -17,7 +18,7 @@ pub type NonEmptyVec = Refined>; /// Rule where the input `Vec` is not empty pub type NonEmptyVecRule = NonEmptyRule>; -impl NonEmptyVec { +impl NonEmptyVec { #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> NonEmpty> { Refined::new_unchecked(self.into_value().into_iter()) @@ -47,7 +48,7 @@ impl NonEmptyVec { } } -impl Add for NonEmptyVec { +impl Add for NonEmptyVec { type Output = Self; fn add(self, rhs: Self) -> Self::Output { diff --git a/src/rule/non_empty/non_empty_vec_deque.rs b/src/rule/non_empty/non_empty_vec_deque.rs index ea1cb4e..8a8bdd4 100644 --- a/src/rule/non_empty/non_empty_vec_deque.rs +++ b/src/rule/non_empty/non_empty_vec_deque.rs @@ -1,6 +1,7 @@ use crate::rule::{NonEmpty, NonEmptyRule}; use crate::Refined; use std::collections::VecDeque; +use std::fmt::Debug; use std::ops::Add; /// A type that holds a value satisfying the `NonEmptyVecDequeRule` @@ -21,7 +22,7 @@ pub type NonEmptyVecDeque = Refined>; /// Rule where the input `VecDeque` is not empty pub type NonEmptyVecDequeRule = NonEmptyRule>; -impl NonEmptyVecDeque { +impl NonEmptyVecDeque { #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> NonEmpty> { Refined::new_unchecked(self.into_value().into_iter()) @@ -57,7 +58,7 @@ impl NonEmptyVecDeque { } } -impl Add for NonEmptyVecDeque { +impl Add for NonEmptyVecDeque { type Output = Self; fn add(self, rhs: Self) -> Self::Output { From b364f14ef5a8cf9aaf13da54e66c897535d92694 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 31 Oct 2024 01:47:35 +0900 Subject: [PATCH 2/6] fix not error message --- src/rule/composer/and.rs | 31 +++++++++++++++++++++++++------ src/rule/composer/or.rs | 20 +++++++++++++++++--- src/rule/number/equal.rs | 2 +- src/rule/number/even.rs | 2 +- src/rule/number/greater.rs | 2 +- src/rule/number/less.rs | 2 +- src/rule/number/min_max.rs | 6 +++--- src/rule/number/odd.rs | 2 +- 8 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/rule/composer/and.rs b/src/rule/composer/and.rs index 9a5ba7f..e50dbe1 100644 --- a/src/rule/composer/and.rs +++ b/src/rule/composer/and.rs @@ -1,6 +1,6 @@ -use std::marker::PhantomData; - +use crate::result::Error; use crate::rule::Rule; +use std::marker::PhantomData; /// A macro to generate a `Rule` that combines multiple rules /// # Example @@ -67,15 +67,33 @@ where type Item = T; fn validate(target: Self::Item) -> crate::Result { - let bounded_rule = |t: T| RULE1::validate(t).and_then(RULE2::validate); - bounded_rule(target) + match RULE1::validate(target) { + Ok(value) => RULE2::validate(value), + Err(err) => { + let rule1_error_message = err.to_string(); + let rule1_type_name = std::any::type_name::(); + match RULE2::validate(err.into_value()) { + Ok(value) => { + let message = format!("{rule1_error_message} ({rule1_type_name})"); + Err(Error::new(value, message)) + } + Err(err) => { + let rule2_type_name = std::any::type_name::(); + let message = format!( + "{rule1_error_message} ({rule1_type_name}) & {err} ({rule2_type_name})", + ); + Err(Error::new(err.into_value(), message)) + } + } + } + } } } #[cfg(test)] mod test { use crate::rule::composer::And; - use crate::rule::{AlphabetRule, EmailRule, NonEmptyStringRule, Rule}; + use crate::rule::{AlphabetRule, EmailRule, EvenRuleU8, LessRuleU8, NonEmptyStringRule, Rule}; type NonEmptyAlphabetString = And>; @@ -86,7 +104,8 @@ mod test { #[test] fn test_rule_binder_err() { - assert!(NonEmptyAlphabetString::validate("Hello1".to_string()).is_err()); + type Target = And![EvenRuleU8, LessRuleU8<10>]; + assert_eq!(Target::validate(11).unwrap_err().to_string(), "the value must be even, but received 11 (refined_type::rule::number::even::EvenRuleU8) & the value must be less than 10, but received 11 (refined_type::rule::number::less::LessRuleU8<10>)"); } #[test] diff --git a/src/rule/composer/or.rs b/src/rule/composer/or.rs index f1ce072..7cbe65e 100644 --- a/src/rule/composer/or.rs +++ b/src/rule/composer/or.rs @@ -1,6 +1,6 @@ -use std::marker::PhantomData; - +use crate::result::Error; use crate::rule::Rule; +use std::marker::PhantomData; /// A macro to generate a `Rule` that combines multiple rules /// # Example @@ -50,7 +50,21 @@ where fn validate(target: Self::Item) -> crate::Result { let bounded_rule = |t: T| match RULE1::validate(t) { Ok(value) => Ok(value), - Err(err) => RULE2::validate(err.into_value()), + Err(err) => { + let rule1_error_message = err.to_string(); + match RULE2::validate(err.into_value()) { + Ok(value) => Ok(value), + Err(err) => { + let rule1_type_name = std::any::type_name::(); + let rule2_type_name = std::any::type_name::(); + let rule2_error_message = err.to_string(); + Err(Error::new( + err.into_value(), + format!("{rule1_error_message} ({rule1_type_name}) | {rule2_error_message} ({rule2_type_name})"), + )) + } + } + } }; bounded_rule(target) } diff --git a/src/rule/number/equal.rs b/src/rule/number/equal.rs index 44e0056..bb01409 100644 --- a/src/rule/number/equal.rs +++ b/src/rule/number/equal.rs @@ -13,7 +13,7 @@ macro_rules! define_equal_rule { if target == EQUAL { Ok(target) } else { - Err($crate::result::Error::new(target, format!("{} does not equal {}", target, EQUAL))) + Err($crate::result::Error::new(target, format!("the value must be equal to {EQUAL}, but received {target}"))) } } } diff --git a/src/rule/number/even.rs b/src/rule/number/even.rs index cbaabab..a73077d 100644 --- a/src/rule/number/even.rs +++ b/src/rule/number/even.rs @@ -17,7 +17,7 @@ macro_rules! even_rule { if target % 2 == 0 { Ok(target) } else { - Err($crate::result::Error::new(target, format!("{} is not even number", target))) + Err($crate::result::Error::new(target, format!("the value must be even, but received {target}"))) } } } diff --git a/src/rule/number/greater.rs b/src/rule/number/greater.rs index 370b127..1c37cd4 100644 --- a/src/rule/number/greater.rs +++ b/src/rule/number/greater.rs @@ -15,7 +15,7 @@ macro_rules! define_greater_rule { if target > THAN { Ok(target) } else { - Err($crate::result::Error::new(target, format!("{} is not greater than {}", target, THAN))) + Err($crate::result::Error::new(target, format!("the value must be greater than {THAN}, but received {target}"))) } } } diff --git a/src/rule/number/less.rs b/src/rule/number/less.rs index de5a94c..c1dd8c2 100644 --- a/src/rule/number/less.rs +++ b/src/rule/number/less.rs @@ -15,7 +15,7 @@ macro_rules! define_less_rule { if target < THAN { Ok(target) } else { - Err($crate::result::Error::new(target, format!("{} is not less than {}", target, THAN))) + Err($crate::result::Error::new(target, format!("the value must be less than {THAN}, but received {target}"))) } } } diff --git a/src/rule/number/min_max.rs b/src/rule/number/min_max.rs index 50d9eb8..b2c00b6 100644 --- a/src/rule/number/min_max.rs +++ b/src/rule/number/min_max.rs @@ -5,9 +5,9 @@ macro_rules! define_min_max_rule { pub type [] = $crate::Refined<[]>; /// Rule where the target value must be greater than or equal to `MIN` and less than or equal to `MAX` - pub type [] = $crate::Or![ - $crate::rule::[], - $crate::rule::[] + pub type [] = $crate::And![ + $crate::rule::[], + $crate::rule::[] ]; } }; diff --git a/src/rule/number/odd.rs b/src/rule/number/odd.rs index 7f14bac..813f7a2 100644 --- a/src/rule/number/odd.rs +++ b/src/rule/number/odd.rs @@ -15,7 +15,7 @@ macro_rules! odd_rule { if target % 2 == 1 { Ok(target) } else { - Err($crate::result::Error::new(target, format!("{} is not odd number", target))) + Err($crate::result::Error::new(target, format!("the value must be odd, but received {target}"))) } } } From a06a8034c6b13ea82f1402076dbbdece777fdea3 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 31 Oct 2024 02:21:33 +0900 Subject: [PATCH 3/6] fix error message --- src/rule/composer/and.rs | 16 +++++----------- src/rule/composer/not.rs | 4 +++- src/rule/composer/or.rs | 7 +++---- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/rule/composer/and.rs b/src/rule/composer/and.rs index e50dbe1..33f5dbc 100644 --- a/src/rule/composer/and.rs +++ b/src/rule/composer/and.rs @@ -1,5 +1,6 @@ use crate::result::Error; use crate::rule::Rule; +use std::fmt::Debug; use std::marker::PhantomData; /// A macro to generate a `Rule` that combines multiple rules @@ -59,7 +60,7 @@ impl Default for And { } } -impl<'a, T, RULE1, RULE2> Rule for And +impl<'a, T: Debug, RULE1, RULE2> Rule for And where RULE1: Rule + 'a, RULE2: Rule + 'a, @@ -71,17 +72,10 @@ where Ok(value) => RULE2::validate(value), Err(err) => { let rule1_error_message = err.to_string(); - let rule1_type_name = std::any::type_name::(); match RULE2::validate(err.into_value()) { - Ok(value) => { - let message = format!("{rule1_error_message} ({rule1_type_name})"); - Err(Error::new(value, message)) - } + Ok(value) => Err(Error::new(value, rule1_error_message)), Err(err) => { - let rule2_type_name = std::any::type_name::(); - let message = format!( - "{rule1_error_message} ({rule1_type_name}) & {err} ({rule2_type_name})", - ); + let message = format!("[{rule1_error_message} && {err}]",); Err(Error::new(err.into_value(), message)) } } @@ -105,7 +99,7 @@ mod test { #[test] fn test_rule_binder_err() { type Target = And![EvenRuleU8, LessRuleU8<10>]; - assert_eq!(Target::validate(11).unwrap_err().to_string(), "the value must be even, but received 11 (refined_type::rule::number::even::EvenRuleU8) & the value must be less than 10, but received 11 (refined_type::rule::number::less::LessRuleU8<10>)"); + assert_eq!(Target::validate(11).unwrap_err().to_string(), "[the value must be even, but received 11 && the value must be less than 10, but received 11]"); } #[test] diff --git a/src/rule/composer/not.rs b/src/rule/composer/not.rs index 0fc9c9c..a37a4d4 100644 --- a/src/rule/composer/not.rs +++ b/src/rule/composer/not.rs @@ -28,7 +28,9 @@ where fn validate(target: Self::Item) -> crate::Result { let bounded_rule = |t: T| match RULE::validate(t) { Ok(value) => { - let type_name = std::any::type_name::(); + let type_name = std::any::type_name::() + .replace("refined_type::rule::composer::or::Or", "Or") + .replace("refined_type::rule::composer::and::And", "And"); let message = format!("{value:?} does not satisfy Not<{type_name}>"); Err(Error::new(value, message)) } diff --git a/src/rule/composer/or.rs b/src/rule/composer/or.rs index 7cbe65e..456350e 100644 --- a/src/rule/composer/or.rs +++ b/src/rule/composer/or.rs @@ -1,5 +1,6 @@ use crate::result::Error; use crate::rule::Rule; +use std::fmt::Debug; use std::marker::PhantomData; /// A macro to generate a `Rule` that combines multiple rules @@ -40,7 +41,7 @@ pub struct Or { _rule2: PhantomData, } -impl<'a, T, RULE1, RULE2> Rule for Or +impl<'a, T: Debug, RULE1, RULE2> Rule for Or where RULE1: Rule + 'a, RULE2: Rule + 'a, @@ -55,12 +56,10 @@ where match RULE2::validate(err.into_value()) { Ok(value) => Ok(value), Err(err) => { - let rule1_type_name = std::any::type_name::(); - let rule2_type_name = std::any::type_name::(); let rule2_error_message = err.to_string(); Err(Error::new( err.into_value(), - format!("{rule1_error_message} ({rule1_type_name}) | {rule2_error_message} ({rule2_type_name})"), + format!("[{rule1_error_message} || {rule2_error_message}]"), )) } } From 9479e6b945b9bc3ae6df74e9c53e27dce90baa89 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 31 Oct 2024 02:29:53 +0900 Subject: [PATCH 4/6] fix error message --- src/rule/composer/or.rs | 4 ++-- src/rule/string.rs | 4 ++-- src/rule/string/email.rs | 8 ++++++++ src/rule/string/regex.rs | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/rule/composer/or.rs b/src/rule/composer/or.rs index 456350e..8128f9f 100644 --- a/src/rule/composer/or.rs +++ b/src/rule/composer/or.rs @@ -90,7 +90,7 @@ mod test { #[test] fn test_rule_binder_macro_err() { - type SampleRule = Or![EmailRule, NonEmptyStringRule, EmailRule]; - assert!(SampleRule::validate("".to_string()).is_err()); + type SampleRule = Or![EmailRule, NonEmptyStringRule]; + assert_eq!(SampleRule::validate("".to_string()).unwrap_err().to_string(), "[\"\" does not match the regex pattern ^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\\.)+[a-zA-Z]{2,}$ || \"\" does not satisfy Not>]"); } } diff --git a/src/rule/string.rs b/src/rule/string.rs index 9cd82b8..afe9596 100644 --- a/src/rule/string.rs +++ b/src/rule/string.rs @@ -7,9 +7,9 @@ mod ipv6; mod regex; pub use alpha_digit::*; -pub use alphabet::{Alphabet, AlphabetRule}; +pub use alphabet::*; pub use digit::*; -pub use email::{Email, EmailRule}; +pub use email::*; pub use ipv4::*; pub use ipv6::*; pub use regex::*; diff --git a/src/rule/string/email.rs b/src/rule/string/email.rs index 5296f08..35505d7 100644 --- a/src/rule/string/email.rs +++ b/src/rule/string/email.rs @@ -14,11 +14,19 @@ use crate::{declare_regex_rule, Refined}; /// ``` pub type Email = Refined>; +pub type EmailString = Refined; + +pub type EmailStr = Refined; + declare_regex_rule![ pub EmailRule, r"^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$" ]; +pub type EmailStringRule = EmailRule; + +pub type EmailStrRule = EmailRule<&'static str>; + #[cfg(test)] mod test { use crate::rule::string::email::EmailRule; diff --git a/src/rule/string/regex.rs b/src/rule/string/regex.rs index 30c6b25..2b80641 100644 --- a/src/rule/string/regex.rs +++ b/src/rule/string/regex.rs @@ -36,7 +36,7 @@ macro_rules! declare_regex_rule { if regex.is_match(target_as_ref) { Ok(target) } else { - let message = format!("{target_as_ref} does not match the regex pattern {regex}"); + let message = format!("\"{target_as_ref}\" does not match the regex pattern {regex}"); Err($crate::result::Error::new(target, message)) } } From 9825b61b7815663c211ad22e111e3bc222daa791 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 31 Oct 2024 02:32:01 +0900 Subject: [PATCH 5/6] fix error message --- src/rule/composer/not.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rule/composer/not.rs b/src/rule/composer/not.rs index a37a4d4..35173c5 100644 --- a/src/rule/composer/not.rs +++ b/src/rule/composer/not.rs @@ -30,7 +30,8 @@ where Ok(value) => { let type_name = std::any::type_name::() .replace("refined_type::rule::composer::or::Or", "Or") - .replace("refined_type::rule::composer::and::And", "And"); + .replace("refined_type::rule::composer::and::And", "And") + .replace("refined_type::rule::composer::not::Not", "Not"); let message = format!("{value:?} does not satisfy Not<{type_name}>"); Err(Error::new(value, message)) } @@ -49,6 +50,6 @@ mod test { fn test_not() { type NonNonEmptyString = Not; assert!(NonNonEmptyString::validate("".to_string()).is_ok()); - assert!(NonNonEmptyString::validate("Hello".to_string()).is_err()) + assert_eq!(NonNonEmptyString::validate("Hello".to_string()).unwrap_err().to_string(), "\"Hello\" does not satisfy Not>>") } } From b20958ebd2ba16bf68eefbceefed7c8ff3c67f0e Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 31 Oct 2024 02:33:09 +0900 Subject: [PATCH 6/6] update cargo.toml --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b0c869e..d15d5ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "refined_type" description = "A library for imbuing rules into types and elevating them to more robust types" -authors = ["Tomoikey"] -repository = "https://github.com/tomoikey/refined-type" +authors = ["tomoikey"] +repository = "https://github.com/tomoikey/refined_type" readme = "README.md" categories = ["accessibility", "development-tools", "rust-patterns"] license = "MIT" -version = "0.5.13" +version = "0.5.14" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html