Skip to content

Commit f9736fc

Browse files
authored
Merge pull request #24 from tomoikey/enhance/error_message
Enhance/error message
2 parents bfaf546 + b20958e commit f9736fc

File tree

18 files changed

+95
-40
lines changed

18 files changed

+95
-40
lines changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[package]
22
name = "refined_type"
33
description = "A library for imbuing rules into types and elevating them to more robust types"
4-
authors = ["Tomoikey"]
5-
repository = "https://github.com/tomoikey/refined-type"
4+
authors = ["tomoikey"]
5+
repository = "https://github.com/tomoikey/refined_type"
66
readme = "README.md"
77
categories = ["accessibility", "development-tools", "rust-patterns"]
88
license = "MIT"
9-
version = "0.5.13"
9+
version = "0.5.14"
1010
edition = "2021"
1111

1212
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

src/rule/composer/and.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use std::marker::PhantomData;
2-
1+
use crate::result::Error;
32
use crate::rule::Rule;
3+
use std::fmt::Debug;
4+
use std::marker::PhantomData;
45

56
/// A macro to generate a `Rule` that combines multiple rules
67
/// # Example
@@ -59,23 +60,34 @@ impl<RULE1, RULE2> Default for And<RULE1, RULE2> {
5960
}
6061
}
6162

62-
impl<'a, T, RULE1, RULE2> Rule for And<RULE1, RULE2>
63+
impl<'a, T: Debug, RULE1, RULE2> Rule for And<RULE1, RULE2>
6364
where
6465
RULE1: Rule<Item = T> + 'a,
6566
RULE2: Rule<Item = T> + 'a,
6667
{
6768
type Item = T;
6869

6970
fn validate(target: Self::Item) -> crate::Result<T> {
70-
let bounded_rule = |t: T| RULE1::validate(t).and_then(RULE2::validate);
71-
bounded_rule(target)
71+
match RULE1::validate(target) {
72+
Ok(value) => RULE2::validate(value),
73+
Err(err) => {
74+
let rule1_error_message = err.to_string();
75+
match RULE2::validate(err.into_value()) {
76+
Ok(value) => Err(Error::new(value, rule1_error_message)),
77+
Err(err) => {
78+
let message = format!("[{rule1_error_message} && {err}]",);
79+
Err(Error::new(err.into_value(), message))
80+
}
81+
}
82+
}
83+
}
7284
}
7385
}
7486

