From 6021b147cdf00cb59231a10174ca0c0ea21698dc Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:19:54 +0900 Subject: [PATCH 01/20] rearrange directory structure --- src/rule.rs | 6 ++---- src/rule/collection.rs | 5 +++++ src/rule/{ => collection}/exists.rs | 0 src/rule/{ => collection}/for_all.rs | 2 +- src/rule/{ => collection}/for_all/collection.rs | 0 src/rule/{ => collection}/for_all/string.rs | 0 6 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 src/rule/collection.rs rename src/rule/{ => collection}/exists.rs (100%) rename src/rule/{ => collection}/for_all.rs (98%) rename src/rule/{ => collection}/for_all/collection.rs (100%) rename src/rule/{ => collection}/for_all/string.rs (100%) diff --git a/src/rule.rs b/src/rule.rs index c48ab6f..108e9b9 100644 --- a/src/rule.rs +++ b/src/rule.rs @@ -1,6 +1,5 @@ +pub use collection::*; pub use empty::*; -pub use exists::*; -pub use for_all::*; pub use length::*; pub use non_empty::*; pub use number::*; @@ -8,10 +7,9 @@ pub use string::*; use crate::result::Error; +mod collection; pub mod composer; mod empty; -mod exists; -mod for_all; mod length; mod non_empty; mod number; diff --git a/src/rule/collection.rs b/src/rule/collection.rs new file mode 100644 index 0000000..55a88d5 --- /dev/null +++ b/src/rule/collection.rs @@ -0,0 +1,5 @@ +mod exists; +mod for_all; + +pub use exists::*; +pub use for_all::*; diff --git a/src/rule/exists.rs b/src/rule/collection/exists.rs similarity index 100% rename from src/rule/exists.rs rename to src/rule/collection/exists.rs diff --git a/src/rule/for_all.rs b/src/rule/collection/for_all.rs similarity index 98% rename from src/rule/for_all.rs rename to src/rule/collection/for_all.rs index c4a9276..49087e5 100644 --- a/src/rule/for_all.rs +++ b/src/rule/collection/for_all.rs @@ -33,7 +33,7 @@ where #[cfg(test)] mod tests { use crate::result::Error; - use crate::rule::for_all::ForAll; + use crate::rule::ForAll; use crate::rule::{ForAllString, ForAllVec, NonEmptyStringRule, Rule}; #[test] diff --git a/src/rule/for_all/collection.rs b/src/rule/collection/for_all/collection.rs similarity index 100% rename from src/rule/for_all/collection.rs rename to src/rule/collection/for_all/collection.rs diff --git a/src/rule/for_all/string.rs b/src/rule/collection/for_all/string.rs similarity index 100% rename from src/rule/for_all/string.rs rename to src/rule/collection/for_all/string.rs From 500d21409c369040c8d871805753f27213613461 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:45:47 +0900 Subject: [PATCH 02/20] implement Head --- src/rule/collection.rs | 2 + src/rule/collection/head.rs | 66 ++++++++++++++++++++++++++ src/rule/collection/head/collection.rs | 34 +++++++++++++ src/rule/collection/head/string.rs | 33 +++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 src/rule/collection/head.rs create mode 100644 src/rule/collection/head/collection.rs create mode 100644 src/rule/collection/head/string.rs diff --git a/src/rule/collection.rs b/src/rule/collection.rs index 55a88d5..d10db7a 100644 --- a/src/rule/collection.rs +++ b/src/rule/collection.rs @@ -1,5 +1,7 @@ mod exists; mod for_all; +mod head; pub use exists::*; pub use for_all::*; +pub use head::*; diff --git a/src/rule/collection/head.rs b/src/rule/collection/head.rs new file mode 100644 index 0000000..773f36f --- /dev/null +++ b/src/rule/collection/head.rs @@ -0,0 +1,66 @@ +mod collection; +mod string; + +use crate::rule::Rule; +use crate::Refined; +use std::collections::{HashSet, VecDeque}; +use std::marker::PhantomData; + +/// A type that holds a value satisfying the `HeadRule` +pub type Head = Refined>; + +/// A type that holds a Vec value satisfying the `HeadRule` +pub type HeadVec = Head::Item>>; + +/// A type that holds a VecDeque value satisfying the `HeadRule` +pub type HeadVecDeque = Head::Item>>; + +/// A type that holds a HashSet value satisfying the `HeadRule` +pub type HeadHashSet = Head::Item>>; + +/// A type that holds a String value satisfying the `HeadRule` +pub type HeadString = Head; + +/// Rule where the first element satisfies the condition +pub struct HeadRule +where + RULE: Rule, +{ + _phantom_data: PhantomData<(RULE, ITERABLE)>, +} + +#[cfg(test)] +mod tests { + use crate::rule::{HeadVec, NonEmptyStringRule}; + + #[test] + fn head_valid() -> anyhow::Result<()> { + let table = vec![ + vec!["good morning".to_string(), "".to_string()], + vec!["hello".to_string(), "hello".to_string()], + ]; + + for value in table { + let head = HeadVec::::new(value.clone())?; + assert_eq!(head.into_value(), value); + } + + Ok(()) + } + + #[test] + fn head_invalid() -> anyhow::Result<()> { + let table = vec![ + vec![], + vec!["".to_string()], + vec!["".to_string(), "hello".to_string()], + ]; + + for value in table { + let head_result = HeadVec::::new(value.clone()); + assert!(head_result.is_err()); + } + + Ok(()) + } +} diff --git a/src/rule/collection/head/collection.rs b/src/rule/collection/head/collection.rs new file mode 100644 index 0000000..24a2e7e --- /dev/null +++ b/src/rule/collection/head/collection.rs @@ -0,0 +1,34 @@ +use crate::result::Error; +use crate::rule::collection::head::HeadRule; +use crate::rule::Rule; +use std::collections::VecDeque; + +impl Rule for HeadRule> +where + RULE: Rule, +{ + type Item = Vec; + + fn validate(target: &Self::Item) -> Result<(), Error> { + if let Some(head) = target.first() { + RULE::validate(head) + } else { + Err(Error::new("empty collection")) + } + } +} + +impl Rule for HeadRule> +where + RULE: Rule, +{ + type Item = VecDeque; + + fn validate(target: &Self::Item) -> Result<(), Error> { + if let Some(head) = target.front() { + RULE::validate(head) + } else { + Err(Error::new("empty collection")) + } + } +} diff --git a/src/rule/collection/head/string.rs b/src/rule/collection/head/string.rs new file mode 100644 index 0000000..f54aee4 --- /dev/null +++ b/src/rule/collection/head/string.rs @@ -0,0 +1,33 @@ +use crate::result::Error; +use crate::rule::collection::head::HeadRule; +use crate::rule::Rule; + +impl Rule for HeadRule +where + RULE: Rule, +{ + type Item = String; + + fn validate(target: &Self::Item) -> Result<(), Error> { + if let Some(head) = target.chars().next() { + RULE::validate(&head) + } else { + Err(Error::new("empty string")) + } + } +} + +impl Rule for HeadRule +where + RULE: Rule, +{ + type Item = &'static str; + + fn validate(target: &Self::Item) -> Result<(), Error> { + if let Some(head) = target.chars().next() { + RULE::validate(&head) + } else { + Err(Error::new("empty string")) + } + } +} From d083bb15413fd07f8e0e66909b6da657891a7ab5 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:47:52 +0900 Subject: [PATCH 03/20] delete HeadHashSet --- src/rule/collection/head.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/rule/collection/head.rs b/src/rule/collection/head.rs index 773f36f..b808e26 100644 --- a/src/rule/collection/head.rs +++ b/src/rule/collection/head.rs @@ -3,7 +3,7 @@ mod string; use crate::rule::Rule; use crate::Refined; -use std::collections::{HashSet, VecDeque}; +use std::collections::VecDeque; use std::marker::PhantomData; /// A type that holds a value satisfying the `HeadRule` @@ -15,9 +15,6 @@ pub type HeadVec = Head::Item>>; /// A type that holds a VecDeque value satisfying the `HeadRule` pub type HeadVecDeque = Head::Item>>; -/// A type that holds a HashSet value satisfying the `HeadRule` -pub type HeadHashSet = Head::Item>>; - /// A type that holds a String value satisfying the `HeadRule` pub type HeadString = Head; From c72c548ce93f16721eb3f706db70e30bd0fa41b5 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:50:28 +0900 Subject: [PATCH 04/20] fix static str --- src/rule/collection/head/string.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rule/collection/head/string.rs b/src/rule/collection/head/string.rs index f54aee4..caf7328 100644 --- a/src/rule/collection/head/string.rs +++ b/src/rule/collection/head/string.rs @@ -17,11 +17,11 @@ where } } -impl Rule for HeadRule +impl<'a, RULE> Rule for HeadRule where RULE: Rule, { - type Item = &'static str; + type Item = &'a str; fn validate(target: &Self::Item) -> Result<(), Error> { if let Some(head) = target.chars().next() { From 04de62962590cd7cc44b7497f80eced265bf0e5d Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:57:24 +0900 Subject: [PATCH 05/20] implement ForAllHashMap --- src/rule/collection/for_all.rs | 5 ++++- src/rule/collection/for_all/collection.rs | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/rule/collection/for_all.rs b/src/rule/collection/for_all.rs index 49087e5..199db95 100644 --- a/src/rule/collection/for_all.rs +++ b/src/rule/collection/for_all.rs @@ -1,7 +1,7 @@ mod collection; mod string; -use std::collections::{HashSet, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; use std::marker::PhantomData; use crate::rule::Rule; @@ -19,6 +19,9 @@ pub type ForAllVecDeque = ForAll::Item>>; /// A type that holds a HashSet value satisfying the `ForAllRule` pub type ForAllHashSet = ForAll::Item>>; +/// A type that holds a HashMap value satisfying the `ForAllRule` +pub type ForAllHashMap = ForAll::Item>>; + /// A type that holds a String value satisfying the `ForAllRule` pub type ForAllString = ForAll; diff --git a/src/rule/collection/for_all/collection.rs b/src/rule/collection/for_all/collection.rs index 53834e4..9f096ee 100644 --- a/src/rule/collection/for_all/collection.rs +++ b/src/rule/collection/for_all/collection.rs @@ -1,4 +1,4 @@ -use std::collections::{HashSet, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; use crate::result::Error; use crate::rule::ForAllRule; @@ -26,3 +26,18 @@ macro_rules! impl_for_all { } impl_for_all![Vec, VecDeque, HashSet]; + +impl Rule for ForAllRule> +where + RULE: Rule, +{ + type Item = HashMap; + + fn validate(target: &Self::Item) -> Result<(), Error> { + if target.values().all(|item| RULE::validate(item).is_ok()) { + Ok(()) + } else { + Err(Error::new("not all items satisfy the condition")) + } + } +} From fe8f475d69ff62a3382e2e73ca176fe9286b4578 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:59:27 +0900 Subject: [PATCH 06/20] implement ExistHashMap --- src/rule/collection/exists.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rule/collection/exists.rs b/src/rule/collection/exists.rs index 47cee60..be1c43b 100644 --- a/src/rule/collection/exists.rs +++ b/src/rule/collection/exists.rs @@ -1,7 +1,7 @@ use crate::rule::composer::Not; use crate::rule::{ForAllRule, Rule}; use crate::Refined; -use std::collections::{HashSet, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; /// A type that holds a value satisfying the `ExistsRule` pub type Exists = Refined>; @@ -15,6 +15,9 @@ pub type ExistsVecDeque = Exists::Item>>; /// A type that holds a HashSet value satisfying the `ExistsRule` pub type ExistsHashSet = Exists::Item>>; +/// A type that holds a HashMap value satisfying the `ExistsRule` +pub type ExistsHashMap = Exists::Item>>; + /// A type that holds a String value satisfying the `ExistsRule` pub type ExistsString = Exists; From 6a99d8cda26fac8a450e5828e5598bf0b784ba52 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:02:08 +0900 Subject: [PATCH 07/20] fix: str lifetime in Forall --- src/rule/collection/for_all/string.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rule/collection/for_all/string.rs b/src/rule/collection/for_all/string.rs index 5499833..f82b72f 100644 --- a/src/rule/collection/for_all/string.rs +++ b/src/rule/collection/for_all/string.rs @@ -18,11 +18,11 @@ where } } -impl Rule for ForAllRule +impl<'a, RULE> Rule for ForAllRule where RULE: Rule, { - type Item = &'static str; + type Item = &'a str; fn validate(target: &Self::Item) -> Result<(), Error> { if target.chars().all(|item| RULE::validate(&item).is_ok()) { From 77a6e440170249ca1ddab6024b887d0a29252911 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 20:16:03 +0900 Subject: [PATCH 08/20] impl: Index --- src/rule/collection.rs | 2 + src/rule/collection/exists.rs | 28 +++++-- src/rule/collection/for_all.rs | 25 ++++-- src/rule/collection/index.rs | 149 +++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 src/rule/collection/index.rs diff --git a/src/rule/collection.rs b/src/rule/collection.rs index d10db7a..9149c85 100644 --- a/src/rule/collection.rs +++ b/src/rule/collection.rs @@ -1,7 +1,9 @@ mod exists; mod for_all; mod head; +mod index; pub use exists::*; pub use for_all::*; pub use head::*; +pub use index::*; diff --git a/src/rule/collection/exists.rs b/src/rule/collection/exists.rs index be1c43b..e517aa3 100644 --- a/src/rule/collection/exists.rs +++ b/src/rule/collection/exists.rs @@ -1,29 +1,45 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + use crate::rule::composer::Not; use crate::rule::{ForAllRule, Rule}; use crate::Refined; -use std::collections::{HashMap, HashSet, VecDeque}; /// A type that holds a value satisfying the `ExistsRule` pub type Exists = Refined>; /// A type that holds a Vec value satisfying the `ExistsRule` -pub type ExistsVec = Exists::Item>>; +pub type ExistsVec = Refined>; /// A type that holds a VecDeque value satisfying the `ExistsRule` -pub type ExistsVecDeque = Exists::Item>>; +pub type ExistsVecDeque = Refined>; /// A type that holds a HashSet value satisfying the `ExistsRule` -pub type ExistsHashSet = Exists::Item>>; +pub type ExistsHashSet = Refined>; /// A type that holds a HashMap value satisfying the `ExistsRule` -pub type ExistsHashMap = Exists::Item>>; +pub type ExistsHashMap = Refined>; /// A type that holds a String value satisfying the `ExistsRule` -pub type ExistsString = Exists; +pub type ExistsString = Refined>; /// Rule where at least one data in the collection satisfies the condition pub type ExistsRule = Not, ITERABLE>>; +/// Rule where at least one data in the `Vec` satisfies the condition +pub type ExistsVecRule = ExistsRule::Item>>; + +/// Rule where at least one data in the `VecDeque` satisfies the condition +pub type ExistsVecDequeRule = ExistsRule::Item>>; + +/// Rule where at least one data in the `HashSet` satisfies the condition +pub type ExistsHashSetRule = ExistsRule::Item>>; + +/// Rule where at least one data in the `HashMap` satisfies the condition +pub type ExistsHashMapRule = ExistsRule::Item>>; + +/// Rule where at least one data in the `String` satisfies the condition +pub type ExistsStringRule = ExistsRule; + #[cfg(test)] mod tests { use crate::rule::{Exists, NonEmptyStringRule}; diff --git a/src/rule/collection/for_all.rs b/src/rule/collection/for_all.rs index 199db95..0fe8aae 100644 --- a/src/rule/collection/for_all.rs +++ b/src/rule/collection/for_all.rs @@ -11,19 +11,19 @@ use crate::Refined; pub type ForAll = Refined>; /// A type that holds a Vec value satisfying the `ForAllRule` -pub type ForAllVec = ForAll::Item>>; +pub type ForAllVec = Refined>; /// A type that holds a VecDeque value satisfying the `ForAllRule` -pub type ForAllVecDeque = ForAll::Item>>; +pub type ForAllVecDeque = Refined>; /// A type that holds a HashSet value satisfying the `ForAllRule` -pub type ForAllHashSet = ForAll::Item>>; +pub type ForAllHashSet = Refined>; /// A type that holds a HashMap value satisfying the `ForAllRule` -pub type ForAllHashMap = ForAll::Item>>; +pub type ForAllHashMap = Refined>; /// A type that holds a String value satisfying the `ForAllRule` -pub type ForAllString = ForAll; +pub type ForAllString = Refined>; /// Rule where all the data in the collection satisfies the condition pub struct ForAllRule @@ -33,6 +33,21 @@ where _phantom_data: PhantomData<(RULE, ITERABLE)>, } +/// Rule where all the data in the `Vec` satisfies the condition +pub type ForAllVecRule = ForAllRule::Item>>; + +/// Rule where all the data in the `VecDeque` satisfies the condition +pub type ForAllVecDequeRule = ForAllRule::Item>>; + +/// Rule where all the data in the `HashSet` satisfies the condition +pub type ForAllHashSetRule = ForAllRule::Item>>; + +/// Rule where all the data in the `HashMap` satisfies the condition +pub type ForAllHashMapRule = ForAllRule::Item>>; + +/// Rule where all the data in the `String` satisfies the condition +pub type ForAllStringRule = ForAllRule; + #[cfg(test)] mod tests { use crate::result::Error; diff --git a/src/rule/collection/index.rs b/src/rule/collection/index.rs new file mode 100644 index 0000000..e0c47a3 --- /dev/null +++ b/src/rule/collection/index.rs @@ -0,0 +1,149 @@ +use std::collections::VecDeque; + +use crate::rule::Rule; + +#[macro_export] +macro_rules! define_index_refined { + ($lit:literal) => { + $crate::paste::item! { + pub type [] = $crate::Refined<[]>; + + pub type [] = $crate::Refined<[]>; + + pub type [] = $crate::Refined<[]>; + + pub type [] = $crate::Refined<[]>; + } + }; + ($lit:literal, $($lits:literal),*) => { + define_index_refined!($lit); + define_index_refined!($($lits),*); + } +} + +#[macro_export] +macro_rules! define_index_rule { + ($lit:literal) => { + $crate::paste::item! { + pub struct [] + where + RULE: $crate::rule::Rule, + { + _phantom_data: ::std::marker::PhantomData<(RULE, ITERABLE)>, + } + + pub type [] = []::Item>>; + impl $crate::rule::Rule for []> where RULE: $crate::rule::Rule { + type Item = Vec; + + fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { + let item = target.get($lit).ok_or_else(|| $crate::result::Error::new(format!("index {} is out of bounds", $lit)))?; + if RULE::validate(item).is_ok() { + Ok(()) + } else { + Err($crate::result::Error::new(format!("the item at index {} does not satisfy the condition", $lit))) + } + } + } + + pub type [] = []::Item>>; + impl $crate::rule::Rule for []> where RULE: $crate::rule::Rule { + type Item = ::std::collections::VecDeque; + + fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { + let item = target.get($lit).ok_or_else(|| $crate::result::Error::new(format!("index {} is out of bounds", $lit)))?; + if RULE::validate(item).is_ok() { + Ok(()) + } else { + Err($crate::result::Error::new(format!("the item at index {} does not satisfy the condition", $lit))) + } + } + } + + pub type [] = []; + impl $crate::rule::Rule for [] where RULE: $crate::rule::Rule { + type Item = String; + + fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { + let item = target.chars().nth($lit).ok_or_else(|| $crate::result::Error::new(format!("index {} is out of bounds", $lit)))?; + if RULE::validate(&item).is_ok() { + Ok(()) + } else { + Err($crate::result::Error::new(format!("the character at index {} does not satisfy the condition", $lit))) + } + } + } + + impl <'a, RULE> $crate::rule::Rule for [] where RULE: $crate::rule::Rule { + type Item = &'a str; + + fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { + let item = target.chars().nth($lit).ok_or_else(|| $crate::result::Error::new(format!("index {} is out of bounds", $lit)))?; + if RULE::validate(&item).is_ok() { + Ok(()) + } else { + Err($crate::result::Error::new(format!("the character at index {} does not satisfy the condition", $lit))) + } + } + } + } + }; + ($lit:literal, $($lits:literal),*) => { + define_index_rule!($lit); + define_index_rule!($($lits),*); + } +} + +// define index refined type for 0 ~ 10 by default. +// if you want to define additional refined index types, you can add more using `define_index_refined`. +define_index_refined!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +// define index rules for 0 ~ 10 by default +// if you want to define additional index rules, you can add more using `define_index_rule`. +define_index_rule!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +#[cfg(test)] +mod tests { + use crate::rule::{Index0Vec, Index1Vec, Index2Vec, NonEmptyStringRule}; + + #[test] + fn test_index_0_non_empty_string() -> anyhow::Result<()> { + let table = vec![ + (vec!["good morning".to_string(), "hello".to_string()], true), + (vec!["good morning".to_string(), "".to_string()], true), + (vec!["".to_string(), "hello".to_string()], false), + (vec!["".to_string(), "".to_string()], false), + ]; + + for (value, expected) in table { + let refined = Index0Vec::::new(value.clone()); + assert_eq!(refined.is_ok(), expected); + } + + Ok(()) + } + + #[test] + fn test_index_1_non_empty_string() -> anyhow::Result<()> { + let table = vec![ + (vec!["good morning".to_string(), "hello".to_string()], true), + (vec!["good morning".to_string(), "".to_string()], false), + (vec!["".to_string(), "hello".to_string()], true), + (vec!["".to_string(), "".to_string()], false), + ]; + + for (value, expected) in table { + let refined = Index1Vec::::new(value.clone()); + assert_eq!(refined.is_ok(), expected); + } + + Ok(()) + } + + #[test] + fn test_index_2_non_empty_string_out_of_bounds() { + let value = vec!["good morning".to_string(), "hello".to_string()]; + let refined = Index2Vec::::new(value); + assert!(refined.is_err()); + } +} From b240f5b0350f9e66f3b4205d63a9c24b8dcbc809 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 20:22:27 +0900 Subject: [PATCH 09/20] refactor: Head --- src/rule/collection/head.rs | 28 ++++++++++----------- src/rule/collection/head/collection.rs | 34 -------------------------- src/rule/collection/head/string.rs | 33 ------------------------- 3 files changed, 14 insertions(+), 81 deletions(-) delete mode 100644 src/rule/collection/head/collection.rs delete mode 100644 src/rule/collection/head/string.rs diff --git a/src/rule/collection/head.rs b/src/rule/collection/head.rs index b808e26..699fb84 100644 --- a/src/rule/collection/head.rs +++ b/src/rule/collection/head.rs @@ -1,30 +1,30 @@ -mod collection; -mod string; - -use crate::rule::Rule; +use crate::rule::{Index0Rule, Rule}; use crate::Refined; use std::collections::VecDeque; -use std::marker::PhantomData; /// A type that holds a value satisfying the `HeadRule` pub type Head = Refined>; /// A type that holds a Vec value satisfying the `HeadRule` -pub type HeadVec = Head::Item>>; +pub type HeadVec = Refined>; /// A type that holds a VecDeque value satisfying the `HeadRule` -pub type HeadVecDeque = Head::Item>>; +pub type HeadVecDeque = Refined>; /// A type that holds a String value satisfying the `HeadRule` -pub type HeadString = Head; +pub type HeadString = Refined>; /// Rule where the first element satisfies the condition -pub struct HeadRule -where - RULE: Rule, -{ - _phantom_data: PhantomData<(RULE, ITERABLE)>, -} +pub type HeadRule = Index0Rule; + +/// Rule where the first element in the `Vec` satisfies the condition +pub type HeadVecRule = HeadRule::Item>>; + +/// Rule where the first element in the `VecDeque` satisfies the condition +pub type HeadVecDequeRule = HeadRule::Item>>; + +/// Rule where the first element in the `String` satisfies the condition +pub type HeadStringRule = HeadRule; #[cfg(test)] mod tests { diff --git a/src/rule/collection/head/collection.rs b/src/rule/collection/head/collection.rs deleted file mode 100644 index 24a2e7e..0000000 --- a/src/rule/collection/head/collection.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::result::Error; -use crate::rule::collection::head::HeadRule; -use crate::rule::Rule; -use std::collections::VecDeque; - -impl Rule for HeadRule> -where - RULE: Rule, -{ - type Item = Vec; - - fn validate(target: &Self::Item) -> Result<(), Error> { - if let Some(head) = target.first() { - RULE::validate(head) - } else { - Err(Error::new("empty collection")) - } - } -} - -impl Rule for HeadRule> -where - RULE: Rule, -{ - type Item = VecDeque; - - fn validate(target: &Self::Item) -> Result<(), Error> { - if let Some(head) = target.front() { - RULE::validate(head) - } else { - Err(Error::new("empty collection")) - } - } -} diff --git a/src/rule/collection/head/string.rs b/src/rule/collection/head/string.rs deleted file mode 100644 index caf7328..0000000 --- a/src/rule/collection/head/string.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::result::Error; -use crate::rule::collection::head::HeadRule; -use crate::rule::Rule; - -impl Rule for HeadRule -where - RULE: Rule, -{ - type Item = String; - - fn validate(target: &Self::Item) -> Result<(), Error> { - if let Some(head) = target.chars().next() { - RULE::validate(&head) - } else { - Err(Error::new("empty string")) - } - } -} - -impl<'a, RULE> Rule for HeadRule -where - RULE: Rule, -{ - type Item = &'a str; - - fn validate(target: &Self::Item) -> Result<(), Error> { - if let Some(head) = target.chars().next() { - RULE::validate(&head) - } else { - Err(Error::new("empty string")) - } - } -} From 3f6e30bc76b48cbbafaa5fc9dbe6bf7fbbf7834b Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 20:58:21 +0900 Subject: [PATCH 10/20] refactor: Last --- src/rule/collection.rs | 2 + src/rule/collection/last.rs | 70 ++++++++++++++++++++++++++ src/rule/collection/last/collection.rs | 42 ++++++++++++++++ src/rule/collection/last/string.rs | 43 ++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 src/rule/collection/last.rs create mode 100644 src/rule/collection/last/collection.rs create mode 100644 src/rule/collection/last/string.rs diff --git a/src/rule/collection.rs b/src/rule/collection.rs index 9149c85..2c246fd 100644 --- a/src/rule/collection.rs +++ b/src/rule/collection.rs @@ -2,8 +2,10 @@ mod exists; mod for_all; mod head; mod index; +mod last; pub use exists::*; pub use for_all::*; pub use head::*; pub use index::*; +pub use last::*; diff --git a/src/rule/collection/last.rs b/src/rule/collection/last.rs new file mode 100644 index 0000000..5a71cac --- /dev/null +++ b/src/rule/collection/last.rs @@ -0,0 +1,70 @@ +mod collection; +mod string; + +use std::collections::VecDeque; +use std::marker::PhantomData; + +use crate::rule::Rule; +use crate::Refined; + +/// A type that holds a value satisfying the `LastRule` +pub type Last = Refined>; + +/// A type that holds a Vec value satisfying the `LastRule` +pub type LastVec = Refined>; + +/// A type that holds a VecDeque value satisfying the `LastRule` +pub type LastVecDeque = Refined>; + +/// A type that holds a String value satisfying the `LastRule` +pub type LastString = Refined>; + +/// Rule where the last element satisfies the condition +pub struct LastRule { + _phantom_data: PhantomData<(RULE, ITERABLE)>, +} + +/// Rule where the last element in the `Vec` satisfies the condition +pub type LastVecRule = LastRule::Item>>; + +/// Rule where the last element in the `VecDeque` satisfies the condition +pub type LastVecDequeRule = LastRule::Item>>; + +/// Rule where the last element in the `String` satisfies the condition +pub type LastStringRule = LastRule; + +#[cfg(test)] +mod tests { + use crate::rule::{LastVec, NonEmptyStringRule}; + + #[test] + fn last_valid() -> anyhow::Result<()> { + let table = vec![ + vec!["".to_string(), "hello".to_string()], + vec!["good morning".to_string(), "hello".to_string()], + ]; + + for value in table { + let last = LastVec::::new(value.clone())?; + assert_eq!(last.into_value(), value); + } + + Ok(()) + } + + #[test] + fn last_invalid() -> anyhow::Result<()> { + let table = vec![ + vec![], + vec!["".to_string()], + vec!["hello".to_string(), "".to_string()], + ]; + + for value in table { + let last_result = LastVec::::new(value.clone()); + assert!(last_result.is_err()); + } + + Ok(()) + } +} diff --git a/src/rule/collection/last/collection.rs b/src/rule/collection/last/collection.rs new file mode 100644 index 0000000..ae0248f --- /dev/null +++ b/src/rule/collection/last/collection.rs @@ -0,0 +1,42 @@ +use crate::rule::{LastRule, Rule}; +use std::collections::VecDeque; + +impl Rule for LastRule> +where + RULE: Rule, +{ + type Item = Vec; + + fn validate(target: &Self::Item) -> Result<(), crate::result::Error> { + let item = target + .last() + .ok_or_else(|| crate::result::Error::new("the vector is empty"))?; + if RULE::validate(item).is_ok() { + Ok(()) + } else { + Err(crate::result::Error::new( + "the last item does not satisfy the condition", + )) + } + } +} + +impl Rule for LastRule> +where + RULE: Rule, +{ + type Item = VecDeque; + + fn validate(target: &Self::Item) -> Result<(), crate::result::Error> { + let item = target + .back() + .ok_or_else(|| crate::result::Error::new("the deque is empty"))?; + if RULE::validate(item).is_ok() { + Ok(()) + } else { + Err(crate::result::Error::new( + "the last item does not satisfy the condition", + )) + } + } +} diff --git a/src/rule/collection/last/string.rs b/src/rule/collection/last/string.rs new file mode 100644 index 0000000..e83d262 --- /dev/null +++ b/src/rule/collection/last/string.rs @@ -0,0 +1,43 @@ +use crate::rule::{LastRule, Rule}; + +impl Rule for LastRule +where + RULE: Rule, +{ + type Item = String; + + fn validate(target: &Self::Item) -> Result<(), crate::result::Error> { + let item = target + .chars() + .last() + .ok_or_else(|| crate::result::Error::new("the string is empty"))?; + if RULE::validate(&item).is_ok() { + Ok(()) + } else { + Err(crate::result::Error::new( + "the last character does not satisfy the condition", + )) + } + } +} + +impl<'a, RULE> Rule for LastRule +where + RULE: Rule, +{ + type Item = &'a str; + + fn validate(target: &Self::Item) -> Result<(), crate::result::Error> { + let item = target + .chars() + .last() + .ok_or_else(|| crate::result::Error::new("the string is empty"))?; + if RULE::validate(&item).is_ok() { + Ok(()) + } else { + Err(crate::result::Error::new( + "the last character does not satisfy the condition", + )) + } + } +} From fb768c39c949d000895f650aa98ada784be0c23b Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:26:13 +0900 Subject: [PATCH 11/20] test: implement README tests --- README.md | 121 ++++++++++++++++++++++++++++++++--------------- tests/read_me.rs | 77 +++++++++++++++++++++++++++--- 2 files changed, 153 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index a2ea252..3da28d0 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,7 @@ type TargetAgeRule = And![TargetAge18OrMore, TargetAge80OrLess]; # Iterator -I have also prepared several useful refined types for Iterators. +`refined_type` has several useful refined types for Iterators. ## `ForAll` @@ -345,6 +345,82 @@ fn example_12() -> anyhow::Result<()> { } ``` +# `Head` + +`Head` is a rule that applies a specific rule to the first element in the Iterator. + +```rust +fn example_13() -> anyhow::Result<()> { + let table = vec![ + (vec!["good morning".to_string(), "".to_string()], true), // PASS + (vec!["hello".to_string(), "hello".to_string()], true), // PASS + (vec![], false), // FAIL + (vec!["".to_string()], false), // FAIL + (vec!["".to_string(), "hello".to_string()], false), // FAIL + ]; + + for (value, ok) in table { + let head = HeadVec::::new(value.clone()); + assert_eq!(head.is_ok(), ok); + } + + Ok(()) +} +``` + +# `Last` + +`Last` is a rule that applies a specific rule to the last element in the Iterator. + +```rust +fn example_14() -> anyhow::Result<()> { + let table = vec![ + (vec!["".to_string(), "hello".to_string()], true), // PASS + (vec!["good morning".to_string(), "hello".to_string()], true), // PASS + (vec![], false), // FAIL + (vec!["".to_string()], false), // FAIL + (vec!["hello".to_string(), "".to_string()], false), // FAIL + ]; + + for (value, ok) in table { + let last = LastVec::::new(value.clone()); + assert_eq!(last.is_ok(), ok); + } + + Ok(()) +} +``` + +# `Tail` + +`Tail` is a rule that applies a specific rule to all elements except the first element in the Iterator. + +```rust +fn example_15() -> anyhow::Result<()> { + Ok(()) +} +``` + +# `Init` + +`Init` is a rule that applies a specific rule to all elements except the last element in the Iterator. + +```rust +fn example_16() -> anyhow::Result<()> { + Ok(()) +} +``` + +# `Index` + +`Index` is a rule that applies a specific rule to the element at a specific index in the Iterator. + +```rust +fn example_17() -> anyhow::Result<()> { + Ok(()) +} +``` + --- ## `into_iter()` and `iter()` @@ -356,7 +432,7 @@ Feel free to explore the capabilities of the Iterator you’ve been given! ### `into_iter()` ```rust -fn example_11() -> anyhow::Result<()> { +fn example_18() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.into_iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -367,7 +443,7 @@ fn example_11() -> anyhow::Result<()> { ### `iter()` ```rust -fn example_12() -> anyhow::Result<()> { +fn example_19() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -378,7 +454,7 @@ fn example_12() -> anyhow::Result<()> { ### `NonEmptyVec` to `NonEmptyVecDeque` using `collect()` ```rust -fn example_13() -> anyhow::Result<()> { +fn example_20() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec_deque: NonEmptyVecDeque = ne_vec.into_iter().collect(); assert_eq!(ne_vec_deque.into_value(), vec![1, 2, 3]); @@ -386,37 +462,6 @@ fn example_13() -> anyhow::Result<()> { } ``` -# Add Trait - -I have implemented the `Add` trait for a part of the `Refined` that I provided. Therefore, operations can be performed -without downgrading the type level. - -### NonEmptyString - -```rust -fn example_14() -> anyhow::Result<()> { - let non_empty_string_1 = NonEmptyString::new("Hello".to_string())?; - let non_empty_string_2 = NonEmptyString::new("World".to_string())?; - let non_empty_string = non_empty_string_1 + non_empty_string_2; // This is also `NonEmptyString` type - - assert_eq!(non_empty_string.into_value(), "HelloWorld"); - Ok(()) -} -``` - -### NonEmptyVec - -```rust -fn example_15() -> anyhow::Result<()> { - let ne_vec_1 = NonEmptyVec::new(vec![1, 2, 3])?; - let ne_vec_2 = NonEmptyVec::new(vec![4, 5, 6])?; - let ne_vec = ne_vec_1 + ne_vec_2; // This is also `NonEmptyVec` type - - assert_eq!(ne_vec.into_value(), vec![1, 2, 3, 4, 5, 6]); - Ok(()) -} -``` - # Length You can impose constraints on objects that have a length, such as `String` or `Vec`. @@ -424,7 +469,7 @@ You can impose constraints on objects that have a length, such as `String` or `V ### String ```rust -fn example_16() -> Result<(), Error> { +fn example_21() -> Result<(), Error> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -459,7 +504,7 @@ fn example_16() -> Result<(), Error> { ```rust #[test] -fn example_17() -> anyhow::Result<()> { +fn example_22() -> anyhow::Result<()> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -516,7 +561,7 @@ by `refined_type`, you can easily do so using `LengthDefinition`. ```rust #[test] -fn example_18() -> anyhow::Result<()> { +fn example_23() -> anyhow::Result<()> { length_equal!(5); #[derive(Debug, PartialEq)] diff --git a/tests/read_me.rs b/tests/read_me.rs index b89e88f..3ce8c39 100644 --- a/tests/read_me.rs +++ b/tests/read_me.rs @@ -4,8 +4,8 @@ use serde_json::json; use refined_type::result::Error; use refined_type::rule::composer::Not; use refined_type::rule::{ - ExistsVec, ForAllVec, LengthDefinition, NonEmptyRule, NonEmptyString, NonEmptyStringRule, - NonEmptyVec, NonEmptyVecDeque, Rule, + ExistsVec, ForAllVec, HeadVec, Index1Vec, LastVec, LengthDefinition, NonEmptyRule, + NonEmptyString, NonEmptyStringRule, NonEmptyVec, NonEmptyVecDeque, Rule, }; use refined_type::{ equal_rule, greater_rule, length_equal, length_greater_than, length_less_than, less_rule, And, @@ -285,6 +285,69 @@ fn example_12() -> anyhow::Result<()> { #[test] fn example_13() -> anyhow::Result<()> { + let table = vec![ + (vec!["good morning".to_string(), "".to_string()], true), // PASS + (vec!["hello".to_string(), "hello".to_string()], true), // PASS + (vec![], false), // FAIL + (vec!["".to_string()], false), // FAIL + (vec!["".to_string(), "hello".to_string()], false), // FAIL + ]; + + for (value, ok) in table { + let head = HeadVec::::new(value.clone()); + assert_eq!(head.is_ok(), ok); + } + + Ok(()) +} + +#[test] +fn example_14() -> anyhow::Result<()> { + let table = vec![ + (vec!["".to_string(), "hello".to_string()], true), // PASS + (vec!["good morning".to_string(), "hello".to_string()], true), // PASS + (vec![], false), // FAIL + (vec!["".to_string()], false), // FAIL + (vec!["hello".to_string(), "".to_string()], false), // FAIL + ]; + + for (value, ok) in table { + let last = LastVec::::new(value.clone()); + assert_eq!(last.is_ok(), ok); + } + + Ok(()) +} + +#[test] +fn example_15() -> anyhow::Result<()> { + Ok(()) +} + +#[test] +fn example_16() -> anyhow::Result<()> { + Ok(()) +} + +#[test] +fn example_17() -> anyhow::Result<()> { + let table = vec![ + (vec!["good morning".to_string(), "hello".to_string()], true), + (vec!["good morning".to_string(), "".to_string()], false), + (vec!["".to_string(), "hello".to_string()], true), + (vec!["".to_string(), "".to_string()], false), + ]; + + for (value, expected) in table { + let refined = Index1Vec::::new(value.clone()); + assert_eq!(refined.is_ok(), expected); + } + + Ok(()) +} + +#[test] +fn example_18() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.into_iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -292,7 +355,7 @@ fn example_13() -> anyhow::Result<()> { } #[test] -fn example_14() -> anyhow::Result<()> { +fn example_19() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -300,7 +363,7 @@ fn example_14() -> anyhow::Result<()> { } #[test] -fn example_15() -> anyhow::Result<()> { +fn example_20() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec_deque: NonEmptyVecDeque = ne_vec.into_iter().collect(); assert_eq!(ne_vec_deque.into_value(), vec![1, 2, 3]); @@ -308,7 +371,7 @@ fn example_15() -> anyhow::Result<()> { } #[test] -fn example_16() -> Result<(), Error> { +fn example_21() -> Result<(), Error> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -339,7 +402,7 @@ fn example_16() -> Result<(), Error> { } #[test] -fn example_17() -> anyhow::Result<()> { +fn example_22() -> anyhow::Result<()> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -389,7 +452,7 @@ fn example_17() -> anyhow::Result<()> { } #[test] -fn example_18() -> anyhow::Result<()> { +fn example_23() -> anyhow::Result<()> { #[derive(Debug, PartialEq)] struct Hello; impl LengthDefinition for Hello { From b4859ddc9403874bb33b907050e4141cdd7b8456 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:17:24 +0900 Subject: [PATCH 12/20] impl: init --- src/rule/collection.rs | 2 + src/rule/collection/for_all.rs | 15 ++---- src/rule/collection/for_all/collection.rs | 43 ----------------- src/rule/collection/for_all/string.rs | 36 -------------- src/rule/collection/init.rs | 57 +++++++++++++++++++++++ src/rule/collection/init/collection.rs | 54 +++++++++++++++++++++ src/rule/collection/init/string.rs | 54 +++++++++++++++++++++ 7 files changed, 170 insertions(+), 91 deletions(-) delete mode 100644 src/rule/collection/for_all/collection.rs delete mode 100644 src/rule/collection/for_all/string.rs create mode 100644 src/rule/collection/init.rs create mode 100644 src/rule/collection/init/collection.rs create mode 100644 src/rule/collection/init/string.rs diff --git a/src/rule/collection.rs b/src/rule/collection.rs index 2c246fd..ee176f9 100644 --- a/src/rule/collection.rs +++ b/src/rule/collection.rs @@ -2,10 +2,12 @@ mod exists; mod for_all; mod head; mod index; +mod init; mod last; pub use exists::*; pub use for_all::*; pub use head::*; pub use index::*; +pub use init::*; pub use last::*; diff --git a/src/rule/collection/for_all.rs b/src/rule/collection/for_all.rs index 0fe8aae..e748f63 100644 --- a/src/rule/collection/for_all.rs +++ b/src/rule/collection/for_all.rs @@ -1,11 +1,7 @@ -mod collection; -mod string; - use std::collections::{HashMap, HashSet, VecDeque}; -use std::marker::PhantomData; -use crate::rule::Rule; -use crate::Refined; +use crate::rule::{InitRule, LastRule, Rule}; +use crate::{And, Refined}; /// A type that holds a value satisfying the `ForAllRule` pub type ForAll = Refined>; @@ -26,12 +22,7 @@ pub type ForAllHashMap = Refined>; pub type ForAllString = Refined>; /// Rule where all the data in the collection satisfies the condition -pub struct ForAllRule -where - RULE: Rule, -{ - _phantom_data: PhantomData<(RULE, ITERABLE)>, -} +pub type ForAllRule = And![InitRule, LastRule]; /// Rule where all the data in the `Vec` satisfies the condition pub type ForAllVecRule = ForAllRule::Item>>; diff --git a/src/rule/collection/for_all/collection.rs b/src/rule/collection/for_all/collection.rs deleted file mode 100644 index 9f096ee..0000000 --- a/src/rule/collection/for_all/collection.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::collections::{HashMap, HashSet, VecDeque}; - -use crate::result::Error; -use crate::rule::ForAllRule; -use crate::rule::Rule; - -macro_rules! impl_for_all { - ($($t:ty),*) => { - $( - impl Rule for ForAllRule - where - RULE: Rule, - { - type Item = $t; - - fn validate(target: &Self::Item) -> Result<(), Error> { - if target.iter().all(|item| RULE::validate(item).is_ok()) { - Ok(()) - } else { - Err(Error::new("not all items satisfy the condition")) - } - } - } - )* - }; -} - -impl_for_all![Vec, VecDeque, HashSet]; - -impl Rule for ForAllRule> -where - RULE: Rule, -{ - type Item = HashMap; - - fn validate(target: &Self::Item) -> Result<(), Error> { - if target.values().all(|item| RULE::validate(item).is_ok()) { - Ok(()) - } else { - Err(Error::new("not all items satisfy the condition")) - } - } -} diff --git a/src/rule/collection/for_all/string.rs b/src/rule/collection/for_all/string.rs deleted file mode 100644 index f82b72f..0000000 --- a/src/rule/collection/for_all/string.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::result::Error; -use crate::rule::{ForAllRule, Rule}; - -impl Rule for ForAllRule -where - RULE: Rule, -{ - type Item = String; - - fn validate(target: &Self::Item) -> Result<(), Error> { - if target.chars().all(|item| RULE::validate(&item).is_ok()) { - Ok(()) - } else { - Err(Error::new(format!( - "{target} does not satisfy the condition" - ))) - } - } -} - -impl<'a, RULE> Rule for ForAllRule -where - RULE: Rule, -{ - type Item = &'a str; - - fn validate(target: &Self::Item) -> Result<(), Error> { - if target.chars().all(|item| RULE::validate(&item).is_ok()) { - Ok(()) - } else { - Err(Error::new(format!( - "{target} does not satisfy the condition" - ))) - } - } -} diff --git a/src/rule/collection/init.rs b/src/rule/collection/init.rs new file mode 100644 index 0000000..660e328 --- /dev/null +++ b/src/rule/collection/init.rs @@ -0,0 +1,57 @@ +use crate::rule::Rule; +use crate::Refined; +use std::collections::VecDeque; +use std::marker::PhantomData; + +mod collection; +mod string; + +/// A type that holds a value satisfying the `InitRule` +pub type Init = Refined>; + +/// A type that holds a Vec value satisfying the `InitRule` +pub type InitVec = Refined>; + +/// A type that holds a VecDeque value satisfying the `InitRule` +pub type InitVecDeque = Refined>; + +/// A type that holds a String value satisfying the `InitRule` +pub type InitString = Refined>; + +/// Rule that applies to the initialization of a collection +pub struct InitRule { + _phantom_data: PhantomData<(RULE, ITERABLE)>, +} + +/// Rule that applies to the initialization of a `Vec` +pub type InitVecRule = InitRule::Item>>; + +/// Rule that applies to the initialization of a `VecDeque` +pub type InitVecDequeRule = InitRule::Item>>; + +/// Rule that applies to the initialization of a `String` +pub type InitStringRule = InitRule; + +#[cfg(test)] +mod tests { + use crate::rule::{InitVec, NonEmptyStringRule}; + + #[test] + fn init_valid() -> anyhow::Result<()> { + let table = vec![ + vec![ + "hello".to_string(), + "hello".to_string(), + "hello".to_string(), + ], + vec!["hello".to_string(), "hello".to_string(), "".to_string()], + ]; + + for value in table { + let init = InitVec::::new(value.clone())?; + assert_eq!(init.into_value(), value); + } + + Ok(()) + } +} diff --git a/src/rule/collection/init/collection.rs b/src/rule/collection/init/collection.rs new file mode 100644 index 0000000..c498502 --- /dev/null +++ b/src/rule/collection/init/collection.rs @@ -0,0 +1,54 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +use crate::result::Error; +use crate::rule::Rule; +use crate::rule::{ForAllRule, InitRule}; + +macro_rules! impl_init { + ($($t:ty),*) => { + $( + impl Rule for InitRule + where + RULE: Rule, + { + type Item = $t; + + fn validate(target: &Self::Item) -> Result<(), Error> { + let length = target.len(); + let mut result = Ok(()); + + for (i, item) in target.iter().enumerate() { + if i == length - 1 { + break; + } + match RULE::validate(item) { + Ok(_) => continue, + Err(e) => { + result = Err(e); + break; + } + } + } + result + } + } + )* + }; +} + +impl_init![Vec, VecDeque, HashSet]; + +impl Rule for ForAllRule> +where + RULE: Rule, +{ + type Item = HashMap; + + fn validate(target: &Self::Item) -> Result<(), Error> { + if target.values().all(|item| RULE::validate(item).is_ok()) { + Ok(()) + } else { + Err(Error::new("not all items satisfy the condition")) + } + } +} diff --git a/src/rule/collection/init/string.rs b/src/rule/collection/init/string.rs new file mode 100644 index 0000000..0bbd38e --- /dev/null +++ b/src/rule/collection/init/string.rs @@ -0,0 +1,54 @@ +use crate::result::Error; +use crate::rule::{InitRule, Rule}; + +impl Rule for InitRule +where + RULE: Rule, +{ + type Item = String; + + fn validate(target: &Self::Item) -> Result<(), Error> { + let length = target.len(); + let mut result = Ok(()); + + for (i, c) in target.chars().enumerate() { + if i == length - 1 { + break; + } + match RULE::validate(&c) { + Ok(_) => continue, + Err(e) => { + result = Err(e); + break; + } + } + } + result + } +} + +impl<'a, RULE> Rule for InitRule +where + RULE: Rule, +{ + type Item = &'a str; + + fn validate(target: &Self::Item) -> Result<(), Error> { + let length = target.len(); + let mut result = Ok(()); + + for (i, c) in target.chars().enumerate() { + if i == length - 1 { + break; + } + match RULE::validate(&c) { + Ok(_) => continue, + Err(e) => { + result = Err(e); + break; + } + } + } + result + } +} From 44eb1b68a040fe6d39b2f18382ad2b4a7529144d Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Mon, 16 Sep 2024 10:41:43 +0900 Subject: [PATCH 13/20] fix rule signature --- src/lib.rs | 2 + src/refined.rs | 35 ++++--- src/result.rs | 19 ++-- src/rule.rs | 4 +- src/rule/collection/exists.rs | 3 +- src/rule/collection/for_all.rs | 10 +- src/rule/collection/head.rs | 3 +- src/rule/collection/index.rs | 72 ++++++++++----- src/rule/collection/init.rs | 3 +- src/rule/collection/init/collection.rs | 77 ++++++++++++---- src/rule/collection/init/string.rs | 47 ++++++---- src/rule/collection/last.rs | 3 +- src/rule/collection/last/collection.rs | 62 +++++++++---- src/rule/collection/last/string.rs | 48 +++++----- src/rule/composer/and.rs | 17 ++-- src/rule/composer/not.rs | 16 ++-- src/rule/composer/or.rs | 26 +++--- src/rule/empty.rs | 21 +++-- src/rule/for_all.rs | 107 ++++++++++++++++++++++ src/rule/length/equal.rs | 10 +- src/rule/length/grater.rs | 10 +- src/rule/length/less.rs | 10 +- src/rule/non_empty.rs | 20 ++-- src/rule/non_empty/non_empty_map.rs | 23 ++--- src/rule/non_empty/non_empty_set.rs | 19 ++-- src/rule/non_empty/non_empty_string.rs | 11 ++- src/rule/non_empty/non_empty_vec.rs | 29 +++--- src/rule/non_empty/non_empty_vec_deque.rs | 21 +++-- src/rule/number/equal.rs | 8 +- src/rule/number/even.rs | 8 +- src/rule/number/greater.rs | 8 +- src/rule/number/less.rs | 8 +- src/rule/number/odd.rs | 8 +- src/rule/string/ipv4.rs | 52 ++++++----- src/rule/string/ipv6.rs | 14 ++- src/rule/string/regex.rs | 11 ++- tests/length.rs | 10 +- tests/read_me.rs | 91 +++++++++--------- 38 files changed, 592 insertions(+), 354 deletions(-) create mode 100644 src/rule/for_all.rs diff --git a/src/lib.rs b/src/lib.rs index 97d5e4c..59bbddc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,5 @@ pub use refined::Refined; mod refined; pub mod result; pub mod rule; + +pub use result::Result; diff --git a/src/refined.rs b/src/refined.rs index 1bc2ddf..754549b 100644 --- a/src/refined.rs +++ b/src/refined.rs @@ -1,8 +1,7 @@ -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt::{Display, Formatter}; - use crate::result::Error; use crate::rule::Rule; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::{Debug, Display, Formatter}; /// Refined is a versatile type in ensuring that `T` satisfies the conditions of `RULE` (predicate type) /// # Example @@ -57,13 +56,23 @@ impl Refined where RULE: Rule, { - pub fn new(value: T) -> Result { - RULE::validate(&value).map_err(|e| Error::new(e.to_string()))?; + pub fn new(value: T) -> Result> { + let value = RULE::validate(value).map_err(|e| { + let message = e.to_string(); + Error::new(e.into_value(), message) + })?; Ok(Self { value }) } - pub fn unsafe_new(value: T) -> Self { - RULE::validate(&value).expect("initialization by `unsafe_new` failed"); + pub fn unsafe_new(value: T) -> Self + where + T: Debug, + { + let value = RULE::validate(value).expect("initialization by `unsafe_new` failed"); + Self { value } + } + + pub(crate) fn new_unchecked(value: T) -> Self { Self { value } } @@ -109,7 +118,7 @@ mod test { } #[test] - fn test_refined_non_empty_string_ok() -> Result<(), Error> { + fn test_refined_non_empty_string_ok() -> Result<(), Error> { let non_empty_string = Refined::::new("Hello".to_string())?; assert_eq!(non_empty_string.value, "Hello"); Ok(()) @@ -123,14 +132,14 @@ mod test { } #[test] - fn test_refined_display() -> Result<(), Error> { + fn test_refined_display() -> Result<(), Error> { let non_empty_string = Refined::::new("Hello".to_string())?; assert_eq!(format!("{}", non_empty_string), "Hello"); Ok(()) } #[test] - fn test_refined_serialize_json_string() -> anyhow::Result<()> { + fn test_refined_serialize_json_string() -> Result<(), Error> { let non_empty_string = Refined::::new("hello".to_string())?; let actual = json!(non_empty_string); @@ -140,7 +149,7 @@ mod test { } #[test] - fn test_refined_serialize_json_struct() -> anyhow::Result<()> { + fn test_refined_serialize_json_struct() -> Result<(), Error> { type NonEmptyString = Refined; #[derive(Serialize)] struct Human { @@ -191,8 +200,8 @@ mod test { let actual = serde_json::from_str::(&json)?; let expected = Human { - name: NonEmptyString::new("john".to_string())?, - friends: NonEmptyVec::new(vec!["tom".to_string(), "taro".to_string()])?, + name: NonEmptyString::unsafe_new("john".to_string()), + friends: NonEmptyVec::unsafe_new(vec!["tom".to_string(), "taro".to_string()]), age: 8, }; assert_eq!(actual, expected); diff --git a/src/result.rs b/src/result.rs index e6e24dc..01fc93e 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,22 +1,29 @@ use std::fmt::{Debug, Display, Formatter}; +/// A type alias for a `Result` to use in the `Refined` module +pub type Result = std::result::Result>; + /// A type indicating a failure to convert to `Refined` #[derive(Debug)] -pub struct Error { +pub struct Error { + value: T, message: String, } -impl std::error::Error for Error {} - -impl Error { - pub fn new(message: impl Into) -> Self { +impl Error { + pub fn new(value: T, message: impl Into) -> Self { Self { + value, message: message.into(), } } + + pub fn into_value(self) -> T { + self.value + } } -impl Display for Error { +impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.message) } diff --git a/src/rule.rs b/src/rule.rs index 108e9b9..14c4533 100644 --- a/src/rule.rs +++ b/src/rule.rs @@ -5,8 +5,6 @@ pub use non_empty::*; pub use number::*; pub use string::*; -use crate::result::Error; - mod collection; pub mod composer; mod empty; @@ -18,5 +16,5 @@ mod string; /// This is a `trait` that specifies the conditions a type `T` should satisfy pub trait Rule { type Item; - fn validate(target: &Self::Item) -> Result<(), Error>; + fn validate(target: Self::Item) -> crate::Result; } diff --git a/src/rule/collection/exists.rs b/src/rule/collection/exists.rs index e517aa3..72666e5 100644 --- a/src/rule/collection/exists.rs +++ b/src/rule/collection/exists.rs @@ -42,10 +42,11 @@ pub type ExistsStringRule = ExistsRule; #[cfg(test)] mod tests { + use crate::result::Error; use crate::rule::{Exists, NonEmptyStringRule}; #[test] - fn exists_1() -> anyhow::Result<()> { + fn exists_1() -> Result<(), Error>> { let value = vec!["good morning".to_string(), "hello".to_string()]; let exists: Exists> = Exists::new(value.clone())?; assert_eq!(exists.into_value(), value); diff --git a/src/rule/collection/for_all.rs b/src/rule/collection/for_all.rs index e748f63..4008a1f 100644 --- a/src/rule/collection/for_all.rs +++ b/src/rule/collection/for_all.rs @@ -46,7 +46,7 @@ mod tests { use crate::rule::{ForAllString, ForAllVec, NonEmptyStringRule, Rule}; #[test] - fn for_all_1() -> anyhow::Result<()> { + fn for_all_1() -> Result<(), Error>> { let value = vec!["good morning".to_string(), "hello".to_string()]; let for_all: ForAllVec = ForAll::new(value.clone())?; assert_eq!(for_all.into_value(), value); @@ -62,16 +62,16 @@ mod tests { } #[test] - fn for_all_3() -> anyhow::Result<()> { + fn for_all_3() -> Result<(), Error> { struct CharRule; impl Rule for CharRule { type Item = char; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { if target.is_alphabetic() { - Ok(()) + Ok(target) } else { - Err(Error::new(format!("{} is not an alphabet", target))) + Err(Error::new(target, format!("{} is not an alphabet", target))) } } } diff --git a/src/rule/collection/head.rs b/src/rule/collection/head.rs index 699fb84..349e776 100644 --- a/src/rule/collection/head.rs +++ b/src/rule/collection/head.rs @@ -28,10 +28,11 @@ pub type HeadStringRule = HeadRule; #[cfg(test)] mod tests { + use crate::result::Error; use crate::rule::{HeadVec, NonEmptyStringRule}; #[test] - fn head_valid() -> anyhow::Result<()> { + fn head_valid() -> Result<(), Error>> { let table = vec![ vec!["good morning".to_string(), "".to_string()], vec!["hello".to_string(), "hello".to_string()], diff --git a/src/rule/collection/index.rs b/src/rule/collection/index.rs index e0c47a3..80c43a5 100644 --- a/src/rule/collection/index.rs +++ b/src/rule/collection/index.rs @@ -36,12 +36,22 @@ macro_rules! define_index_rule { impl $crate::rule::Rule for []> where RULE: $crate::rule::Rule { type Item = Vec; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - let item = target.get($lit).ok_or_else(|| $crate::result::Error::new(format!("index {} is out of bounds", $lit)))?; - if RULE::validate(item).is_ok() { - Ok(()) - } else { - Err($crate::result::Error::new(format!("the item at index {} does not satisfy the condition", $lit))) + fn validate(target: Self::Item) -> Result> { + if $lit >= target.len() { + return Err($crate::result::Error::new(target, format!("index {} is out of bounds", $lit))); + } + let mut target = target; + match RULE::validate(target.remove($lit)) { + Ok(validated_item) => { + let mut result = target; + result.insert($lit, validated_item); + Ok(result) + } + Err(err) => { + let mut result = target; + result.insert($lit, err.into_value()); + Err($crate::result::Error::new(result, format!("the item at index {} does not satisfy the condition", $lit))) + } } } } @@ -50,12 +60,20 @@ macro_rules! define_index_rule { impl $crate::rule::Rule for []> where RULE: $crate::rule::Rule { type Item = ::std::collections::VecDeque; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - let item = target.get($lit).ok_or_else(|| $crate::result::Error::new(format!("index {} is out of bounds", $lit)))?; - if RULE::validate(item).is_ok() { - Ok(()) - } else { - Err($crate::result::Error::new(format!("the item at index {} does not satisfy the condition", $lit))) + fn validate(target: Self::Item) -> Result> { + if $lit >= target.len() { + return Err($crate::result::Error::new(target, format!("index {} is out of bounds", $lit))); + } + let mut target = target; + match RULE::validate(target.remove($lit).expect("unreachable")) { + Ok(validated_item) => { + target.insert($lit, validated_item); + Ok(target) + } + Err(err) => { + target.insert($lit, err.into_value()); + Err($crate::result::Error::new(target, format!("the item at index {} does not satisfy the condition", $lit))) + } } } } @@ -64,12 +82,20 @@ macro_rules! define_index_rule { impl $crate::rule::Rule for [] where RULE: $crate::rule::Rule { type Item = String; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - let item = target.chars().nth($lit).ok_or_else(|| $crate::result::Error::new(format!("index {} is out of bounds", $lit)))?; - if RULE::validate(&item).is_ok() { - Ok(()) - } else { - Err($crate::result::Error::new(format!("the character at index {} does not satisfy the condition", $lit))) + fn validate(target: Self::Item) -> Result> { + if $lit >= target.len() { + return Err($crate::result::Error::new(target, format!("index {} is out of bounds", $lit))); + } + let mut target = target; + match RULE::validate(target.remove($lit)) { + Ok(validated_item) => { + target.insert($lit, validated_item); + Ok(target) + } + Err(err) => { + target.insert($lit, err.into_value()); + Err($crate::result::Error::new(target, format!("the character at index {} does not satisfy the condition", $lit))) + } } } } @@ -77,12 +103,12 @@ macro_rules! define_index_rule { impl <'a, RULE> $crate::rule::Rule for [] where RULE: $crate::rule::Rule { type Item = &'a str; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - let item = target.chars().nth($lit).ok_or_else(|| $crate::result::Error::new(format!("index {} is out of bounds", $lit)))?; - if RULE::validate(&item).is_ok() { - Ok(()) + fn validate(target: Self::Item) -> Result> { + let item = target.chars().nth($lit).ok_or_else(|| $crate::result::Error::new(target, format!("index {} is out of bounds", $lit)))?; + if RULE::validate(item).is_ok() { + Ok(target) } else { - Err($crate::result::Error::new(format!("the character at index {} does not satisfy the condition", $lit))) + Err($crate::result::Error::new(target, format!("the character at index {} does not satisfy the condition", $lit))) } } } diff --git a/src/rule/collection/init.rs b/src/rule/collection/init.rs index 660e328..497d6a6 100644 --- a/src/rule/collection/init.rs +++ b/src/rule/collection/init.rs @@ -34,10 +34,11 @@ pub type InitStringRule = InitRule; #[cfg(test)] mod tests { + use crate::result::Error; use crate::rule::{InitVec, NonEmptyStringRule}; #[test] - fn init_valid() -> anyhow::Result<()> { + fn init_valid() -> Result<(), Error>> { let table = vec![ vec![ "hello".to_string(), diff --git a/src/rule/collection/init/collection.rs b/src/rule/collection/init/collection.rs index c498502..70060fa 100644 --- a/src/rule/collection/init/collection.rs +++ b/src/rule/collection/init/collection.rs @@ -1,8 +1,8 @@ -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, VecDeque}; use crate::result::Error; +use crate::rule::InitRule; use crate::rule::Rule; -use crate::rule::{ForAllRule, InitRule}; macro_rules! impl_init { ($($t:ty),*) => { @@ -13,42 +13,79 @@ macro_rules! impl_init { { type Item = $t; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { let length = target.len(); - let mut result = Ok(()); + let mut remains = target.into_iter(); + let mut result = VecDeque::new(); + let mut failed = false; - for (i, item) in target.iter().enumerate() { - if i == length - 1 { - break; - } - match RULE::validate(item) { - Ok(_) => continue, - Err(e) => { - result = Err(e); - break; + for (i, item) in remains.by_ref().enumerate() { + if i < length - 1 { + match RULE::validate(item) { + Ok(validated_item) => result.push_back(validated_item), + Err(err) => { + result.push_back(err.into_value()); + failed = true; + break; + } } + } else { + result.push_back(item); } } - result + + if failed { + result.append(&mut remains.collect::>()); + let result = result.into_iter().collect::<$t>(); + Err(Error::new( + result, + "Failed to validate all items", + )) + } else { + Ok(result.into_iter().collect::<$t>()) + } } } )* }; } -impl_init![Vec, VecDeque, HashSet]; +impl_init![Vec, VecDeque]; -impl Rule for ForAllRule> +impl Rule for InitRule> where RULE: Rule, + K: Eq + std::hash::Hash, { type Item = HashMap; - fn validate(target: &Self::Item) -> Result<(), Error> { - if target.values().all(|item| RULE::validate(item).is_ok()) { - Ok(()) + fn validate(target: Self::Item) -> Result> { + let length = target.len(); + let mut remains = target.into_iter(); + let mut result = VecDeque::new(); + let mut failed = false; + + for (i, (key, value)) in remains.by_ref().enumerate() { + if i < length - 1 { + match RULE::validate(value) { + Ok(validated_item) => result.push_back((key, validated_item)), + Err(err) => { + result.push_back((key, err.into_value())); + failed = true; + break; + } + } + } else { + result.push_back((key, value)); + } + } + + if failed { + result.append(&mut remains.collect::>()); + let result = result.into_iter().collect::>(); + Err(Error::new(result, "Failed to validate all items")) } else { - Err(Error::new("not all items satisfy the condition")) + Ok(result.into_iter().collect::>()) } } } diff --git a/src/rule/collection/init/string.rs b/src/rule/collection/init/string.rs index 0bbd38e..2cfdc0d 100644 --- a/src/rule/collection/init/string.rs +++ b/src/rule/collection/init/string.rs @@ -7,23 +7,33 @@ where { type Item = String; - fn validate(target: &Self::Item) -> Result<(), Error> { - let length = target.len(); - let mut result = Ok(()); + fn validate(target: Self::Item) -> Result> { + let mut remains = target.chars(); + let mut result = Vec::new(); + let mut failed = false; - for (i, c) in target.chars().enumerate() { - if i == length - 1 { - break; - } - match RULE::validate(&c) { - Ok(_) => continue, - Err(e) => { - result = Err(e); - break; + for (i, item) in remains.by_ref().enumerate() { + if i < target.len() - 1 { + match RULE::validate(item) { + Ok(validated_item) => result.push(validated_item), + Err(err) => { + result.push(err.into_value()); + failed = true; + break; + } } + } else { + result.push(item); } } - result + + if failed { + result.append(&mut remains.collect::>()); + let result = result.into_iter().collect::(); + Err(Error::new(result, "Failed to validate all items")) + } else { + Ok(result.into_iter().collect::()) + } } } @@ -33,18 +43,17 @@ where { type Item = &'a str; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { let length = target.len(); - let mut result = Ok(()); - + let mut result = Ok(target); for (i, c) in target.chars().enumerate() { if i == length - 1 { break; } - match RULE::validate(&c) { + match RULE::validate(c) { Ok(_) => continue, - Err(e) => { - result = Err(e); + Err(_) => { + result = Err(Error::new(target, "Failed to validate all items")); break; } } diff --git a/src/rule/collection/last.rs b/src/rule/collection/last.rs index 5a71cac..f081fb2 100644 --- a/src/rule/collection/last.rs +++ b/src/rule/collection/last.rs @@ -35,10 +35,11 @@ pub type LastStringRule = LastRule; #[cfg(test)] mod tests { + use crate::result::Error; use crate::rule::{LastVec, NonEmptyStringRule}; #[test] - fn last_valid() -> anyhow::Result<()> { + fn last_valid() -> Result<(), Error>> { let table = vec![ vec!["".to_string(), "hello".to_string()], vec!["good morning".to_string(), "hello".to_string()], diff --git a/src/rule/collection/last/collection.rs b/src/rule/collection/last/collection.rs index ae0248f..26ecb8e 100644 --- a/src/rule/collection/last/collection.rs +++ b/src/rule/collection/last/collection.rs @@ -7,16 +7,27 @@ where { type Item = Vec; - fn validate(target: &Self::Item) -> Result<(), crate::result::Error> { - let item = target - .last() - .ok_or_else(|| crate::result::Error::new("the vector is empty"))?; - if RULE::validate(item).is_ok() { - Ok(()) - } else { - Err(crate::result::Error::new( - "the last item does not satisfy the condition", - )) + fn validate(target: Self::Item) -> Result> { + let mut target = target.into_iter().collect::>(); + let last = target.pop_back(); + match last { + Some(item) => match RULE::validate(item) { + Ok(validated_item) => { + target.push_back(validated_item); + Ok(target.into_iter().collect()) + } + Err(e) => { + target.push_back(e.into_value()); + Err(crate::result::Error::new( + target.into_iter().collect(), + "Failed to validate the last item", + )) + } + }, + None => Err(crate::result::Error::new( + target.into_iter().collect(), + "Last item does not exist", + )), } } } @@ -27,16 +38,27 @@ where { type Item = VecDeque; - fn validate(target: &Self::Item) -> Result<(), crate::result::Error> { - let item = target - .back() - .ok_or_else(|| crate::result::Error::new("the deque is empty"))?; - if RULE::validate(item).is_ok() { - Ok(()) - } else { - Err(crate::result::Error::new( - "the last item does not satisfy the condition", - )) + fn validate(target: Self::Item) -> Result> { + let mut target = target; + let last = target.pop_back(); + match last { + Some(item) => match RULE::validate(item) { + Ok(validated_item) => { + target.push_back(validated_item); + Ok(target) + } + Err(err) => { + target.push_back(err.into_value()); + Err(crate::result::Error::new( + target, + "Failed to validate the last item", + )) + } + }, + None => Err(crate::result::Error::new( + target, + "Last item does not exist", + )), } } } diff --git a/src/rule/collection/last/string.rs b/src/rule/collection/last/string.rs index e83d262..393c28d 100644 --- a/src/rule/collection/last/string.rs +++ b/src/rule/collection/last/string.rs @@ -6,17 +6,19 @@ where { type Item = String; - fn validate(target: &Self::Item) -> Result<(), crate::result::Error> { - let item = target - .chars() - .last() - .ok_or_else(|| crate::result::Error::new("the string is empty"))?; - if RULE::validate(&item).is_ok() { - Ok(()) - } else { - Err(crate::result::Error::new( - "the last character does not satisfy the condition", - )) + fn validate(target: Self::Item) -> Result> { + match target.chars().last() { + Some(item) => match RULE::validate(item) { + Ok(_) => Ok(target), + Err(_) => Err(crate::result::Error::new( + target, + "Failed to validate the last item", + )), + }, + None => Err(crate::result::Error::new( + target, + "Last item does not exist", + )), } } } @@ -27,17 +29,19 @@ where { type Item = &'a str; - fn validate(target: &Self::Item) -> Result<(), crate::result::Error> { - let item = target - .chars() - .last() - .ok_or_else(|| crate::result::Error::new("the string is empty"))?; - if RULE::validate(&item).is_ok() { - Ok(()) - } else { - Err(crate::result::Error::new( - "the last character does not satisfy the condition", - )) + fn validate(target: Self::Item) -> Result> { + match target.chars().last() { + Some(item) => match RULE::validate(item) { + Ok(_) => Ok(target), + Err(_) => Err(crate::result::Error::new( + target, + "Failed to validate the last item", + )), + }, + None => Err(crate::result::Error::new( + target, + "Last item does not exist", + )), } } } diff --git a/src/rule/composer/and.rs b/src/rule/composer/and.rs index f774788..9a5ba7f 100644 --- a/src/rule/composer/and.rs +++ b/src/rule/composer/and.rs @@ -1,6 +1,5 @@ use std::marker::PhantomData; -use crate::result::Error; use crate::rule::Rule; /// A macro to generate a `Rule` that combines multiple rules @@ -11,7 +10,7 @@ use crate::rule::Rule; /// /// type NonEmptyAlphabetString = And![EmailRule, NonEmptyStringRule, EmailRule]; /// -/// let actual = NonEmptyAlphabetString::validate(&"sample@example.com".to_string()); +/// let actual = NonEmptyAlphabetString::validate("sample@example.com".to_string()); /// assert!(actual.is_ok()); /// ``` #[macro_export] @@ -32,7 +31,7 @@ macro_rules! And { /// /// type NonEmptyAlphabetString<'a> = And>; /// -/// let actual = NonEmptyAlphabetString::validate(&"Hello".to_string()); +/// let actual = NonEmptyAlphabetString::validate("Hello".to_string()); /// assert!(actual.is_ok()); /// ``` #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] @@ -67,8 +66,8 @@ where { type Item = T; - fn validate(target: &Self::Item) -> Result<(), Error> { - let bounded_rule = |t: &T| RULE1::validate(t).and_then(|_| RULE2::validate(t)); + fn validate(target: Self::Item) -> crate::Result { + let bounded_rule = |t: T| RULE1::validate(t).and_then(RULE2::validate); bounded_rule(target) } } @@ -82,23 +81,23 @@ mod test { #[test] fn test_rule_binder_ok() { - assert!(NonEmptyAlphabetString::validate(&"Hello".to_string()).is_ok()); + assert!(NonEmptyAlphabetString::validate("Hello".to_string()).is_ok()); } #[test] fn test_rule_binder_err() { - assert!(NonEmptyAlphabetString::validate(&"Hello1".to_string()).is_err()); + assert!(NonEmptyAlphabetString::validate("Hello1".to_string()).is_err()); } #[test] fn test_rule_binder_macro_ok() { type SampleRule = And![EmailRule, NonEmptyStringRule, EmailRule]; - assert!(SampleRule::validate(&"sample@example.com".to_string()).is_ok()); + assert!(SampleRule::validate("sample@example.com".to_string()).is_ok()); } #[test] fn test_rule_binder_macro_err() { type SampleRule = And![AlphabetRule, NonEmptyStringRule, EmailRule]; - assert!(SampleRule::validate(&"Hello".to_string()).is_err()); + assert!(SampleRule::validate("Hello".to_string()).is_err()); } } diff --git a/src/rule/composer/not.rs b/src/rule/composer/not.rs index 0fd85bf..8515a4f 100644 --- a/src/rule/composer/not.rs +++ b/src/rule/composer/not.rs @@ -10,8 +10,8 @@ use std::marker::PhantomData; /// /// type NonEmptyString = Not>; /// -/// assert!(NonEmptyString::validate(&"non empty".to_string()).is_ok()); -/// assert!(NonEmptyString::validate(&"".to_string()).is_err()); +/// assert!(NonEmptyString::validate("non empty".to_string()).is_ok()); +/// assert!(NonEmptyString::validate("".to_string()).is_err()); /// ``` #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] pub struct Not { @@ -24,10 +24,10 @@ where { type Item = T; - fn validate(target: &Self::Item) -> Result<(), Error> { - let bounded_rule = |t: &T| match RULE::validate(t) { - Ok(_) => Err(Error::new("Target satisfies the `Not` rule")), - Err(_) => Ok(()), + 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")), + Err(err) => Ok(err.into_value()), }; bounded_rule(target) } @@ -41,7 +41,7 @@ mod test { #[test] fn test_not() { type NonNonEmptyString = Not; - assert!(NonNonEmptyString::validate(&"".to_string()).is_ok()); - assert!(NonNonEmptyString::validate(&"Hello".to_string()).is_err()) + assert!(NonNonEmptyString::validate("".to_string()).is_ok()); + assert!(NonNonEmptyString::validate("Hello".to_string()).is_err()) } } diff --git a/src/rule/composer/or.rs b/src/rule/composer/or.rs index e16d80a..f1ce072 100644 --- a/src/rule/composer/or.rs +++ b/src/rule/composer/or.rs @@ -1,6 +1,5 @@ use std::marker::PhantomData; -use crate::result::Error; use crate::rule::Rule; /// A macro to generate a `Rule` that combines multiple rules @@ -11,7 +10,7 @@ use crate::rule::Rule; /// /// type NewRule = Or![EmailRule, NonEmptyStringRule, EmailRule]; /// -/// let actual = NewRule::validate(&"sample@example.com".to_string()); +/// let actual = NewRule::validate("sample@example.com".to_string()); /// assert!(actual.is_ok()); #[macro_export] macro_rules! Or { @@ -31,9 +30,9 @@ macro_rules! Or { /// /// type EmptyOrAlphabetString = Or, AlphabetRule>; /// -/// assert!(EmptyOrAlphabetString::validate(&"".to_string()).is_ok()); -/// assert!(EmptyOrAlphabetString::validate(&"alphabet".to_string()).is_ok()); -/// assert!(EmptyOrAlphabetString::validate(&"1".to_string()).is_err()); +/// assert!(EmptyOrAlphabetString::validate("".to_string()).is_ok()); +/// assert!(EmptyOrAlphabetString::validate("alphabet".to_string()).is_ok()); +/// assert!(EmptyOrAlphabetString::validate("1".to_string()).is_err()); /// ``` #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] pub struct Or { @@ -48,8 +47,11 @@ where { type Item = T; - fn validate(target: &Self::Item) -> Result<(), Error> { - let bounded_rule = |t: &T| RULE1::validate(t).or_else(|_| RULE2::validate(t)); + 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()), + }; bounded_rule(target) } } @@ -62,20 +64,20 @@ mod test { #[test] fn test_or() { type NonEmptyOrAlphabetString = Or>; - assert!(NonEmptyOrAlphabetString::validate(&"hello".to_string()).is_ok()); - assert!(NonEmptyOrAlphabetString::validate(&"12345".to_string()).is_ok()); - assert!(NonEmptyOrAlphabetString::validate(&"".to_string()).is_ok()); + assert!(NonEmptyOrAlphabetString::validate("hello".to_string()).is_ok()); + assert!(NonEmptyOrAlphabetString::validate("12345".to_string()).is_ok()); + assert!(NonEmptyOrAlphabetString::validate("".to_string()).is_ok()); } #[test] fn test_rule_binder_macro_ok() { type SampleRule = Or![EmailRule, NonEmptyStringRule, EmailRule]; - assert!(SampleRule::validate(&"hoge".to_string()).is_ok()); + assert!(SampleRule::validate("hoge".to_string()).is_ok()); } #[test] fn test_rule_binder_macro_err() { type SampleRule = Or![EmailRule, NonEmptyStringRule, EmailRule]; - assert!(SampleRule::validate(&"".to_string()).is_err()); + assert!(SampleRule::validate("".to_string()).is_err()); } } diff --git a/src/rule/empty.rs b/src/rule/empty.rs index 468a3cb..257d8c0 100644 --- a/src/rule/empty.rs +++ b/src/rule/empty.rs @@ -42,14 +42,14 @@ where /// ```rust /// use refined_type::rule::{EmptyRule, Rule}; /// -/// assert!(EmptyRule::::validate(&"".to_string()).is_ok()); -/// assert!(EmptyRule::::validate(&"non empty".to_string()).is_err()); +/// assert!(EmptyRule::::validate("".to_string()).is_ok()); +/// assert!(EmptyRule::::validate("non empty".to_string()).is_err()); /// -/// assert!(EmptyRule::>::validate(&Vec::::new()).is_ok()); -/// assert!(EmptyRule::>::validate(&vec![1, 2, 3]).is_err()); +/// assert!(EmptyRule::>::validate(Vec::::new()).is_ok()); +/// assert!(EmptyRule::>::validate(vec![1, 2, 3]).is_err()); /// -/// assert!(EmptyRule::::validate(&0).is_ok()); -/// assert!(EmptyRule::::validate(&1).is_err()); +/// assert!(EmptyRule::::validate(0).is_ok()); +/// assert!(EmptyRule::::validate(1).is_err()); /// ``` #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] pub struct EmptyRule { @@ -62,21 +62,22 @@ where { type Item = T; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> crate::Result { if target.empty() { - Ok(()) + Ok(target) } else { - Err(Error::new("The input value is not empty")) + Err(Error::new(target, "The input value is not empty")) } } } #[cfg(test)] mod test { + use crate::result::Error; use crate::rule::Empty; #[test] - fn test_add_empty() -> anyhow::Result<()> { + fn test_add_empty() -> Result<(), Error> { let empty_1 = Empty::new(0)?; let empty_2 = Empty::new(0)?; let empty = empty_1 + empty_2; diff --git a/src/rule/for_all.rs b/src/rule/for_all.rs new file mode 100644 index 0000000..99a8086 --- /dev/null +++ b/src/rule/for_all.rs @@ -0,0 +1,107 @@ +mod collection; +mod string; + +use std::collections::VecDeque; +use std::marker::PhantomData; + +use crate::result::Error; +use crate::rule::Rule; +use crate::Refined; + +/// A type that holds a value satisfying the `ForAllRule` +pub type ForAll<'a, RULE, ITERABLE> = Refined>; + +pub trait Iterable<'a> { + type Item: 'a; + fn into_iterator(self) -> Box + 'a>; +} + +/// Rule where all the data in the collection satisfies the condition +pub struct ForAllRule +where + RULE: Rule, +{ + _phantom_data: PhantomData<(RULE, ITERABLE)>, +} + +impl<'a, RULE, ITERABLE, ITEM> Rule for ForAllRule +where + RULE: Rule, + ITERABLE: Iterable<'a, Item = ITEM> + FromIterator, +{ + type Item = ITERABLE; + + fn validate(target: Self::Item) -> crate::Result { + let mut remains = target.into_iterator(); + let mut result = VecDeque::new(); + let mut failed = false; + + for item in remains.by_ref() { + match RULE::validate(item) { + Ok(validated_item) => result.push_back(validated_item), + Err(err) => { + result.push_back(err.into_value()); + failed = true; + break; + } + } + } + + if failed { + result.append(&mut remains.collect::>()); + let result = result.into_iter().collect::>(); + Err(Error::new( + ITERABLE::from_iter(result), + "Failed to validate all items", + )) + } else { + let result = result.into_iter().collect::>(); + Ok(ITERABLE::from_iter(result)) + } + } +} + +#[cfg(test)] +mod tests { + use crate::result::Error; + use crate::rule::for_all::ForAll; + use crate::rule::{NonEmptyStringRule, Rule}; + + #[test] + fn for_all_1() -> Result<(), Error>> { + let value = vec!["good morning".to_string(), "hello".to_string()]; + let for_all: ForAll = ForAll::new(value.clone())?; + assert_eq!(for_all.into_value(), value); + Ok(()) + } + + #[test] + fn for_all_2() -> Result<(), Error>> { + let value = vec!["good morning".to_string(), "".to_string()]; + let for_all_result = ForAll::>::new(value.clone()); + assert!(for_all_result.is_err()); + Ok(()) + } + + #[test] + fn for_all_3() -> Result<(), Error> { + struct CharRule; + impl Rule for CharRule { + type Item = char; + + fn validate(target: Self::Item) -> Result> { + if target.is_alphabetic() { + Ok(target) + } else { + let message = format!("{} is not an alphabet", target); + Err(Error::new(target, message)) + } + } + } + + let value = "hello".to_string(); + let for_all: ForAll = ForAll::new(value.clone())?; + assert_eq!(for_all.into_value(), value); + Ok(()) + } +} diff --git a/src/rule/length/equal.rs b/src/rule/length/equal.rs index a2cd741..6e1220d 100644 --- a/src/rule/length/equal.rs +++ b/src/rule/length/equal.rs @@ -29,11 +29,11 @@ macro_rules! length_equal { impl $crate::rule::Rule for [] where ITEM: $crate::rule::LengthDefinition { type Item = ITEM; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { + fn validate(target: Self::Item) -> Result> { if target.length() == $length { - Ok(()) + Ok(target) } else { - Err($crate::result::Error::new(format!("target length is not equal to {}", $length))) + Err($crate::result::Error::new(target, format!("target length is not equal to {}", $length))) } } } @@ -52,7 +52,7 @@ mod tests { length_equal!(5, 10); #[test] - fn test_length_equal_5() -> Result<(), Error> { + fn test_length_equal_5() -> Result<(), Error<&'static str>> { let target = "12345"; let refined = LengthEqual5::new(target)?; assert_eq!(refined.into_value(), "12345"); @@ -67,7 +67,7 @@ mod tests { } #[test] - fn test_length_equal_10() -> Result<(), Error> { + fn test_length_equal_10() -> Result<(), Error<&'static str>> { let target = "1234567890"; let refined = LengthEqual10::new(target)?; assert_eq!(refined.into_value(), "1234567890"); diff --git a/src/rule/length/grater.rs b/src/rule/length/grater.rs index fd1e1aa..4af120a 100644 --- a/src/rule/length/grater.rs +++ b/src/rule/length/grater.rs @@ -28,11 +28,11 @@ macro_rules! length_greater_than { impl $crate::rule::Rule for [] where ITEM: $crate::rule::LengthDefinition { type Item = ITEM; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { + fn validate(target: Self::Item) -> Result> { if target.length() > $length { - Ok(()) + Ok(target) } else { - Err($crate::result::Error::new(format!("target length is not greater than {}", $length))) + Err($crate::result::Error::new(target, format!("target length is not greater than {}", $length))) } } } @@ -51,7 +51,7 @@ mod tests { length_greater_than!(5, 10); #[test] - fn test_length_greater_than_5() -> Result<(), Error> { + fn test_length_greater_than_5() -> Result<(), Error<&'static str>> { let target = "123456"; let refined = LengthGreaterThan5::new(target)?; assert_eq!(refined.into_value(), "123456"); @@ -66,7 +66,7 @@ mod tests { } #[test] - fn test_length_greater_than_10() -> Result<(), Error> { + fn test_length_greater_than_10() -> Result<(), Error<&'static str>> { let target = "12345678901"; let refined = LengthGreaterThan10::new(target)?; assert_eq!(refined.into_value(), "12345678901"); diff --git a/src/rule/length/less.rs b/src/rule/length/less.rs index 3815fb6..133d3e4 100644 --- a/src/rule/length/less.rs +++ b/src/rule/length/less.rs @@ -29,11 +29,11 @@ macro_rules! length_less_than { impl $crate::rule::Rule for [] where ITEM: $crate::rule::LengthDefinition { type Item = ITEM; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { + fn validate(target: Self::Item) -> Result> { if target.length() < $length { - Ok(()) + Ok(target) } else { - Err($crate::result::Error::new(format!("target length is not less than {}", $length))) + Err($crate::result::Error::new(target, format!("target length is not less than {}", $length))) } } } @@ -52,7 +52,7 @@ mod tests { length_less_than!(5, 10); #[test] - fn test_length_less_than_5() -> Result<(), Error> { + fn test_length_less_than_5() -> Result<(), Error<&'static str>> { let target = "1234"; let refined = LengthLessThan5::new(target)?; assert_eq!(refined.into_value(), "1234"); @@ -67,7 +67,7 @@ mod tests { } #[test] - fn test_length_less_than_10() -> Result<(), Error> { + fn test_length_less_than_10() -> Result<(), Error<&'static str>> { let target = "123456789"; let refined = LengthLessThan10::new(target)?; assert_eq!(refined.into_value(), "123456789"); diff --git a/src/rule/non_empty.rs b/src/rule/non_empty.rs index 26b85b9..f647923 100644 --- a/src/rule/non_empty.rs +++ b/src/rule/non_empty.rs @@ -7,13 +7,13 @@ mod non_empty_vec_deque; use crate::rule::composer::Not; use crate::rule::{EmptyDefinition, EmptyRule}; use crate::Refined; -use std::iter::Map; - pub use non_empty_map::*; pub use non_empty_set::*; pub use non_empty_string::*; pub use non_empty_vec::*; pub use non_empty_vec_deque::*; +use std::fmt::Debug; +use std::iter::Map; /// A type that holds a value satisfying the `NonEmptyRule` /// The definition of empty is defined by `EmptyDefinition`. @@ -22,18 +22,18 @@ pub type NonEmpty = Refined>; /// Rule where the input value is not empty /// ```rust /// use refined_type::rule::{NonEmptyRule, Rule}; -/// assert!(NonEmptyRule::::validate(&"non empty".to_string()).is_ok()); -/// assert!(NonEmptyRule::::validate(&"".to_string()).is_err()); +/// assert!(NonEmptyRule::::validate("non empty".to_string()).is_ok()); +/// assert!(NonEmptyRule::::validate("".to_string()).is_err()); /// -/// assert!(NonEmptyRule::>::validate(&vec![1, 2, 3]).is_ok()); -/// assert!(NonEmptyRule::>::validate(&Vec::new()).is_err()); +/// assert!(NonEmptyRule::>::validate(vec![1, 2, 3]).is_ok()); +/// assert!(NonEmptyRule::>::validate(Vec::new()).is_err()); /// -/// assert!(NonEmptyRule::::validate(&1).is_ok()); -/// assert!(NonEmptyRule::::validate(&0).is_err()); +/// assert!(NonEmptyRule::::validate(1).is_ok()); +/// assert!(NonEmptyRule::::validate(0).is_err()); /// ``` pub type NonEmptyRule = Not>; -impl NonEmpty { +impl NonEmpty { pub fn map(self, f: F) -> Refined>> where Self: Sized, @@ -44,7 +44,7 @@ impl NonEmpty { .expect("This error is always unreachable") } - pub fn collect + EmptyDefinition>(self) -> NonEmpty + pub fn collect + EmptyDefinition>(self) -> NonEmpty where Self: Sized, { diff --git a/src/rule/non_empty/non_empty_map.rs b/src/rule/non_empty/non_empty_map.rs index 074d5ad..aaf9051 100644 --- a/src/rule/non_empty/non_empty_map.rs +++ b/src/rule/non_empty/non_empty_map.rs @@ -32,12 +32,12 @@ pub type NonEmptyHashMapRule = NonEmptyRule NonEmptyHashMap { #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> NonEmpty> { - Refined::new(self.into_value().into_iter()).expect("This error is always unreachable") + Refined::new_unchecked(self.into_value().into_iter()) } #[allow(clippy::should_implement_trait)] pub fn iter(&self) -> NonEmpty> { - Refined::new(self.value().iter()).expect("This error is always unreachable") + Refined::new_unchecked(self.value().iter()) } pub fn hasher(&self) -> &S { @@ -89,17 +89,18 @@ where pub fn insert(self, k: K, v: V) -> Self { let mut result = self.into_value(); result.insert(k, v); - Refined::new(result).expect("This error is always unreachable") + Refined::new_unchecked(result) } } #[cfg(test)] mod test { + use crate::result::Error; use crate::rule::{NonEmptyHashMap, NonEmptyVec}; use std::collections::{HashMap, HashSet}; #[test] - fn test_map_len() -> anyhow::Result<()> { + fn test_map_len() -> Result<(), Error>> { let mut map = HashMap::new(); map.insert("1", 1); let map = NonEmptyHashMap::new(map)?; @@ -108,7 +109,7 @@ mod test { } #[test] - fn test_map_is_empty() -> anyhow::Result<()> { + fn test_map_is_empty() -> Result<(), Error>> { let mut map = HashMap::new(); map.insert("1", 1); let map = NonEmptyHashMap::new(map)?; @@ -117,7 +118,7 @@ mod test { } #[test] - fn test_map_keys() -> anyhow::Result<()> { + fn test_map_keys() -> Result<(), Error>> { let mut map = HashMap::new(); map.insert("1", 1); map.insert("2", 2); @@ -130,7 +131,7 @@ mod test { } #[test] - fn test_map_into_keys() -> anyhow::Result<()> { + fn test_map_into_keys() -> Result<(), Error>> { let mut map = HashMap::new(); map.insert("1", 1); map.insert("2", 2); @@ -143,7 +144,7 @@ mod test { } #[test] - fn test_map_values() -> anyhow::Result<()> { + fn test_map_values() -> Result<(), Error>> { let mut map = HashMap::new(); map.insert("1", 1); map.insert("2", 2); @@ -156,7 +157,7 @@ mod test { } #[test] - fn test_map_into_values() -> anyhow::Result<()> { + fn test_map_into_values() -> Result<(), Error>> { let mut map = HashMap::new(); map.insert("1", 1); map.insert("2", 2); @@ -169,7 +170,7 @@ mod test { } #[test] - fn test_map_get() -> anyhow::Result<()> { + fn test_map_get() -> Result<(), Error>> { let mut map = HashMap::new(); map.insert("1", 1); map.insert("2", 2); @@ -179,7 +180,7 @@ mod test { } #[test] - fn test_map_insert() -> anyhow::Result<()> { + fn test_map_insert() -> Result<(), Error>> { let mut map = HashMap::new(); map.insert("1", 1); map.insert("2", 2); diff --git a/src/rule/non_empty/non_empty_set.rs b/src/rule/non_empty/non_empty_set.rs index 2ab30d4..3351fda 100644 --- a/src/rule/non_empty/non_empty_set.rs +++ b/src/rule/non_empty/non_empty_set.rs @@ -29,12 +29,12 @@ pub type NonEmptyHashSetRule = NonEmptyRule>; impl NonEmptyHashSet { #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> NonEmpty> { - Refined::new(self.into_value().into_iter()).expect("This error is always unreachable") + Refined::new_unchecked(self.into_value().into_iter()) } #[allow(clippy::should_implement_trait)] pub fn iter(&self) -> NonEmpty> { - Refined::new(self.value().iter()).expect("This error is always unreachable") + Refined::new_unchecked(self.value().iter()) } pub fn len(&self) -> usize { @@ -62,7 +62,7 @@ where pub fn insert(self, value: T) -> Self { let mut result = self.into_value(); result.insert(value); - Refined::new(result).expect("This error is always unreachable") + Refined::new_unchecked(result) } pub fn get(&self, value: &Q) -> Option<&T> @@ -88,6 +88,7 @@ where #[cfg(test)] mod test { + use crate::result::Error; use crate::rule::NonEmptyHashSet; use std::collections::HashSet; @@ -99,7 +100,7 @@ mod test { } #[test] - fn test_len() -> anyhow::Result<()> { + fn test_len() -> Result<(), Error>> { let mut set = HashSet::new(); set.insert(1); let set = NonEmptyHashSet::new(set)?; @@ -108,7 +109,7 @@ mod test { } #[test] - fn test_is_empty() -> anyhow::Result<()> { + fn test_is_empty() -> Result<(), Error>> { let mut set = HashSet::new(); set.insert(1); let set = NonEmptyHashSet::new(set)?; @@ -117,7 +118,7 @@ mod test { } #[test] - fn test_insert() -> anyhow::Result<()> { + fn test_insert() -> Result<(), Error>> { let mut set_origin = HashSet::new(); set_origin.insert(1); @@ -129,7 +130,7 @@ mod test { } #[test] - fn test_is_get() -> anyhow::Result<()> { + fn test_is_get() -> Result<(), Error>> { let mut set = HashSet::new(); set.insert(1); let set = NonEmptyHashSet::new(set)?; @@ -138,7 +139,7 @@ mod test { } #[test] - fn test_is_contains() -> anyhow::Result<()> { + fn test_is_contains() -> Result<(), Error>> { let mut set_origin = HashSet::new(); set_origin.insert(1); let set = NonEmptyHashSet::new(set_origin.clone())?.insert(2); @@ -147,7 +148,7 @@ mod test { } #[test] - fn test_is_difference() -> anyhow::Result<()> { + fn test_is_difference() -> Result<(), Error>> { let mut set_origin = HashSet::new(); set_origin.insert(1); let set = NonEmptyHashSet::new(set_origin.clone())?.insert(2); diff --git a/src/rule/non_empty/non_empty_string.rs b/src/rule/non_empty/non_empty_string.rs index 30c527e..3469f73 100644 --- a/src/rule/non_empty/non_empty_string.rs +++ b/src/rule/non_empty/non_empty_string.rs @@ -74,7 +74,7 @@ impl NonEmptyString { } impl FromStr for NonEmptyString { - type Err = Error; + type Err = Error; fn from_str(s: &str) -> Result { Refined::new(s.to_string()) } @@ -91,17 +91,18 @@ impl Add for NonEmptyString { #[cfg(test)] mod test { + use crate::result::Error; use crate::rule::{NonEmptyString, NonEmptyStringRule, Rule}; use std::str::FromStr; #[test] fn test_non_empty_string() { - assert!(NonEmptyStringRule::validate(&"hello".to_string()).is_ok()); - assert!(NonEmptyStringRule::validate(&"".to_string()).is_err()); + assert!(NonEmptyStringRule::validate("hello".to_string()).is_ok()); + assert!(NonEmptyStringRule::validate("".to_string()).is_err()); } #[test] - fn test_add_string() -> anyhow::Result<()> { + fn test_add_string() -> Result<(), Error> { let non_empty_string_1 = NonEmptyString::new("Hello".to_string())?; let non_empty_string_2 = NonEmptyString::new("World".to_string())?; let non_empty_string = non_empty_string_1 + non_empty_string_2; @@ -111,7 +112,7 @@ mod test { } #[test] - fn test_from_str() -> anyhow::Result<()> { + fn test_from_str() -> Result<(), Error> { let non_empty_string = NonEmptyString::from_str("Hello")?; assert_eq!(non_empty_string.into_value(), "Hello"); Ok(()) diff --git a/src/rule/non_empty/non_empty_vec.rs b/src/rule/non_empty/non_empty_vec.rs index ed8e276..1156152 100644 --- a/src/rule/non_empty/non_empty_vec.rs +++ b/src/rule/non_empty/non_empty_vec.rs @@ -20,12 +20,12 @@ pub type NonEmptyVecRule = NonEmptyRule>; impl NonEmptyVec { #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> NonEmpty> { - Refined::new(self.into_value().into_iter()).expect("This error is always unreachable") + Refined::new_unchecked(self.into_value().into_iter()) } #[allow(clippy::should_implement_trait)] pub fn iter(&self) -> NonEmpty> { - Refined::new(self.value().iter()).expect("This error is always unreachable") + Refined::new_unchecked(self.value().iter()) } pub fn get(&self, index: usize) -> Option<&T> { @@ -43,7 +43,7 @@ impl NonEmptyVec { pub fn push(self, value: T) -> Self { let mut result = self.into_value(); result.push(value); - Refined::new(result).expect("This error is always unreachable") + Refined::new_unchecked(result) } } @@ -53,24 +53,25 @@ impl Add for NonEmptyVec { fn add(self, rhs: Self) -> Self::Output { let mut result = self.into_value(); result.append(&mut rhs.into_value()); - Refined::new(result).expect("This error is always unreachable") + Refined::new_unchecked(result) } } #[cfg(test)] mod test { + use crate::result::Error; use crate::rule::non_empty::non_empty_vec_deque::NonEmptyVecDeque; use crate::rule::non_empty::NonEmptyVecRule; use crate::rule::{NonEmptyVec, Rule}; #[test] fn test_non_empty_vec() { - assert!(NonEmptyVecRule::validate(&vec![1, 2, 3]).is_ok()); - assert!(NonEmptyVecRule::::validate(&vec![]).is_err()); + assert!(NonEmptyVecRule::validate(vec![1, 2, 3]).is_ok()); + assert!(NonEmptyVecRule::::validate(vec![]).is_err()); } #[test] - fn test_add_vec() -> anyhow::Result<()> { + fn test_add_vec() -> Result<(), Error>> { let ne_vec_1 = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec_2 = NonEmptyVec::new(vec![4, 5, 6])?; let ne_vec = ne_vec_1 + ne_vec_2; @@ -79,7 +80,7 @@ mod test { } #[test] - fn test_into_iter() -> anyhow::Result<()> { + fn test_into_iter() -> Result<(), Error>> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.into_iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -87,7 +88,7 @@ mod test { } #[test] - fn test_iter() -> anyhow::Result<()> { + fn test_iter() -> Result<(), Error>> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -95,7 +96,7 @@ mod test { } #[test] - fn test_collect_to_deque() -> anyhow::Result<()> { + fn test_collect_to_deque() -> Result<(), Error>> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVecDeque = ne_vec.into_iter().collect(); assert_eq!(ne_vec.into_value(), vec![1, 2, 3]); @@ -103,28 +104,28 @@ mod test { } #[test] - fn test_push() -> anyhow::Result<()> { + fn test_push() -> Result<(), Error>> { let vec = NonEmptyVec::new(vec![1])?.push(2).push(3); assert_eq!(vec.into_value(), vec![1, 2, 3]); Ok(()) } #[test] - fn test_get() -> anyhow::Result<()> { + fn test_get() -> Result<(), Error>> { let vec = NonEmptyVec::new(vec![1])?; assert_eq!(vec.get(0), Some(&1)); Ok(()) } #[test] - fn test_len() -> anyhow::Result<()> { + fn test_len() -> Result<(), Error>> { let vec = NonEmptyVec::new(vec![1])?; assert_eq!(vec.len(), 1); Ok(()) } #[test] - fn test_is_empty() -> anyhow::Result<()> { + fn test_is_empty() -> Result<(), Error>> { let vec = NonEmptyVec::new(vec![1])?; assert!(!vec.is_empty()); Ok(()) diff --git a/src/rule/non_empty/non_empty_vec_deque.rs b/src/rule/non_empty/non_empty_vec_deque.rs index a0cf184..ea1cb4e 100644 --- a/src/rule/non_empty/non_empty_vec_deque.rs +++ b/src/rule/non_empty/non_empty_vec_deque.rs @@ -24,12 +24,12 @@ pub type NonEmptyVecDequeRule = NonEmptyRule>; impl NonEmptyVecDeque { #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> NonEmpty> { - Refined::new(self.into_value().into_iter()).expect("This error is always unreachable") + Refined::new_unchecked(self.into_value().into_iter()) } #[allow(clippy::should_implement_trait)] pub fn iter(&self) -> NonEmpty> { - Refined::new(self.value().iter()).expect("This error is always unreachable") + Refined::new_unchecked(self.value().iter()) } pub fn get(&self, index: usize) -> Option<&T> { @@ -47,13 +47,13 @@ impl NonEmptyVecDeque { pub fn push_front(self, value: T) -> Self { let mut result = self.into_value(); result.push_front(value); - Refined::new(result).expect("This error is always unreachable") + Refined::new_unchecked(result) } pub fn push_back(self, value: T) -> Self { let mut result = self.into_value(); result.push_back(value); - Refined::new(result).expect("This error is always unreachable") + Refined::new_unchecked(result) } } @@ -63,18 +63,19 @@ impl Add for NonEmptyVecDeque { fn add(self, rhs: Self) -> Self::Output { let mut result = self.into_value(); result.append(&mut rhs.into_value()); - Refined::new(result).expect("This error is always unreachable") + Refined::new_unchecked(result) } } #[cfg(test)] mod test { + use crate::result::Error; use crate::rule::non_empty::non_empty_vec_deque::NonEmptyVecDeque; use crate::rule::NonEmptyVec; use std::collections::VecDeque; #[test] - fn test_collect_to_vec() -> anyhow::Result<()> { + fn test_collect_to_vec() -> Result<(), Error>> { let mut deque = VecDeque::new(); deque.push_front(1); let deque = NonEmptyVecDeque::new(deque)?.push_front(2).push_back(3); @@ -84,7 +85,7 @@ mod test { } #[test] - fn test_vec_deque_push() -> anyhow::Result<()> { + fn test_vec_deque_push() -> Result<(), Error>> { let mut deque = VecDeque::new(); deque.push_front(1); let deque = NonEmptyVecDeque::new(deque)?.push_front(2).push_back(3); @@ -93,7 +94,7 @@ mod test { } #[test] - fn test_get() -> anyhow::Result<()> { + fn test_get() -> Result<(), Error>> { let mut deque = VecDeque::new(); deque.push_front(1); let deque = NonEmptyVecDeque::new(deque)?; @@ -102,7 +103,7 @@ mod test { } #[test] - fn test_len() -> anyhow::Result<()> { + fn test_len() -> Result<(), Error>> { let mut deque = VecDeque::new(); deque.push_front(1); let deque = NonEmptyVecDeque::new(deque)?; @@ -111,7 +112,7 @@ mod test { } #[test] - fn test_is_empty() -> anyhow::Result<()> { + fn test_is_empty() -> Result<(), Error>> { let mut deque = VecDeque::new(); deque.push_front(1); let deque = NonEmptyVecDeque::new(deque)?; diff --git a/src/rule/number/equal.rs b/src/rule/number/equal.rs index 66587f2..1c31902 100644 --- a/src/rule/number/equal.rs +++ b/src/rule/number/equal.rs @@ -15,11 +15,11 @@ macro_rules! equal_rule { impl $crate::rule::Rule for [] { type Item = $t; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - if *target == $e { - Ok(()) + fn validate(target: Self::Item) -> Result> { + if target == $e { + Ok(target) } else { - Err($crate::result::Error::new(format!("{} does not equal {}", target, $e))) + Err($crate::result::Error::new(target, format!("{} does not equal {}", target, $e))) } } } diff --git a/src/rule/number/even.rs b/src/rule/number/even.rs index cf76d11..cbaabab 100644 --- a/src/rule/number/even.rs +++ b/src/rule/number/even.rs @@ -13,11 +13,11 @@ macro_rules! even_rule { impl $crate::rule::Rule for [] { type Item = $t; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - if *target % 2 == 0 { - Ok(()) + fn validate(target: Self::Item) -> Result> { + if target % 2 == 0 { + Ok(target) } else { - Err($crate::result::Error::new(format!("{} is not even number", target))) + Err($crate::result::Error::new(target, format!("{} is not even number", target))) } } } diff --git a/src/rule/number/greater.rs b/src/rule/number/greater.rs index 67cf3e9..59375e2 100644 --- a/src/rule/number/greater.rs +++ b/src/rule/number/greater.rs @@ -15,11 +15,11 @@ macro_rules! greater_rule { impl $crate::rule::Rule for [] { type Item = $t; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - if *target > $e { - Ok(()) + fn validate(target: Self::Item) -> Result> { + if target > $e { + Ok(target) } else { - Err($crate::result::Error::new(format!("{} is not greater than {}", target, $e))) + Err($crate::result::Error::new(target, format!("{} is not greater than {}", target, $e))) } } } diff --git a/src/rule/number/less.rs b/src/rule/number/less.rs index b12b293..fea2aec 100644 --- a/src/rule/number/less.rs +++ b/src/rule/number/less.rs @@ -15,11 +15,11 @@ macro_rules! less_rule { impl $crate::rule::Rule for [] { type Item = $t; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - if *target < $e { - Ok(()) + fn validate(target: Self::Item) -> Result> { + if target < $e { + Ok(target) } else { - Err($crate::result::Error::new(format!("{} is not less than {}", target, $e))) + Err($crate::result::Error::new(target, format!("{} is not less than {}", target, $e))) } } } diff --git a/src/rule/number/odd.rs b/src/rule/number/odd.rs index 1e4a079..7f14bac 100644 --- a/src/rule/number/odd.rs +++ b/src/rule/number/odd.rs @@ -11,11 +11,11 @@ macro_rules! odd_rule { impl $crate::rule::Rule for [] { type Item = $t; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - if *target % 2 == 1 { - Ok(()) + fn validate(target: Self::Item) -> Result> { + if target % 2 == 1 { + Ok(target) } else { - Err($crate::result::Error::new(format!("{} is not odd number", target))) + Err($crate::result::Error::new(target, format!("{} is not odd number", target))) } } } diff --git a/src/rule/string/ipv4.rs b/src/rule/string/ipv4.rs index 7a128b7..f222317 100644 --- a/src/rule/string/ipv4.rs +++ b/src/rule/string/ipv4.rs @@ -14,15 +14,13 @@ pub struct Ipv4AddrRule { impl> Rule for Ipv4AddrRule { type Item = T; - fn validate(target: &Self::Item) -> Result<(), Error> { - let target = target.as_ref(); - if std::net::Ipv4Addr::from_str(target).is_ok() { - Ok(()) + fn validate(target: Self::Item) -> crate::Result { + let target_as_ref = target.as_ref(); + if std::net::Ipv4Addr::from_str(target_as_ref).is_ok() { + Ok(target) } else { - Err(Error::new(format!( - "{} is not a valid IPv4 address", - target - ))) + let message = format!("{} is not a valid IPv4 address", target_as_ref); + Err(Error::new(target, message)) } } } @@ -37,15 +35,18 @@ pub struct PublicIpv4AddrRule { impl> Rule for PublicIpv4AddrRule { type Item = T; - fn validate(target: &Self::Item) -> Result<(), Error> { - let target = target.as_ref(); - if std::net::Ipv4Addr::from_str(target) - .map_err(|e| Error::new(e.to_string()))? - .is_private() - { - Err(Error::new(format!("{} is a private IP address", target))) + fn validate(target: Self::Item) -> crate::Result { + let target_as_ref = target.as_ref(); + let ipv4_result = std::net::Ipv4Addr::from_str(target_as_ref); + if let Ok(ipv4) = ipv4_result { + if !ipv4.is_private() { + Ok(target) + } else { + let message = format!("{} is a private IP address", target_as_ref); + Err(Error::new(target, message)) + } } else { - Ok(()) + Ok(target) } } } @@ -60,15 +61,18 @@ pub struct PrivateIpv4AddrRule { impl> Rule for PrivateIpv4AddrRule { type Item = T; - fn validate(target: &Self::Item) -> Result<(), Error> { - let target = target.as_ref(); - if std::net::Ipv4Addr::from_str(target) - .map_err(|e| Error::new(e.to_string()))? - .is_private() - { - Ok(()) + fn validate(target: Self::Item) -> crate::Result { + let target_as_ref = target.as_ref(); + if let Ok(ipv4) = std::net::Ipv4Addr::from_str(target_as_ref) { + if ipv4.is_private() { + Ok(target) + } else { + let message = format!("{} is a public IP address", target_as_ref); + Err(Error::new(target, message)) + } } else { - Err(Error::new(format!("{} is a public IP address", target))) + let message = format!("{} is not a valid IPv4 address", target_as_ref); + Err(Error::new(target, message)) } } } diff --git a/src/rule/string/ipv6.rs b/src/rule/string/ipv6.rs index 88d1788..c9d6536 100644 --- a/src/rule/string/ipv6.rs +++ b/src/rule/string/ipv6.rs @@ -14,15 +14,13 @@ pub struct Ipv6AddrRule { impl> Rule for Ipv6AddrRule { type Item = T; - fn validate(target: &Self::Item) -> Result<(), Error> { - let target = target.as_ref(); - if std::net::Ipv6Addr::from_str(target).is_ok() { - Ok(()) + fn validate(target: Self::Item) -> crate::Result { + let target_as_ref = target.as_ref(); + if std::net::Ipv6Addr::from_str(target_as_ref).is_ok() { + Ok(target) } else { - Err(Error::new(format!( - "{} is not a valid IPv6 address", - target - ))) + let message = format!("{} is not a valid IPv6 address", target_as_ref); + Err(Error::new(target, message)) } } } diff --git a/src/rule/string/regex.rs b/src/rule/string/regex.rs index 77a4957..30c6b25 100644 --- a/src/rule/string/regex.rs +++ b/src/rule/string/regex.rs @@ -30,13 +30,14 @@ macro_rules! declare_regex_rule { impl> $crate::rule::Rule for $rule { type Item = STRING; - fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> { - let target = target.as_ref(); + fn validate(target: Self::Item) -> Result> { + let target_as_ref = target.as_ref(); let regex = $crate::rule::Regex::new($regex).expect("invalid regex pattern"); - if regex.is_match(target) { - Ok(()) + if regex.is_match(target_as_ref) { + Ok(target) } else { - Err($crate::result::Error::new(format!("{target} 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)) } } } diff --git a/tests/length.rs b/tests/length.rs index 8ae6703..ad2edbc 100644 --- a/tests/length.rs +++ b/tests/length.rs @@ -6,7 +6,7 @@ length_equal!(5, 10); length_less_than!(10); #[test] -fn test_length() -> Result<(), refined_type::result::Error> { +fn test_length() -> Result<(), refined_type::result::Error> { type Password = Refined>; type From5To10Rule = And< @@ -34,7 +34,7 @@ fn test_length_fail() { } #[test] -fn test_length_greater_than_5() -> Result<(), refined_type::result::Error> { +fn test_length_greater_than_5() -> Result<(), refined_type::result::Error<&'static str>> { let target = "123456"; let refined = LengthGreaterThan5::new(target)?; assert_eq!(refined.into_value(), "123456"); @@ -49,7 +49,7 @@ fn test_length_greater_than_5_fail() { } #[test] -fn test_length_equal_5() -> Result<(), refined_type::result::Error> { +fn test_length_equal_5() -> Result<(), refined_type::result::Error<&'static str>> { let target = "12345"; let refined = LengthEqual5::new(target)?; assert_eq!(refined.into_value(), "12345"); @@ -64,7 +64,7 @@ fn test_length_equal_5_fail() { } #[test] -fn test_length_equal_10() -> Result<(), refined_type::result::Error> { +fn test_length_equal_10() -> Result<(), refined_type::result::Error<&'static str>> { let target = "1234567890"; let refined = LengthEqual10::new(target)?; assert_eq!(refined.into_value(), "1234567890"); @@ -79,7 +79,7 @@ fn test_length_equal_10_fail() { } #[test] -fn test_length_less_than_10() -> Result<(), refined_type::result::Error> { +fn test_length_less_than_10() -> Result<(), refined_type::result::Error<&'static str>> { let target = "123456789"; let refined = LengthLessThan10::new(target)?; assert_eq!(refined.into_value(), "123456789"); diff --git a/tests/read_me.rs b/tests/read_me.rs index 3ce8c39..91df9cc 100644 --- a/tests/read_me.rs +++ b/tests/read_me.rs @@ -33,8 +33,8 @@ fn example_1() -> anyhow::Result<()> { let actual = serde_json::from_str::(&json)?; let expected = Human { - name: MyNonEmptyString::new("john".to_string())?, - friends: MyNonEmptyVec::new(vec!["tom".to_string(), "taro".to_string()])?, + name: MyNonEmptyString::unsafe_new("john".to_string()), + friends: MyNonEmptyVec::unsafe_new(vec!["tom".to_string(), "taro".to_string()]), }; assert_eq!(actual, expected); Ok(()) @@ -75,11 +75,12 @@ struct ContainsWorldRule; impl Rule for ContainsHelloRule { type Item = String; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { if target.contains("Hello") { - Ok(()) + Ok(target) } else { - Err(Error::new(format!("{} does not contain `Hello`", target))) + let message = format!("{} does not contain `Hello`", target); + Err(Error::new(target, message)) } } } @@ -87,11 +88,12 @@ impl Rule for ContainsHelloRule { impl Rule for ContainsCommaRule { type Item = String; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { if target.contains(",") { - Ok(()) + Ok(target) } else { - Err(Error::new(format!("{} does not contain `,`", target))) + let message = format!("{} does not contain `,`", target); + Err(Error::new(target, message)) } } } @@ -99,11 +101,12 @@ impl Rule for ContainsCommaRule { impl Rule for ContainsWorldRule { type Item = String; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { if target.contains("World") { - Ok(()) + Ok(target) } else { - Err(Error::new(format!("{} does not contain `World`", target))) + let message = format!("{} does not contain `World`", target); + Err(Error::new(target, message)) } } } @@ -151,14 +154,12 @@ struct EndsWithJohnRule; impl Rule for StartsWithHelloRule { type Item = String; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { if target.starts_with("Hello") { - Ok(()) + Ok(target) } else { - Err(Error::new(format!( - "{} does not start with `Hello`", - target - ))) + let message = format!("{} does not start with `Hello`", target); + Err(Error::new(target, message)) } } } @@ -166,11 +167,12 @@ impl Rule for StartsWithHelloRule { impl Rule for StartsWithByeRule { type Item = String; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { if target.starts_with("Bye") { - Ok(()) + Ok(target) } else { - Err(Error::new(format!("{} does not start with `Bye`", target))) + let message = format!("{} does not start with `Bye`", target); + Err(Error::new(target, message)) } } } @@ -178,11 +180,12 @@ impl Rule for StartsWithByeRule { impl Rule for EndsWithJohnRule { type Item = String; - fn validate(target: &Self::Item) -> Result<(), Error> { + fn validate(target: Self::Item) -> Result> { if target.ends_with("John") { - Ok(()) + Ok(target) } else { - Err(Error::new(format!("{} does not end with `John`", target))) + let message = format!("{} does not end with `John`", target); + Err(Error::new(target, message)) } } } @@ -194,10 +197,10 @@ fn example_8() { EndsWithJohnRule ]; - assert!(GreetingRule::validate(&"Hello! Nice to meet you John".to_string()).is_ok()); - assert!(GreetingRule::validate(&"Bye! Have a good day John".to_string()).is_ok()); - assert!(GreetingRule::validate(&"How are you? Have a good day John".to_string()).is_err()); - assert!(GreetingRule::validate(&"Bye! Have a good day Tom".to_string()).is_err()); + assert!(GreetingRule::validate("Hello! Nice to meet you John".to_string()).is_ok()); + assert!(GreetingRule::validate("Bye! Have a good day John".to_string()).is_ok()); + assert!(GreetingRule::validate("How are you? Have a good day John".to_string()).is_err()); + assert!(GreetingRule::validate("Bye! Have a good day Tom".to_string()).is_err()); } #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)] @@ -209,7 +212,7 @@ struct Human2 { #[test] fn example_9() -> anyhow::Result<()> { let john = Human2 { - name: NonEmptyString::new("john".to_string())?, + name: NonEmptyString::unsafe_new("john".to_string()), age: 8, }; @@ -233,7 +236,7 @@ fn example_10() -> anyhow::Result<()> { let actual = serde_json::from_str::(&json)?; let expected = Human2 { - name: NonEmptyString::new("john".to_string())?, + name: NonEmptyString::unsafe_new("john".to_string()), age: 8, }; assert_eq!(actual, expected); @@ -260,7 +263,7 @@ type TargetAge80OrLess = Or![EqualRule80u8, LessRule80u8]; type TargetAgeRule = And![TargetAge18OrMore, TargetAge80OrLess]; #[test] -fn example_11() -> anyhow::Result<()> { +fn example_11() -> Result<(), Error>> { let vec = vec!["Hello".to_string(), "World".to_string()]; let for_all_ok = ForAllVec::::new(vec.clone())?; assert_eq!(vec, for_all_ok.into_value()); @@ -272,7 +275,7 @@ fn example_11() -> anyhow::Result<()> { } #[test] -fn example_12() -> anyhow::Result<()> { +fn example_12() -> Result<(), Error>> { let vec = vec!["Hello".to_string(), "".to_string()]; let exists_ok = ExistsVec::::new(vec.clone())?; assert_eq!(vec, exists_ok.into_value()); @@ -347,7 +350,7 @@ fn example_17() -> anyhow::Result<()> { } #[test] -fn example_18() -> anyhow::Result<()> { +fn example_18() -> Result<(), Error>> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.into_iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -355,7 +358,7 @@ fn example_18() -> anyhow::Result<()> { } #[test] -fn example_19() -> anyhow::Result<()> { +fn example_19() -> Result<(), Error>> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -363,7 +366,7 @@ fn example_19() -> anyhow::Result<()> { } #[test] -fn example_20() -> anyhow::Result<()> { +fn example_20() -> Result<(), Error>> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec_deque: NonEmptyVecDeque = ne_vec.into_iter().collect(); assert_eq!(ne_vec_deque.into_value(), vec![1, 2, 3]); @@ -371,7 +374,7 @@ fn example_20() -> anyhow::Result<()> { } #[test] -fn example_21() -> Result<(), Error> { +fn example_21() -> Result<(), Error> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -402,7 +405,7 @@ fn example_21() -> Result<(), Error> { } #[test] -fn example_22() -> anyhow::Result<()> { +fn example_22() -> Result<(), Error>> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -451,16 +454,16 @@ fn example_22() -> anyhow::Result<()> { Ok(()) } -#[test] -fn example_23() -> anyhow::Result<()> { - #[derive(Debug, PartialEq)] - struct Hello; - impl LengthDefinition for Hello { - fn length(&self) -> usize { - 5 - } +#[derive(Debug, PartialEq)] +struct Hello; +impl LengthDefinition for Hello { + fn length(&self) -> usize { + 5 } +} +#[test] +fn example_23() -> Result<(), Error> { length_equal!(5); let hello = Refined::>::new(Hello)?; assert_eq!(hello.into_value(), Hello); From 65991f85c09e12be407a1d22e5608e6979611ca9 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Wed, 18 Sep 2024 07:00:01 +0900 Subject: [PATCH 14/20] fix reverse --- src/rule/collection.rs | 2 + src/rule/collection/last.rs | 10 +--- src/rule/collection/last/collection.rs | 64 ----------------------- src/rule/collection/last/string.rs | 47 ----------------- src/rule/collection/reverse.rs | 25 +++++++++ src/rule/collection/reverse/collection.rs | 31 +++++++++++ src/rule/collection/reverse/string.rs | 21 ++++++++ 7 files changed, 81 insertions(+), 119 deletions(-) delete mode 100644 src/rule/collection/last/collection.rs delete mode 100644 src/rule/collection/last/string.rs create mode 100644 src/rule/collection/reverse.rs create mode 100644 src/rule/collection/reverse/collection.rs create mode 100644 src/rule/collection/reverse/string.rs diff --git a/src/rule/collection.rs b/src/rule/collection.rs index ee176f9..edc979a 100644 --- a/src/rule/collection.rs +++ b/src/rule/collection.rs @@ -4,6 +4,7 @@ mod head; mod index; mod init; mod last; +mod reverse; pub use exists::*; pub use for_all::*; @@ -11,3 +12,4 @@ pub use head::*; pub use index::*; pub use init::*; pub use last::*; +pub use reverse::*; diff --git a/src/rule/collection/last.rs b/src/rule/collection/last.rs index f081fb2..27f4efe 100644 --- a/src/rule/collection/last.rs +++ b/src/rule/collection/last.rs @@ -1,10 +1,6 @@ -mod collection; -mod string; - use std::collections::VecDeque; -use std::marker::PhantomData; -use crate::rule::Rule; +use crate::rule::{Index0Rule, ReverseRule, Rule}; use crate::Refined; /// A type that holds a value satisfying the `LastRule` @@ -20,9 +16,7 @@ pub type LastVecDeque = Refined>; pub type LastString = Refined>; /// Rule where the last element satisfies the condition -pub struct LastRule { - _phantom_data: PhantomData<(RULE, ITERABLE)>, -} +pub type LastRule = ReverseRule, ITERABLE>; /// Rule where the last element in the `Vec` satisfies the condition pub type LastVecRule = LastRule::Item>>; diff --git a/src/rule/collection/last/collection.rs b/src/rule/collection/last/collection.rs deleted file mode 100644 index 26ecb8e..0000000 --- a/src/rule/collection/last/collection.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::rule::{LastRule, Rule}; -use std::collections::VecDeque; - -impl Rule for LastRule> -where - RULE: Rule, -{ - type Item = Vec; - - fn validate(target: Self::Item) -> Result> { - let mut target = target.into_iter().collect::>(); - let last = target.pop_back(); - match last { - Some(item) => match RULE::validate(item) { - Ok(validated_item) => { - target.push_back(validated_item); - Ok(target.into_iter().collect()) - } - Err(e) => { - target.push_back(e.into_value()); - Err(crate::result::Error::new( - target.into_iter().collect(), - "Failed to validate the last item", - )) - } - }, - None => Err(crate::result::Error::new( - target.into_iter().collect(), - "Last item does not exist", - )), - } - } -} - -impl Rule for LastRule> -where - RULE: Rule, -{ - type Item = VecDeque; - - fn validate(target: Self::Item) -> Result> { - let mut target = target; - let last = target.pop_back(); - match last { - Some(item) => match RULE::validate(item) { - Ok(validated_item) => { - target.push_back(validated_item); - Ok(target) - } - Err(err) => { - target.push_back(err.into_value()); - Err(crate::result::Error::new( - target, - "Failed to validate the last item", - )) - } - }, - None => Err(crate::result::Error::new( - target, - "Last item does not exist", - )), - } - } -} diff --git a/src/rule/collection/last/string.rs b/src/rule/collection/last/string.rs deleted file mode 100644 index 393c28d..0000000 --- a/src/rule/collection/last/string.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::rule::{LastRule, Rule}; - -impl Rule for LastRule -where - RULE: Rule, -{ - type Item = String; - - fn validate(target: Self::Item) -> Result> { - match target.chars().last() { - Some(item) => match RULE::validate(item) { - Ok(_) => Ok(target), - Err(_) => Err(crate::result::Error::new( - target, - "Failed to validate the last item", - )), - }, - None => Err(crate::result::Error::new( - target, - "Last item does not exist", - )), - } - } -} - -impl<'a, RULE> Rule for LastRule -where - RULE: Rule, -{ - type Item = &'a str; - - fn validate(target: Self::Item) -> Result> { - match target.chars().last() { - Some(item) => match RULE::validate(item) { - Ok(_) => Ok(target), - Err(_) => Err(crate::result::Error::new( - target, - "Failed to validate the last item", - )), - }, - None => Err(crate::result::Error::new( - target, - "Last item does not exist", - )), - } - } -} diff --git a/src/rule/collection/reverse.rs b/src/rule/collection/reverse.rs new file mode 100644 index 0000000..64541c4 --- /dev/null +++ b/src/rule/collection/reverse.rs @@ -0,0 +1,25 @@ +mod collection; +mod string; + +use crate::rule::Rule; +use crate::Refined; +use std::collections::VecDeque; +use std::marker::PhantomData; + +pub type Reverse = Refined>; + +pub type ReverseVec = Refined>; + +pub type ReverseVecDeque = Refined>; + +pub type ReverseString = Refined>; + +pub struct ReverseRule { + _phantom_data: PhantomData<(RULE, ITERABLE)>, +} + +pub type ReverseVecRule = ReverseRule::Item>>; + +pub type ReverseVecDequeRule = ReverseRule::Item>>; + +pub type ReverseStringRule = ReverseRule; diff --git a/src/rule/collection/reverse/collection.rs b/src/rule/collection/reverse/collection.rs new file mode 100644 index 0000000..ea37091 --- /dev/null +++ b/src/rule/collection/reverse/collection.rs @@ -0,0 +1,31 @@ +use std::collections::VecDeque; + +use crate::result::Error; +use crate::rule::collection::reverse::ReverseRule; +use crate::rule::Rule; + +macro_rules! impl_reverse { + ($($t:ty),*) => { + $( + impl Rule for ReverseRule + where + RULE: Rule, + { + type Item = $t; + + fn validate(target: Self::Item) -> Result> { + match RULE::validate(target.into_iter().rev().collect()) { + Ok(value) => Ok(value.into_iter().rev().collect()), + Err(e) => { + let message = e.to_string(); + Err(Error::new(e.into_value().into_iter().rev().collect(), message)) + }, + } + } + } + )* + }; + () => {}; +} + +impl_reverse![Vec, VecDeque]; diff --git a/src/rule/collection/reverse/string.rs b/src/rule/collection/reverse/string.rs new file mode 100644 index 0000000..d483666 --- /dev/null +++ b/src/rule/collection/reverse/string.rs @@ -0,0 +1,21 @@ +use crate::rule::{ReverseRule, Rule}; + +impl Rule for ReverseRule +where + RULE: Rule, +{ + type Item = String; + + fn validate(target: Self::Item) -> Result> { + match RULE::validate(target.chars().rev().collect()) { + Ok(value) => Ok(value.chars().rev().collect()), + Err(e) => { + let message = e.to_string(); + Err(crate::result::Error::new( + e.into_value().chars().rev().collect(), + message, + )) + } + } + } +} From 5b3ca5ccaf26a64d43678197d1ecdc27d84842eb Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:24:27 +0900 Subject: [PATCH 15/20] impl: skip --- src/rule/collection.rs | 2 + src/rule/collection/exists.rs | 17 ++-- src/rule/collection/for_all.rs | 21 +++-- src/rule/collection/init.rs | 17 ++-- src/rule/collection/init/collection.rs | 91 ------------------- src/rule/collection/init/string.rs | 63 ------------- src/rule/collection/skip.rs | 41 +++++++++ src/rule/collection/skip/collection.rs | 47 ++++++++++ src/rule/collection/skip/option.rs | 10 ++ src/rule/collection/skip/option/no_skip.rs | 12 +++ src/rule/collection/skip/option/skip_first.rs | 12 +++ src/rule/collection/skip/string.rs | 33 +++++++ 12 files changed, 187 insertions(+), 179 deletions(-) delete mode 100644 src/rule/collection/init/collection.rs delete mode 100644 src/rule/collection/init/string.rs create mode 100644 src/rule/collection/skip.rs create mode 100644 src/rule/collection/skip/collection.rs create mode 100644 src/rule/collection/skip/option.rs create mode 100644 src/rule/collection/skip/option/no_skip.rs create mode 100644 src/rule/collection/skip/option/skip_first.rs create mode 100644 src/rule/collection/skip/string.rs diff --git a/src/rule/collection.rs b/src/rule/collection.rs index edc979a..2142f6f 100644 --- a/src/rule/collection.rs +++ b/src/rule/collection.rs @@ -5,6 +5,7 @@ mod index; mod init; mod last; mod reverse; +mod skip; pub use exists::*; pub use for_all::*; @@ -13,3 +14,4 @@ pub use index::*; pub use init::*; pub use last::*; pub use reverse::*; +pub use skip::*; diff --git a/src/rule/collection/exists.rs b/src/rule/collection/exists.rs index 72666e5..9f96768 100644 --- a/src/rule/collection/exists.rs +++ b/src/rule/collection/exists.rs @@ -5,7 +5,7 @@ use crate::rule::{ForAllRule, Rule}; use crate::Refined; /// A type that holds a value satisfying the `ExistsRule` -pub type Exists = Refined>; +pub type Exists = Refined::Item>>; /// A type that holds a Vec value satisfying the `ExistsRule` pub type ExistsVec = Refined>; @@ -23,22 +23,25 @@ pub type ExistsHashMap = Refined>; pub type ExistsString = Refined>; /// Rule where at least one data in the collection satisfies the condition -pub type ExistsRule = Not, ITERABLE>>; +pub type ExistsRule = Not, ITERABLE, ITEM>>; /// Rule where at least one data in the `Vec` satisfies the condition -pub type ExistsVecRule = ExistsRule::Item>>; +pub type ExistsVecRule = ExistsRule::Item>, ::Item>; /// Rule where at least one data in the `VecDeque` satisfies the condition -pub type ExistsVecDequeRule = ExistsRule::Item>>; +pub type ExistsVecDequeRule = + ExistsRule::Item>, ::Item>; /// Rule where at least one data in the `HashSet` satisfies the condition -pub type ExistsHashSetRule = ExistsRule::Item>>; +pub type ExistsHashSetRule = + ExistsRule::Item>, ::Item>; /// Rule where at least one data in the `HashMap` satisfies the condition -pub type ExistsHashMapRule = ExistsRule::Item>>; +pub type ExistsHashMapRule = + ExistsRule::Item>, ::Item>; /// Rule where at least one data in the `String` satisfies the condition -pub type ExistsStringRule = ExistsRule; +pub type ExistsStringRule = ExistsRule; #[cfg(test)] mod tests { diff --git a/src/rule/collection/for_all.rs b/src/rule/collection/for_all.rs index 4008a1f..a0085ae 100644 --- a/src/rule/collection/for_all.rs +++ b/src/rule/collection/for_all.rs @@ -1,10 +1,10 @@ use std::collections::{HashMap, HashSet, VecDeque}; -use crate::rule::{InitRule, LastRule, Rule}; -use crate::{And, Refined}; +use crate::rule::{NoSkip, Rule, SkipRule}; +use crate::Refined; /// A type that holds a value satisfying the `ForAllRule` -pub type ForAll = Refined>; +pub type ForAll = Refined>; /// A type that holds a Vec value satisfying the `ForAllRule` pub type ForAllVec = Refined>; @@ -22,22 +22,25 @@ pub type ForAllHashMap = Refined>; pub type ForAllString = Refined>; /// Rule where all the data in the collection satisfies the condition -pub type ForAllRule = And![InitRule, LastRule]; +pub type ForAllRule = SkipRule>; /// Rule where all the data in the `Vec` satisfies the condition -pub type ForAllVecRule = ForAllRule::Item>>; +pub type ForAllVecRule = ForAllRule::Item>, ::Item>; /// Rule where all the data in the `VecDeque` satisfies the condition -pub type ForAllVecDequeRule = ForAllRule::Item>>; +pub type ForAllVecDequeRule = + ForAllRule::Item>, ::Item>; /// Rule where all the data in the `HashSet` satisfies the condition -pub type ForAllHashSetRule = ForAllRule::Item>>; +pub type ForAllHashSetRule = + ForAllRule::Item>, ::Item>; /// Rule where all the data in the `HashMap` satisfies the condition -pub type ForAllHashMapRule = ForAllRule::Item>>; +pub type ForAllHashMapRule = + ForAllRule::Item>, ::Item>; /// Rule where all the data in the `String` satisfies the condition -pub type ForAllStringRule = ForAllRule; +pub type ForAllStringRule = ForAllRule; #[cfg(test)] mod tests { diff --git a/src/rule/collection/init.rs b/src/rule/collection/init.rs index 497d6a6..3355a14 100644 --- a/src/rule/collection/init.rs +++ b/src/rule/collection/init.rs @@ -1,13 +1,12 @@ -use crate::rule::Rule; +use crate::rule::{ReverseRule, Rule, SkipFirst, SkipRule}; use crate::Refined; use std::collections::VecDeque; -use std::marker::PhantomData; mod collection; mod string; /// A type that holds a value satisfying the `InitRule` -pub type Init = Refined>; +pub type Init = Refined>; /// A type that holds a Vec value satisfying the `InitRule` pub type InitVec = Refined>; @@ -19,18 +18,18 @@ pub type InitVecDeque = Refined>; pub type InitString = Refined>; /// Rule that applies to the initialization of a collection -pub struct InitRule { - _phantom_data: PhantomData<(RULE, ITERABLE)>, -} +pub type InitRule = + ReverseRule>, ITERABLE>; /// Rule that applies to the initialization of a `Vec` -pub type InitVecRule = InitRule::Item>>; +pub type InitVecRule = InitRule::Item>, ::Item>; /// Rule that applies to the initialization of a `VecDeque` -pub type InitVecDequeRule = InitRule::Item>>; +pub type InitVecDequeRule = + InitRule::Item>, ::Item>; /// Rule that applies to the initialization of a `String` -pub type InitStringRule = InitRule; +pub type InitStringRule = InitRule; #[cfg(test)] mod tests { diff --git a/src/rule/collection/init/collection.rs b/src/rule/collection/init/collection.rs deleted file mode 100644 index 70060fa..0000000 --- a/src/rule/collection/init/collection.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::collections::{HashMap, VecDeque}; - -use crate::result::Error; -use crate::rule::InitRule; -use crate::rule::Rule; - -macro_rules! impl_init { - ($($t:ty),*) => { - $( - impl Rule for InitRule - where - RULE: Rule, - { - type Item = $t; - - fn validate(target: Self::Item) -> Result> { - let length = target.len(); - let mut remains = target.into_iter(); - let mut result = VecDeque::new(); - let mut failed = false; - - for (i, item) in remains.by_ref().enumerate() { - if i < length - 1 { - match RULE::validate(item) { - Ok(validated_item) => result.push_back(validated_item), - Err(err) => { - result.push_back(err.into_value()); - failed = true; - break; - } - } - } else { - result.push_back(item); - } - } - - if failed { - result.append(&mut remains.collect::>()); - let result = result.into_iter().collect::<$t>(); - Err(Error::new( - result, - "Failed to validate all items", - )) - } else { - Ok(result.into_iter().collect::<$t>()) - } - } - } - )* - }; -} - -impl_init![Vec, VecDeque]; - -impl Rule for InitRule> -where - RULE: Rule, - K: Eq + std::hash::Hash, -{ - type Item = HashMap; - - fn validate(target: Self::Item) -> Result> { - let length = target.len(); - let mut remains = target.into_iter(); - let mut result = VecDeque::new(); - let mut failed = false; - - for (i, (key, value)) in remains.by_ref().enumerate() { - if i < length - 1 { - match RULE::validate(value) { - Ok(validated_item) => result.push_back((key, validated_item)), - Err(err) => { - result.push_back((key, err.into_value())); - failed = true; - break; - } - } - } else { - result.push_back((key, value)); - } - } - - if failed { - result.append(&mut remains.collect::>()); - let result = result.into_iter().collect::>(); - Err(Error::new(result, "Failed to validate all items")) - } else { - Ok(result.into_iter().collect::>()) - } - } -} diff --git a/src/rule/collection/init/string.rs b/src/rule/collection/init/string.rs deleted file mode 100644 index 2cfdc0d..0000000 --- a/src/rule/collection/init/string.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::result::Error; -use crate::rule::{InitRule, Rule}; - -impl Rule for InitRule -where - RULE: Rule, -{ - type Item = String; - - fn validate(target: Self::Item) -> Result> { - let mut remains = target.chars(); - let mut result = Vec::new(); - let mut failed = false; - - for (i, item) in remains.by_ref().enumerate() { - if i < target.len() - 1 { - match RULE::validate(item) { - Ok(validated_item) => result.push(validated_item), - Err(err) => { - result.push(err.into_value()); - failed = true; - break; - } - } - } else { - result.push(item); - } - } - - if failed { - result.append(&mut remains.collect::>()); - let result = result.into_iter().collect::(); - Err(Error::new(result, "Failed to validate all items")) - } else { - Ok(result.into_iter().collect::()) - } - } -} - -impl<'a, RULE> Rule for InitRule -where - RULE: Rule, -{ - type Item = &'a str; - - fn validate(target: Self::Item) -> Result> { - let length = target.len(); - let mut result = Ok(target); - for (i, c) in target.chars().enumerate() { - if i == length - 1 { - break; - } - match RULE::validate(c) { - Ok(_) => continue, - Err(_) => { - result = Err(Error::new(target, "Failed to validate all items")); - break; - } - } - } - result - } -} diff --git a/src/rule/collection/skip.rs b/src/rule/collection/skip.rs new file mode 100644 index 0000000..a624b42 --- /dev/null +++ b/src/rule/collection/skip.rs @@ -0,0 +1,41 @@ +mod collection; +mod option; +mod string; + +use std::collections::VecDeque; +use std::marker::PhantomData; + +pub use option::*; + +use crate::rule::Rule; +use crate::Refined; + +/// A type that holds a value satisfying the `SkipRule` +pub type Skip = Refined>; + +/// A type that holds a `Vec` value satisfying the `SkipRule` +pub type SkipVec = Refined>; + +/// A type that holds a `VecDeque` value satisfying the `SkipRule` +pub type SkipVecDeque = Refined>; + +/// A type that holds a `String` value satisfying the `SkipRule` +pub type SkipString = Refined>; + +/// Rule where the data in the collection satisfies the condition after skipping the first element +pub struct SkipRule +where + RULE: Rule, + OPTION: SkipOption, +{ + _phantom_data: PhantomData<(RULE, ITERABLE, OPTION)>, +} + +/// Rule where the data in the `Vec` satisfies the condition after skipping the first element +pub type SkipVecRule = SkipRule::Item>, OPTION>; + +/// Rule where the data in the `VecDeque` satisfies the condition after skipping the first element +pub type SkipVecDequeRule = SkipRule::Item>, OPTION>; + +/// Rule where the data in the `String` satisfies the condition after skipping the first element +pub type SkipStringRule = SkipRule; diff --git a/src/rule/collection/skip/collection.rs b/src/rule/collection/skip/collection.rs new file mode 100644 index 0000000..57efbd5 --- /dev/null +++ b/src/rule/collection/skip/collection.rs @@ -0,0 +1,47 @@ +use std::collections::VecDeque; + +macro_rules! impl_skip_rule { + ($($t:ty),*) => { + $( + impl $crate::rule::Rule for $crate::rule::SkipRule + where + RULE: $crate::rule::Rule, + OPTION: $crate::rule::SkipOption, + { + type Item = $t; + + fn validate(target: Self::Item) -> Result> { + let mut remains = target.into_iter(); + let mut result = VecDeque::new(); + let (mut is_valid, mut message) = (true, String::new()); + for (i, item) in remains.by_ref().enumerate() { + if OPTION::should_skip(i, &item) { + result.push_back(item); + continue; + } + match RULE::validate(item) { + Ok(validated_item) => result.push_back(validated_item), + Err(err) => { + is_valid = false; + message = format!( + "the item at index {} does not satisfy the condition: {}", + i, err + ); + result.push_back(err.into_value()); + } + } + } + + if is_valid { + Ok(result.into_iter().collect()) + } else { + result.append(&mut remains.collect::>()); + Err(crate::result::Error::new(result.into_iter().collect(), message)) + } + } + } + )* + }; +} + +impl_skip_rule![Vec, VecDeque]; diff --git a/src/rule/collection/skip/option.rs b/src/rule/collection/skip/option.rs new file mode 100644 index 0000000..a77d126 --- /dev/null +++ b/src/rule/collection/skip/option.rs @@ -0,0 +1,10 @@ +mod no_skip; +mod skip_first; + +pub use no_skip::NoSkip; +pub use skip_first::SkipFirst; + +pub trait SkipOption { + type Item; + fn should_skip(i: usize, item: &Self::Item) -> bool; +} diff --git a/src/rule/collection/skip/option/no_skip.rs b/src/rule/collection/skip/option/no_skip.rs new file mode 100644 index 0000000..bf62ec1 --- /dev/null +++ b/src/rule/collection/skip/option/no_skip.rs @@ -0,0 +1,12 @@ +use crate::rule::SkipOption; + +pub struct NoSkip { + _phantom_data: std::marker::PhantomData, +} + +impl SkipOption for NoSkip { + type Item = ITEM; + fn should_skip(_: usize, _: &Self::Item) -> bool { + false + } +} diff --git a/src/rule/collection/skip/option/skip_first.rs b/src/rule/collection/skip/option/skip_first.rs new file mode 100644 index 0000000..b5b638c --- /dev/null +++ b/src/rule/collection/skip/option/skip_first.rs @@ -0,0 +1,12 @@ +use crate::rule::SkipOption; + +pub struct SkipFirst { + _phantom_data: std::marker::PhantomData, +} + +impl SkipOption for SkipFirst { + type Item = ITEM; + fn should_skip(i: usize, _: &Self::Item) -> bool { + i == 0 + } +} diff --git a/src/rule/collection/skip/string.rs b/src/rule/collection/skip/string.rs new file mode 100644 index 0000000..11d8638 --- /dev/null +++ b/src/rule/collection/skip/string.rs @@ -0,0 +1,33 @@ +use crate::result::Error; +use crate::rule::collection::skip::option::SkipOption; +use crate::rule::collection::skip::SkipRule; +use crate::rule::Rule; + +impl Rule for SkipRule +where + RULE: Rule, + OPTION: SkipOption, +{ + type Item = String; + + fn validate(target: Self::Item) -> Result> { + let chars = target.chars(); + let (mut is_valid, mut message) = (true, String::new()); + for (i, c) in chars.enumerate() { + if OPTION::should_skip(i, &c) { + continue; + } else if let Err(e) = RULE::validate(c) { + is_valid = false; + message = format!( + "the character at index {} does not satisfy the condition: {}", + i, e + ); + } + } + if is_valid { + Ok(target) + } else { + Err(Error::new(target, message)) + } + } +} From 74bbb9118d94b5ada0aad8b37946384d06dd8b75 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:36:09 +0900 Subject: [PATCH 16/20] impl: skip test --- src/rule/collection/init.rs | 3 --- src/rule/collection/skip.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/rule/collection/init.rs b/src/rule/collection/init.rs index 3355a14..702de3e 100644 --- a/src/rule/collection/init.rs +++ b/src/rule/collection/init.rs @@ -2,9 +2,6 @@ use crate::rule::{ReverseRule, Rule, SkipFirst, SkipRule}; use crate::Refined; use std::collections::VecDeque; -mod collection; -mod string; - /// A type that holds a value satisfying the `InitRule` pub type Init = Refined>; diff --git a/src/rule/collection/skip.rs b/src/rule/collection/skip.rs index a624b42..30ed571 100644 --- a/src/rule/collection/skip.rs +++ b/src/rule/collection/skip.rs @@ -39,3 +39,32 @@ pub type SkipVecDequeRule = SkipRule /// Rule where the data in the `String` satisfies the condition after skipping the first element pub type SkipStringRule = SkipRule; + +#[cfg(test)] +mod tests { + use crate::result::Error; + use crate::rule::{NonEmptyStringRule, SkipFirst, SkipVec}; + + #[test] + fn test_skip_first() -> Result<(), Error>> { + let table = vec![ + ( + vec!["hey".to_string(), "hello".to_string(), "world".to_string()], + vec!["hey".to_string(), "hello".to_string(), "world".to_string()], + ), + ( + vec!["".to_string(), "hello".to_string(), "world".to_string()], + vec!["".to_string(), "hello".to_string(), "world".to_string()], + ), + (vec!["".to_string()], vec!["".to_string()]), + (vec![], vec![]), + ]; + + for (data, expected) in table { + let value = SkipVec::>::new(data)?; + assert_eq!(value.into_value(), expected); + } + + Ok(()) + } +} From 961531e7f3f90e53f9cf8c7997ce8e75e7f4791c Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:37:39 +0900 Subject: [PATCH 17/20] impl: skip test --- src/rule/collection/skip.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/rule/collection/skip.rs b/src/rule/collection/skip.rs index 30ed571..e649556 100644 --- a/src/rule/collection/skip.rs +++ b/src/rule/collection/skip.rs @@ -46,7 +46,7 @@ mod tests { use crate::rule::{NonEmptyStringRule, SkipFirst, SkipVec}; #[test] - fn test_skip_first() -> Result<(), Error>> { + fn test_skip_first_valid() -> Result<(), Error>> { let table = vec![ ( vec!["hey".to_string(), "hello".to_string(), "world".to_string()], @@ -67,4 +67,19 @@ mod tests { Ok(()) } + + #[test] + fn test_skip_first_invalid() { + let table = vec![ + vec!["hey".to_string(), "".to_string(), "world".to_string()], + vec!["".to_string(), "".to_string(), "world".to_string()], + vec!["hey".to_string(), "hello".to_string(), "".to_string()], + vec!["".to_string(), "hello".to_string(), "".to_string()], + ]; + + for data in table { + let value = SkipVec::>::new(data); + assert!(value.is_err()); + } + } } From 46000cdd4d0563acc63fcdd86a345e9a6006e431 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:40:18 +0900 Subject: [PATCH 18/20] impl: skip test --- src/rule/collection/skip.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rule/collection/skip.rs b/src/rule/collection/skip.rs index e649556..be90925 100644 --- a/src/rule/collection/skip.rs +++ b/src/rule/collection/skip.rs @@ -61,7 +61,7 @@ mod tests { ]; for (data, expected) in table { - let value = SkipVec::>::new(data)?; + let value = SkipVec::>::new(data)?; assert_eq!(value.into_value(), expected); } @@ -78,7 +78,7 @@ mod tests { ]; for data in table { - let value = SkipVec::>::new(data); + let value = SkipVec::>::new(data); assert!(value.is_err()); } } From a0b7dbd724522e15d8450f66b16de9e494bd5553 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:49:42 +0900 Subject: [PATCH 19/20] impl: tail test --- src/rule/collection.rs | 2 + src/rule/collection/tail.rs | 78 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/rule/collection/tail.rs diff --git a/src/rule/collection.rs b/src/rule/collection.rs index 2142f6f..45bb13b 100644 --- a/src/rule/collection.rs +++ b/src/rule/collection.rs @@ -6,6 +6,7 @@ mod init; mod last; mod reverse; mod skip; +mod tail; pub use exists::*; pub use for_all::*; @@ -15,3 +16,4 @@ pub use init::*; pub use last::*; pub use reverse::*; pub use skip::*; +pub use tail::*; diff --git a/src/rule/collection/tail.rs b/src/rule/collection/tail.rs new file mode 100644 index 0000000..f5e64be --- /dev/null +++ b/src/rule/collection/tail.rs @@ -0,0 +1,78 @@ +use crate::rule::{Rule, SkipFirst, SkipRule}; +use crate::Refined; +use std::collections::VecDeque; + +/// A type that holds a value satisfying the `TailRule` +pub type Tail = Refined>; + +/// A type that holds a `Vec` value satisfying the `TailRule` +pub type TailVec = Tail::Item>, ::Item>; + +/// A type that holds a `VecDeque` value satisfying the `TailRule` +pub type TailVecDeque = Tail::Item>, ::Item>; + +/// A type that holds a `String` value satisfying the `TailRule` +pub type TailString = Tail; + +/// Rule where the data in the collection satisfies the condition after skipping the first element +pub type TailRule = SkipRule>; + +/// Rule where the data in the `Vec` satisfies the condition after skipping the first element +pub type TailVecRule = TailRule::Item>, ::Item>; + +/// Rule where the data in the `VecDeque` satisfies the condition after skipping the first element +pub type TailVecDequeRule = + TailRule::Item>, ::Item>; + +/// Rule where the data in the `String` satisfies the condition after skipping the first element +pub type TailStringRule = TailRule; + +#[cfg(test)] +mod tests { + use crate::result::Error; + use crate::rule::{NonEmptyStringRule, TailVec}; + + #[test] + fn test_tail_valid() -> Result<(), Error>> { + let table = vec![ + ( + vec!["hey".to_string(), "hello".to_string(), "world".to_string()], + vec!["hey".to_string(), "hello".to_string(), "world".to_string()], + ), + ( + vec!["".to_string(), "hello".to_string(), "world".to_string()], + vec!["".to_string(), "hello".to_string(), "world".to_string()], + ), + (vec!["".to_string()], vec!["".to_string()]), + (vec![], vec![]), + ]; + + for (input, expected) in table { + let refined = TailVec::::new(input.clone())?; + assert_eq!(refined.into_value(), expected); + } + + Ok(()) + } + + #[test] + fn test_tail_invalid() -> Result<(), Error>> { + let table = vec![ + vec!["hey".to_string(), "hello".to_string(), "".to_string()], + vec!["hey".to_string(), "".to_string(), "".to_string()], + vec!["".to_string(), "hello".to_string(), "".to_string()], + vec!["".to_string(), "".to_string(), "".to_string()], + vec!["hey".to_string(), "hello".to_string(), "".to_string()], + vec!["hey".to_string(), "".to_string(), "".to_string()], + vec!["".to_string(), "hello".to_string(), "".to_string()], + vec!["".to_string(), "".to_string(), "".to_string()], + ]; + + for input in table { + let refined = TailVec::::new(input.clone()); + assert!(refined.is_err()); + } + + Ok(()) + } +} From 150602fae2424209c73a206c4d86f0390fc8e226 Mon Sep 17 00:00:00 2001 From: tomoikey <55743826+tomoikey@users.noreply.github.com> Date: Thu, 19 Sep 2024 04:23:39 +0900 Subject: [PATCH 20/20] impl: reverse test --- README.md | 128 +++++++++++++++++++++++++++-- src/rule/collection/reverse.rs | 45 ++++++++--- tests/read_me.rs | 142 ++++++++++++++++++++++++++++++--- 3 files changed, 284 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 3da28d0..7b30805 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,22 @@ fn example_14() -> anyhow::Result<()> { ```rust fn example_15() -> anyhow::Result<()> { + let table = vec![ + (vec!["hey".to_string(), "hello".to_string(), "world".to_string()], true), + (vec!["hey".to_string(), "hello".to_string(), "".to_string()], false), + (vec!["hey".to_string(), "".to_string(), "world".to_string()], false), + (vec!["hey".to_string(), "".to_string(), "".to_string()], false), + (vec!["".to_string(), "hello".to_string(), "world".to_string()], true), + (vec!["".to_string(), "hello".to_string(), "".to_string()], false), + (vec!["".to_string(), "".to_string(), "world".to_string()], false), + (vec!["".to_string(), "".to_string(), "".to_string()], false), + ]; + + for (value, ok) in table { + let tail = TailVec::::new(value.clone()); + assert_eq!(tail.is_ok(), ok); + } + Ok(()) } ``` @@ -407,6 +423,22 @@ fn example_15() -> anyhow::Result<()> { ```rust fn example_16() -> anyhow::Result<()> { + let table = vec![ + (vec!["hey".to_string(), "hello".to_string(), "world".to_string()], true), + (vec!["hey".to_string(), "hello".to_string(), "".to_string()], true), + (vec!["hey".to_string(), "".to_string(), "world".to_string()], false), + (vec!["hey".to_string(), "".to_string(), "".to_string()], false), + (vec!["".to_string(), "hello".to_string(), "world".to_string()], false), + (vec!["".to_string(), "hello".to_string(), "".to_string()], false), + (vec!["".to_string(), "".to_string(), "world".to_string()], false), + (vec!["".to_string(), "".to_string(), "".to_string()], false), + ]; + + for (value, ok) in table { + let init = InitVec::::new(value.clone()); + assert_eq!(init.is_ok(), ok); + } + Ok(()) } ``` @@ -417,10 +449,94 @@ fn example_16() -> anyhow::Result<()> { ```rust fn example_17() -> anyhow::Result<()> { + let table = vec![ + (vec!["good morning".to_string(), "hello".to_string()], true), + (vec!["good morning".to_string(), "".to_string()], false), + (vec!["".to_string(), "hello".to_string()], true), + (vec!["".to_string(), "".to_string()], false), + ]; + + for (value, expected) in table { + let refined = Index1Vec::::new(value.clone()); + assert_eq!(refined.is_ok(), expected); + } + + Ok(()) +} +``` + +# `Reverse` + +`Reverse` is a rule that applies a specific rule to all elements in the Iterator in reverse order. +`refined_type` crate has `Index0` to `Index10` by default. + +```rust +fn example_18() -> Result<(), Error>> { + let table = vec![ + (vec!["good morning".to_string(), "hello".to_string()], true), + (vec!["good morning".to_string(), "".to_string()], false), + (vec!["".to_string(), "hello".to_string()], true), + (vec!["".to_string(), "".to_string()], false), + ]; + + for (value, expected) in table { + let refined = Reverse::, _>::new(value.clone()); + assert_eq!(refined.is_ok(), expected); + } + Ok(()) } ``` +if you need more, you can define it like this. + +```rust +define_index_refined!(11, 12, 13); +define_index_rule!(11, 12, 13); +``` + +# `Skip` + +`Skip` is a rule that applies a specific rule to the elements of the Iterator while skipping the elements according +to `SkipOption`. + +```rust +fn example_19() -> Result<(), Error>> { + let table = vec![ + (vec!["hey".to_string(), "hello".to_string(), "world".to_string()], true), + (vec!["hey".to_string(), "hello".to_string(), "".to_string()], false), + (vec!["hey".to_string(), "".to_string(), "world".to_string()], false), + (vec!["hey".to_string(), "".to_string(), "".to_string()], false), + (vec!["".to_string(), "hello".to_string(), "world".to_string()], true), + (vec!["".to_string(), "hello".to_string(), "".to_string()], false), + (vec!["".to_string(), "".to_string(), "world".to_string()], false), + (vec!["".to_string(), "".to_string(), "".to_string()], false), + ]; + + for (value, ok) in table { + let init = SkipVec::>::new(value.clone()); + assert_eq!(init.is_ok(), ok); + } + + Ok(()) +} +``` + +if you need more skip option, you can define it like this. + +```rust +pub struct NoSkip { + _phantom_data: std::marker::PhantomData, +} + +impl SkipOption for NoSkip { + type Item = ITEM; + fn should_skip(_: usize, _: &Self::Item) -> bool { + false + } +} +``` + --- ## `into_iter()` and `iter()` @@ -432,7 +548,7 @@ Feel free to explore the capabilities of the Iterator you’ve been given! ### `into_iter()` ```rust -fn example_18() -> anyhow::Result<()> { +fn example_20() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.into_iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -443,7 +559,7 @@ fn example_18() -> anyhow::Result<()> { ### `iter()` ```rust -fn example_19() -> anyhow::Result<()> { +fn example_21() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec: NonEmptyVec = ne_vec.iter().map(|n| n * 2).map(|n| n * 3).collect(); assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); @@ -454,7 +570,7 @@ fn example_19() -> anyhow::Result<()> { ### `NonEmptyVec` to `NonEmptyVecDeque` using `collect()` ```rust -fn example_20() -> anyhow::Result<()> { +fn example_22() -> anyhow::Result<()> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec_deque: NonEmptyVecDeque = ne_vec.into_iter().collect(); assert_eq!(ne_vec_deque.into_value(), vec![1, 2, 3]); @@ -469,7 +585,7 @@ You can impose constraints on objects that have a length, such as `String` or `V ### String ```rust -fn example_21() -> Result<(), Error> { +fn example_23() -> Result<(), Error> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -504,7 +620,7 @@ fn example_21() -> Result<(), Error> { ```rust #[test] -fn example_22() -> anyhow::Result<()> { +fn example_24() -> anyhow::Result<()> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -561,7 +677,7 @@ by `refined_type`, you can easily do so using `LengthDefinition`. ```rust #[test] -fn example_23() -> anyhow::Result<()> { +fn example_25() -> anyhow::Result<()> { length_equal!(5); #[derive(Debug, PartialEq)] diff --git a/src/rule/collection/reverse.rs b/src/rule/collection/reverse.rs index 64541c4..cb48130 100644 --- a/src/rule/collection/reverse.rs +++ b/src/rule/collection/reverse.rs @@ -1,25 +1,44 @@ mod collection; mod string; -use crate::rule::Rule; use crate::Refined; -use std::collections::VecDeque; use std::marker::PhantomData; +/// A type that holds a value satisfying the `ReverseRule` pub type Reverse = Refined>; -pub type ReverseVec = Refined>; - -pub type ReverseVecDeque = Refined>; - -pub type ReverseString = Refined>; - +/// Rule where the data in the collection satisfies the condition after reversing pub struct ReverseRule { _phantom_data: PhantomData<(RULE, ITERABLE)>, } -pub type ReverseVecRule = ReverseRule::Item>>; - -pub type ReverseVecDequeRule = ReverseRule::Item>>; - -pub type ReverseStringRule = ReverseRule; +#[cfg(test)] +mod tests { + use crate::result::Error; + use crate::rule::{Index0VecRule, NonEmptyStringRule, Reverse}; + + #[test] + fn test_reverse_valid() -> Result<(), Error>> { + let table = vec![ + vec!["hey".to_string(), "hello".to_string()], + vec!["hello".to_string()], + ]; + + for input in table { + let refined = Reverse::, _>::new(input.clone())?; + assert_eq!(refined.into_value(), input); + } + + Ok(()) + } + + #[test] + fn test_reverse_invalid() { + let table = vec![vec!["".to_string()], vec![]]; + + for input in table { + let refined = Reverse::, _>::new(input.clone()); + assert!(refined.is_err()); + } + } +} diff --git a/tests/read_me.rs b/tests/read_me.rs index 91df9cc..249d007 100644 --- a/tests/read_me.rs +++ b/tests/read_me.rs @@ -4,8 +4,9 @@ use serde_json::json; use refined_type::result::Error; use refined_type::rule::composer::Not; use refined_type::rule::{ - ExistsVec, ForAllVec, HeadVec, Index1Vec, LastVec, LengthDefinition, NonEmptyRule, - NonEmptyString, NonEmptyStringRule, NonEmptyVec, NonEmptyVecDeque, Rule, + ExistsVec, ForAllVec, HeadVec, Index0VecRule, Index1Vec, InitVec, LastVec, LengthDefinition, + NonEmptyRule, NonEmptyString, NonEmptyStringRule, NonEmptyVec, NonEmptyVecDeque, Reverse, Rule, + SkipFirst, SkipVec, TailVec, }; use refined_type::{ equal_rule, greater_rule, length_equal, length_greater_than, length_less_than, less_rule, And, @@ -324,11 +325,85 @@ fn example_14() -> anyhow::Result<()> { #[test] fn example_15() -> anyhow::Result<()> { + let table = vec![ + ( + vec!["hey".to_string(), "hello".to_string(), "world".to_string()], + true, + ), + ( + vec!["hey".to_string(), "hello".to_string(), "".to_string()], + false, + ), + ( + vec!["hey".to_string(), "".to_string(), "world".to_string()], + false, + ), + ( + vec!["hey".to_string(), "".to_string(), "".to_string()], + false, + ), + ( + vec!["".to_string(), "hello".to_string(), "world".to_string()], + true, + ), + ( + vec!["".to_string(), "hello".to_string(), "".to_string()], + false, + ), + ( + vec!["".to_string(), "".to_string(), "world".to_string()], + false, + ), + (vec!["".to_string(), "".to_string(), "".to_string()], false), + ]; + + for (value, ok) in table { + let tail = TailVec::::new(value.clone()); + assert_eq!(tail.is_ok(), ok); + } + Ok(()) } #[test] fn example_16() -> anyhow::Result<()> { + let table = vec![ + ( + vec!["hey".to_string(), "hello".to_string(), "world".to_string()], + true, + ), + ( + vec!["hey".to_string(), "hello".to_string(), "".to_string()], + true, + ), + ( + vec!["hey".to_string(), "".to_string(), "world".to_string()], + false, + ), + ( + vec!["hey".to_string(), "".to_string(), "".to_string()], + false, + ), + ( + vec!["".to_string(), "hello".to_string(), "world".to_string()], + false, + ), + ( + vec!["".to_string(), "hello".to_string(), "".to_string()], + false, + ), + ( + vec!["".to_string(), "".to_string(), "world".to_string()], + false, + ), + (vec!["".to_string(), "".to_string(), "".to_string()], false), + ]; + + for (value, ok) in table { + let init = InitVec::::new(value.clone()); + assert_eq!(init.is_ok(), ok); + } + Ok(()) } @@ -351,22 +426,65 @@ fn example_17() -> anyhow::Result<()> { #[test] fn example_18() -> Result<(), Error>> { - let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; - let ne_vec: NonEmptyVec = ne_vec.into_iter().map(|n| n * 2).map(|n| n * 3).collect(); - assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); + let table = vec![ + (vec!["good morning".to_string(), "hello".to_string()], true), + (vec!["good morning".to_string(), "".to_string()], false), + (vec!["".to_string(), "hello".to_string()], true), + (vec!["".to_string(), "".to_string()], false), + ]; + + for (value, expected) in table { + let refined = Reverse::, _>::new(value.clone()); + assert_eq!(refined.is_ok(), expected); + } + Ok(()) } #[test] fn example_19() -> Result<(), Error>> { - let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; - let ne_vec: NonEmptyVec = ne_vec.iter().map(|n| n * 2).map(|n| n * 3).collect(); - assert_eq!(ne_vec.into_value(), vec![6, 12, 18]); + let table = vec![ + ( + vec!["hey".to_string(), "hello".to_string(), "world".to_string()], + true, + ), + ( + vec!["hey".to_string(), "hello".to_string(), "".to_string()], + false, + ), + ( + vec!["hey".to_string(), "".to_string(), "world".to_string()], + false, + ), + ( + vec!["hey".to_string(), "".to_string(), "".to_string()], + false, + ), + ( + vec!["".to_string(), "hello".to_string(), "world".to_string()], + true, + ), + ( + vec!["".to_string(), "hello".to_string(), "".to_string()], + false, + ), + ( + vec!["".to_string(), "".to_string(), "world".to_string()], + false, + ), + (vec!["".to_string(), "".to_string(), "".to_string()], false), + ]; + + for (value, ok) in table { + let init = SkipVec::>::new(value.clone()); + assert_eq!(init.is_ok(), ok); + } + Ok(()) } #[test] -fn example_20() -> Result<(), Error>> { +fn example_22() -> Result<(), Error>> { let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?; let ne_vec_deque: NonEmptyVecDeque = ne_vec.into_iter().collect(); assert_eq!(ne_vec_deque.into_value(), vec![1, 2, 3]); @@ -374,7 +492,7 @@ fn example_20() -> Result<(), Error>> { } #[test] -fn example_21() -> Result<(), Error> { +fn example_23() -> Result<(), Error> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -405,7 +523,7 @@ fn example_21() -> Result<(), Error> { } #[test] -fn example_22() -> Result<(), Error>> { +fn example_24() -> Result<(), Error>> { length_greater_than!(5); length_equal!(5, 10); length_less_than!(10); @@ -463,7 +581,7 @@ impl LengthDefinition for Hello { } #[test] -fn example_23() -> Result<(), Error> { +fn example_25() -> Result<(), Error> { length_equal!(5); let hello = Refined::>::new(Hello)?; assert_eq!(hello.into_value(), Hello);