Skip to content

Commit

Permalink
Merge pull request #24 from tomoikey/enhance/error_message
Browse files Browse the repository at this point in the history
Enhance/error message
  • Loading branch information
tomoikey authored Oct 30, 2024
2 parents bfaf546 + b20958e commit f9736fc
Show file tree
Hide file tree
Showing 18 changed files with 95 additions and 40 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
27 changes: 20 additions & 7 deletions src/rule/composer/and.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::marker::PhantomData;

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
/// # Example
Expand Down Expand Up @@ -59,23 +60,34 @@ impl<RULE1, RULE2> Default for And<RULE1, RULE2> {
}
}

impl<'a, T, RULE1, RULE2> Rule for And<RULE1, RULE2>
impl<'a, T: Debug, RULE1, RULE2> Rule for And<RULE1, RULE2>
where
RULE1: Rule<Item = T> + 'a,
RULE2: Rule<Item = T> + 'a,
{
type Item = T;

fn validate(target: Self::Item) -> crate::Result<T> {
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();
match RULE2::validate(err.into_value()) {
Ok(value) => Err(Error::new(value, rule1_error_message)),
Err(err) => {
let message = format!("[{rule1_error_message} && {err}]",);
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<NonEmptyStringRule, AlphabetRule<String>>;

Expand All @@ -86,7 +98,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 && the value must be less than 10, but received 11]");
}

#[test]
Expand Down
14 changes: 11 additions & 3 deletions src/rule/composer/not.rs
Original file line number Diff line number Diff line change
@@ -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`.
Expand All @@ -18,15 +19,22 @@ pub struct Not<RULE> {
_rule: PhantomData<RULE>,
}

impl<'a, T, RULE> Rule for Not<RULE>
impl<'a, T: Debug, RULE> Rule for Not<RULE>
where
RULE: Rule<Item = T> + 'a,
{
type Item = T;

fn validate(target: Self::Item) -> crate::Result<T> {
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::<RULE>()
.replace("refined_type::rule::composer::or::Or", "Or")
.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))
}
Err(err) => Ok(err.into_value()),
};
bounded_rule(target)
Expand All @@ -42,6 +50,6 @@ mod test {
fn test_not() {
type NonNonEmptyString = Not<NonEmptyStringRule>;
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<Not<refined_type::rule::empty::EmptyRule<alloc::string::String>>>")
}
}
25 changes: 19 additions & 6 deletions src/rule/composer/or.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::marker::PhantomData;

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
/// # Example
Expand Down Expand Up @@ -40,7 +41,7 @@ pub struct Or<RULE1, RULE2> {
_rule2: PhantomData<RULE2>,
}

impl<'a, T, RULE1, RULE2> Rule for Or<RULE1, RULE2>
impl<'a, T: Debug, RULE1, RULE2> Rule for Or<RULE1, RULE2>
where
RULE1: Rule<Item = T> + 'a,
RULE2: Rule<Item = T> + 'a,
Expand All @@ -50,7 +51,19 @@ where
fn validate(target: Self::Item) -> crate::Result<T> {
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 rule2_error_message = err.to_string();
Err(Error::new(
err.into_value(),
format!("[{rule1_error_message} || {rule2_error_message}]"),
))
}
}
}
};
bounded_rule(target)
}
Expand All @@ -77,7 +90,7 @@ mod test {

#[test]
fn test_rule_binder_macro_err() {
type SampleRule = Or![EmailRule<String>, NonEmptyStringRule, EmailRule<String>];
assert!(SampleRule::validate("".to_string()).is_err());
type SampleRule = Or![EmailRule<String>, 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<refined_type::rule::empty::EmptyRule<alloc::string::String>>]");
}
}
6 changes: 4 additions & 2 deletions src/rule/non_empty/non_empty_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -29,7 +30,7 @@ pub type NonEmptyHashMap<K, V, S = RandomState> = Refined<NonEmptyHashMapRule<K,
/// Rule where the input `HashMap` is not empty
pub type NonEmptyHashMapRule<K, V, S = RandomState> = NonEmptyRule<HashMap<K, V, S>>;

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

impl<K, V, S> NonEmptyHashMap<K, V, S>
where
K: Eq + Hash,
K: Eq + Hash + Debug,
V: Debug,
S: BuildHasher,
{
pub fn get<Q>(&self, k: &Q) -> Option<&V>
Expand Down
5 changes: 3 additions & 2 deletions src/rule/non_empty/non_empty_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -26,7 +27,7 @@ pub type NonEmptyHashSet<T, S = RandomState> = Refined<NonEmptyRule<HashSet<T, S
/// Rule where the input `HashSet` is not empty
pub type NonEmptyHashSetRule<T, S = RandomState> = NonEmptyRule<HashSet<T, S>>;

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

impl<T, S> NonEmptyHashSet<T, S>
where
T: Eq + Hash,
T: Eq + Hash + Debug,
S: BuildHasher,
{
pub fn insert(self, value: T) -> Self {
Expand Down
12 changes: 10 additions & 2 deletions src/rule/non_empty/non_empty_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<refined_type::rule::empty::EmptyRule<alloc::string::String>>"#
);
}

#[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<refined_type::rule::empty::EmptyRule<&str>>"#
);
}

#[test]
Expand Down
5 changes: 3 additions & 2 deletions src/rule/non_empty/non_empty_vec.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::rule::{NonEmpty, NonEmptyRule};
use crate::Refined;
use std::fmt::Debug;

use std::ops::Add;

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

impl<T> NonEmptyVec<T> {
impl<T: Debug> NonEmptyVec<T> {
#[allow(clippy::should_implement_trait)]
pub fn into_iter(self) -> NonEmpty<std::vec::IntoIter<T>> {
Refined::new_unchecked(self.into_value().into_iter())
Expand Down Expand Up @@ -47,7 +48,7 @@ impl<T> NonEmptyVec<T> {
}
}

impl<T> Add for NonEmptyVec<T> {
impl<T: Debug> Add for NonEmptyVec<T> {
type Output = Self;

fn add(self, rhs: Self) -> Self::Output {
Expand Down
5 changes: 3 additions & 2 deletions src/rule/non_empty/non_empty_vec_deque.rs
Original file line number Diff line number Diff line change
@@ -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`
Expand All @@ -21,7 +22,7 @@ pub type NonEmptyVecDeque<T> = Refined<NonEmptyVecDequeRule<T>>;
/// Rule where the input `VecDeque` is not empty
pub type NonEmptyVecDequeRule<T> = NonEmptyRule<VecDeque<T>>;

impl<T> NonEmptyVecDeque<T> {
impl<T: Debug> NonEmptyVecDeque<T> {
#[allow(clippy::should_implement_trait)]
pub fn into_iter(self) -> NonEmpty<std::collections::vec_deque::IntoIter<T>> {
Refined::new_unchecked(self.into_value().into_iter())
Expand Down Expand Up @@ -57,7 +58,7 @@ impl<T> NonEmptyVecDeque<T> {
}
}

impl<T> Add for NonEmptyVecDeque<T> {
impl<T: Debug> Add for NonEmptyVecDeque<T> {
type Output = Self;

fn add(self, rhs: Self) -> Self::Output {
Expand Down
2 changes: 1 addition & 1 deletion src/rule/number/equal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/rule/number/even.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/rule/number/greater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/rule/number/less.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")))
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/rule/number/min_max.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ macro_rules! define_min_max_rule {
pub type [<MinMax $t:camel>]<const MIN: $t, const MAX: $t> = $crate::Refined<[<MinMaxRule $t:camel>]<MIN, MAX>>;

/// Rule where the target value must be greater than or equal to `MIN` and less than or equal to `MAX`
pub type [<MinMaxRule $t:camel>]<const MIN: $t, const MAX: $t> = $crate::Or![
$crate::rule::[<RangeRule $t:camel>]<MIN, MAX>,
$crate::rule::[<EqualRule $t:camel>]<MAX>
pub type [<MinMaxRule $t:camel>]<const MIN: $t, const MAX: $t> = $crate::And![
$crate::rule::[<GreaterEqualRule $t:camel>]<MIN>,
$crate::rule::[<LessEqualRule $t:camel>]<MAX>
];
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/rule/number/odd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/rule/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
8 changes: 8 additions & 0 deletions src/rule/string/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@ use crate::{declare_regex_rule, Refined};
/// ```
pub type Email<STRING> = Refined<EmailRule<STRING>>;

pub type EmailString = Refined<EmailStringRule>;

pub type EmailStr = Refined<EmailStrRule>;

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<String>;

pub type EmailStrRule = EmailRule<&'static str>;

#[cfg(test)]
mod test {
use crate::rule::string::email::EmailRule;
Expand Down
2 changes: 1 addition & 1 deletion src/rule/string/regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
Expand Down

0 comments on commit f9736fc

Please sign in to comment.