7587
#[cfg(test)]
7688
mod test {
7789
use crate::rule::composer::And;
78-
use crate::rule::{AlphabetRule, EmailRule, NonEmptyStringRule, Rule};
90+
use crate::rule::{AlphabetRule, EmailRule, EvenRuleU8, LessRuleU8, NonEmptyStringRule, Rule};
7991

8092
type NonEmptyAlphabetString = And<NonEmptyStringRule, AlphabetRule<String>>;
8193

@@ -86,7 +98,8 @@ mod test {
8698

8799
#[test]
88100
fn test_rule_binder_err() {
89-
assert!(NonEmptyAlphabetString::validate("Hello1".to_string()).is_err());
101+
type Target = And![EvenRuleU8, LessRuleU8<10>];
102+
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]");
90103
}
91104

92105
#[test]

src/rule/composer/not.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::result::Error;
22
use crate::rule::Rule;
3+
use std::fmt::Debug;
34
use std::marker::PhantomData;
45

56
/// `Not` reverses the definition of a certain `Rule`.
@@ -18,15 +19,22 @@ pub struct Not<RULE> {
1819
_rule: PhantomData<RULE>,
1920
}
2021

21-
impl<'a, T, RULE> Rule for Not<RULE>
22+
impl<'a, T: Debug, RULE> Rule for Not<RULE>
2223
where
2324
RULE: Rule<Item = T> + 'a,
2425
{
2526
type Item = T;
2627

2728
fn validate(target: Self::Item) -> crate::Result<T> {
2829
let bounded_rule = |t: T| match RULE::validate(t) {
29-
Ok(value) => Err(Error::new(value, "Target satisfies the `Not` rule")),
30+
Ok(value) => {
31+
let type_name = std::any::type_name::<RULE>()
32+
.replace("refined_type::rule::composer::or::Or", "Or")
33+
.replace("refined_type::rule::composer::and::And", "And")
34+
.replace("refined_type::rule::composer::not::Not", "Not");
35+
let message = format!("{value:?} does not satisfy Not<{type_name}>");
36+
Err(Error::new(value, message))
37+
}
3038
Err(err) => Ok(err.into_value()),
3139
};
3240
bounded_rule(target)
@@ -42,6 +50,6 @@ mod test {
4250
fn test_not() {
4351
type NonNonEmptyString = Not<NonEmptyStringRule>;
4452
assert!(NonNonEmptyString::validate("".to_string()).is_ok());
45-
assert!(NonNonEmptyString::validate("Hello".to_string()).is_err())
53+
assert_eq!(NonNonEmptyString::validate("Hello".to_string()).unwrap_err().to_string(), "\"Hello\" does not satisfy Not<Not<refined_type::rule::empty::EmptyRule<alloc::string::String>>>")
4654
}
4755
}

src/rule/composer/or.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use std::marker::PhantomData;
2-
1+
use crate::result::Error;
32
use crate::rule::Rule;
3+
use std::fmt::Debug;
4+
use std::marker::PhantomData;
45

56
/// A macro to generate a `Rule` that combines multiple rules
67
/// # Example
@@ -40,7 +41,7 @@ pub struct Or<RULE1, RULE2> {
4041
_rule2: PhantomData<RULE2>,
4142
}
4243

43-
impl<'a, T, RULE1, RULE2> Rule for Or<RULE1, RULE2>
44+
impl<'a, T: Debug, RULE1, RULE2> Rule for Or<RULE1, RULE2>
4445
where
4546
RULE1: Rule<Item = T> + 'a,
4647
RULE2: Rule<Item = T> + 'a,
@@ -50,7 +51,19 @@ where
5051
fn validate(target: Self::Item) -> crate::Result<T> {
5152
let bounded_rule = |t: T| match RULE1::validate(t) {
5253
Ok(value) => Ok(value),
53-
Err(err) => RULE2::validate(err.into_value()),
54+
Err(err) => {
55+
let rule1_error_message = err.to_string();
56+
match RULE2::validate(err.into_value()) {
57+
Ok(value) => Ok(value),
58+
Err(err) => {
59+
let rule2_error_message = err.to_string();
60+
Err(Error::new(
61+
err.into_value(),
62+
format!("[{rule1_error_message} || {rule2_error_message}]"),
63+
))
64+
}
65+
}
66+
}
5467
};
5568
bounded_rule(target)
5669
}
@@ -77,7 +90,7 @@ mod test {
7790

7891
#[test]
7992
fn test_rule_binder_macro_err() {
80-
type SampleRule = Or![EmailRule<String>, NonEmptyStringRule, EmailRule<String>];
81-
assert!(SampleRule::validate("".to_string()).is_err());
93+
type SampleRule = Or![EmailRule<String>, NonEmptyStringRule];
94+
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<refined_type::rule::empty::EmptyRule<alloc::string::String>>]");
8295
}
8396
}

src/rule/non_empty/non_empty_map.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::borrow::Borrow;
44
use std::collections::hash_map::RandomState;
55
use std::collections::hash_map::{IntoKeys, IntoValues, Keys, Values};
66
use std::collections::HashMap;
7+
use std::fmt::Debug;
78
use std::hash::{BuildHasher, Hash};
89

910
/// A type that holds a value satisfying the `NonEmptyHashMapRule`
@@ -29,7 +30,7 @@ pub type NonEmptyHashMap<K, V, S = RandomState> = Refined<NonEmptyHashMapRule<K,
2930
/// Rule where the input `HashMap` is not empty
3031
pub type NonEmptyHashMapRule<K, V, S = RandomState> = NonEmptyRule<HashMap<K, V, S>>;
3132

32-
impl<K, V, S> NonEmptyHashMap<K, V, S> {
33+
impl<K: Debug, V: Debug, S> NonEmptyHashMap<K, V, S> {
3334
#[allow(clippy::should_implement_trait)]
3435
pub fn into_iter(self) -> NonEmpty<std::collections::hash_map::IntoIter<K, V>> {
3536
Refined::new_unchecked(self.into_value().into_iter())
@@ -75,7 +76,8 @@ impl<K, V, S> NonEmptyHashMap<K, V, S> {
7576

7677
impl<K, V, S> NonEmptyHashMap<K, V, S>
7778
where
78-
K: Eq + Hash,
79+
K: Eq + Hash + Debug,
80+
V: Debug,
7981
S: BuildHasher,
8082
{
8183
pub fn get<Q>(&self, k: &Q) -> Option<&V>

src/rule/non_empty/non_empty_set.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::collections::hash_set::Difference;
55

66
use std::collections::hash_map::RandomState;
77
use std::collections::HashSet;
8+
use std::fmt::Debug;
89
use std::hash::{BuildHasher, Hash};
910

1011
/// A type that holds a value satisfying the `NonEmptyHashSetRule`
@@ -26,7 +27,7 @@ pub type NonEmptyHashSet<T, S = RandomState> = Refined<NonEmptyRule<HashSet<T, S
2627
/// Rule where the input `HashSet` is not empty
2728
pub type NonEmptyHashSetRule<T, S = RandomState> = NonEmptyRule<HashSet<T, S>>;
2829

29-
impl<T, S> NonEmptyHashSet<T, S> {
30+
impl<T: Debug, S> NonEmptyHashSet<T, S> {
3031
#[allow(clippy::should_implement_trait)]
3132
pub fn into_iter(self) -> NonEmpty<std::collections::hash_set::IntoIter<T>> {
3233
Refined::new_unchecked(self.into_value().into_iter())
@@ -56,7 +57,7 @@ impl<T, S> NonEmptyHashSet<T, S> {
5657

5758
impl<T, S> NonEmptyHashSet<T, S>
5859
where
59-
T: Eq + Hash,
60+
T: Eq + Hash + Debug,
6061
S: BuildHasher,
6162
{
6263
pub fn insert(self, value: T) -> Self {

src/rule/non_empty/non_empty_string.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,21 @@ mod test {
115115
#[test]
116116
fn test_non_empty_string() {
117117
assert!(NonEmptyStringRule::validate("hello".to_string()).is_ok());
118-
assert!(NonEmptyStringRule::validate("".to_string()).is_err());
118+
assert_eq!(
119+
NonEmptyStringRule::validate("".to_string())
120+
.unwrap_err()
121+
.to_string(),
122+
r#""" does not satisfy Not<refined_type::rule::empty::EmptyRule<alloc::string::String>>"#
123+
);
119124
}
120125

121126
#[test]
122127
fn test_non_empty_str() {
123128
assert!(NonEmptyStrRule::validate("hello").is_ok());
124-
assert!(NonEmptyStrRule::validate("").is_err());
129+
assert_eq!(
130+
NonEmptyStrRule::validate("").unwrap_err().to_string(),
131+
r#""" does not satisfy Not<refined_type::rule::empty::EmptyRule<&str>>"#
132+
);
125133
}
126134

127135
#[test]

src/rule/non_empty/non_empty_vec.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::rule::{NonEmpty, NonEmptyRule};
22
use crate::Refined;
3+
use std::fmt::Debug;
34

45
use std::ops::Add;
56

@@ -17,7 +18,7 @@ pub type NonEmptyVec<T> = Refined<NonEmptyVecRule<T>>;
1718
/// Rule where the input `Vec` is not empty
1819
pub type NonEmptyVecRule<T> = NonEmptyRule<Vec<T>>;
1920

20-
impl<T> NonEmptyVec<T> {
21+
impl<T: Debug> NonEmptyVec<T> {
2122
#[allow(clippy::should_implement_trait)]
2223
pub fn into_iter(self) -> NonEmpty<std::vec::IntoIter<T>> {
2324
Refined::new_unchecked(self.into_value().into_iter())
@@ -47,7 +48,7 @@ impl<T> NonEmptyVec<T> {
4748
}
4849
}
4950

50-
impl<T> Add for NonEmptyVec<T> {
51+
impl<T: Debug> Add for NonEmptyVec<T> {
5152
type Output = Self;
5253

5354
fn add(self, rhs: Self) -> Self::Output {

src/rule/non_empty/non_empty_vec_deque.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::rule::{NonEmpty, NonEmptyRule};
22
use crate::Refined;
33
use std::collections::VecDeque;
4+
use std::fmt::Debug;
45
use std::ops::Add;
56

67
/// A type that holds a value satisfying the `NonEmptyVecDequeRule`
@@ -21,7 +22,7 @@ pub type NonEmptyVecDeque<T> = Refined<NonEmptyVecDequeRule<T>>;
2122
/// Rule where the input `VecDeque` is not empty
2223
pub type NonEmptyVecDequeRule<T> = NonEmptyRule<VecDeque<T>>;
2324

24-
impl<T> NonEmptyVecDeque<T> {
25+
impl<T: Debug> NonEmptyVecDeque<T> {
2526
#[allow(clippy::should_implement_trait)]
2627
pub fn into_iter(self) -> NonEmpty<std::collections::vec_deque::IntoIter<T>> {
2728
Refined::new_unchecked(self.into_value().into_iter())
@@ -57,7 +58,7 @@ impl<T> NonEmptyVecDeque<T> {
5758
}
5859
}
5960

60-
impl<T> Add for NonEmptyVecDeque<T> {
61+
impl<T: Debug> Add for NonEmptyVecDeque<T> {
6162
type Output = Self;
6263

6364
fn add(self, rhs: Self) -> Self::Output {

src/rule/number/equal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ macro_rules! define_equal_rule {
1313
if target == EQUAL {
1414
Ok(target)
1515
} else {
16-
Err($crate::result::Error::new(target, format!("{} does not equal {}", target, EQUAL)))
16+
Err($crate::result::Error::new(target, format!("the value must be equal to {EQUAL}, but received {target}")))
1717
}
1818
}
1919
}

0 commit comments

Comments
 (0)