Skip to content

Commit de772f2

Browse files
authored
Merge pull request #12 from tomoikey/for_all
`ForAll` and `Exists` implemention
2 parents c44215b + e70fd44 commit de772f2

27 files changed

+630
-219
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ description = "A library for imbuing rules into types and elevating them to more
44
authors = ["Tomoikey"]
55
repository = "https://github.com/tomoikey/refined-type"
66
readme = "README.md"
7-
categories = ["accessibility", "development-tools"]
7+
categories = ["accessibility", "development-tools", "rust-patterns"]
88
license = "MIT"
9-
version = "0.4.16"
9+
version = "0.5.0"
1010
edition = "2021"
1111

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

README.md

+71-59
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,17 @@ All rules can be arbitrarily combined and extended as long as the target type ma
1616
As an example, let's convert from JSON to a struct.
1717

1818
```rust
19-
use refined_type::Refined;
20-
use refined_type::rule::NonEmptyRule;
21-
use serde::Deserialize;
22-
use serde_json::json;
23-
2419
// define the constraints you expect by combining 'Refined' and 'Rule'.
25-
pub type MyNonEmptyString = Refined<NonEmptyRule<String>>;
26-
pub type MyNonEmptyVec<T> = Refined<NonEmptyRule<Vec<T>>>;
20+
type MyNonEmptyString = Refined<NonEmptyRule<String>>;
21+
type MyNonEmptyVec<T> = Refined<NonEmptyRule<Vec<T>>>;
2722

2823
// define a struct for converting from JSON.
2924
#[derive(Debug, Eq, PartialEq, Deserialize)]
3025
struct Human {
3126
name: MyNonEmptyString,
32-
friends: MyNonEmptyVec<String>
27+
friends: MyNonEmptyVec<String>,
3328
}
3429

35-
// In the first example, both 'name' and 'friends' satisfy the rule, so the conversion from JSON will succeed.
3630
fn example_1() -> anyhow::Result<()> {
3731
let json = json! {{
3832
"name": "john",
@@ -43,7 +37,7 @@ fn example_1() -> anyhow::Result<()> {
4337
let actual = serde_json::from_str::<Human>(&json)?;
4438
let expected = Human {
4539
name: MyNonEmptyString::new("john".to_string())?,
46-
friends: MyNonEmptyVec::new(vec!["tom".to_string(), "taro".to_string()])?
40+
friends: MyNonEmptyVec::new(vec!["tom".to_string(), "taro".to_string()])?,
4741
};
4842
assert_eq!(actual, expected);
4943
Ok(())
@@ -91,7 +85,7 @@ Once the definition is complete, all you need to do is implement the Rule trait.
9185
Add your preferred conditions as you like.
9286

9387
```rust
94-
fn main() {
88+
fn example_4() {
9589
let non_empty_string_result = Refined::<NonEmptyStringRule>::new("Hello World".to_string());
9690
assert_eq!(non_empty_string_result.unwrap().deref(), "Hello World");
9791

@@ -112,15 +106,14 @@ By using Rule Composer, composite rules can be easily created.
112106

113107
```rust
114108
struct ContainsHelloRule;
115-
116109
struct ContainsWorldRule;
117110

118111
impl Rule for ContainsHelloRule {
119112
type Item = String;
120113

121-
fn validate(target: Self::Item) -> Result<Self::Item, Error<Self::Item>> {
114+
fn validate(target: &Self::Item) -> Result<(), Error> {
122115
if target.contains("Hello") {
123-
Ok(target)
116+
Ok(())
124117
} else {
125118
Err(Error::new(format!("{} does not contain `Hello`", target)))
126119
}
@@ -130,9 +123,9 @@ impl Rule for ContainsHelloRule {
130123
impl Rule for ContainsWorldRule {
131124
type Item = String;
132125

133-
fn validate(target: Self::Item) -> Result<Self::Item, Error<Self::Item>> {
126+
fn validate(target: &Self::Item) -> Result<(), Error> {
134127
if target.contains("World") {
135-
Ok(target)
128+
Ok(())
136129
} else {
137130
Err(Error::new(format!("{} does not contain `World`", target)))
138131
}
@@ -146,7 +139,7 @@ impl Rule for ContainsWorldRule {
146139
It is generally effective when you want to narrow down the condition range.
147140

148141
```rust
149-
fn main() {
142+
fn example_5() {
150143
type HelloAndWorldRule = And<ContainsHelloRule, ContainsWorldRule>;
151144

152145
let rule_ok = Refined::<HelloAndWorldRule>::new("Hello! World!".to_string());
@@ -163,7 +156,7 @@ fn main() {
163156
It is generally effective when you want to expand the condition range.
164157

165158
```rust
166-
fn main() {
159+
fn example_6() {
167160
type HelloOrWorldRule = Or<ContainsHelloRule, ContainsWorldRule>;
168161

169162
let rule_ok_1 = Refined::<HelloOrWorldRule>::new("Hello! World!".to_string());
@@ -183,7 +176,7 @@ fn main() {
183176
It is generally effective when you want to discard only certain situations.
184177

185178
```rust
186-
fn main() {
179+
fn example_7() {
187180
type NotHelloRule = Not<ContainsHelloRule>;
188181

189182
let rule_ok = Refined::<NotHelloRule>::new("hello! World!".to_string());
@@ -200,18 +193,16 @@ Rule Composer is also a rule.
200193
Therefore, it can be treated much like a composite function
201194

202195
```rust
203-
struct StartWithHelloRule;
204-
205-
struct StartWithByeRule;
206-
207-
struct EndWithJohnRule;
196+
struct StartsWithHelloRule;
197+
struct StartsWithByeRule;
198+
struct EndsWithJohnRule;
208199

209200
impl Rule for StartsWithHelloRule {
210201
type Item = String;
211202

212-
fn validate(target: Self::Item) -> Result<Self::Item, Error<Self::Item>> {
203+
fn validate(target: &Self::Item) -> Result<(), Error> {
213204
if target.starts_with("Hello") {
214-
Ok(target)
205+
Ok(())
215206
} else {
216207
Err(Error::new(format!("{} does not start with `Hello`", target)))
217208
}
@@ -221,34 +212,34 @@ impl Rule for StartsWithHelloRule {
221212
impl Rule for StartsWithByeRule {
222213
type Item = String;
223214

224-
fn validate(target: Self::Item) -> Result<Self::Item, Error<Self::Item>> {
215+
fn validate(target: &Self::Item) -> Result<(), Error> {
225216
if target.starts_with("Bye") {
226-
Ok(target)
217+
Ok(())
227218
} else {
228219
Err(Error::new(format!("{} does not start with `Bye`", target)))
229220
}
230221
}
231222
}
232223

233-
impl Rule for EndWithJohnRule {
224+
impl Rule for EndsWithJohnRule {
234225
type Item = String;
235226

236-
fn validate(target: Self::Item) -> Result<Self::Item, Error<Self::Item>> {
227+
fn validate(target: &Self::Item) -> Result<(), Error> {
237228
if target.ends_with("John") {
238-
Ok(target)
229+
Ok(())
239230
} else {
240231
Err(Error::new(format!("{} does not end with `John`", target)))
241232
}
242233
}
243234
}
244235

245-
fn main() {
246-
type GreetingRule = And<Or<StartWithHelloRule, StartWithByeRule>, EndWithJohnRule>;
236+
fn example_8() {
237+
type GreetingRule = And<Or<StartsWithHelloRule, StartsWithByeRule>, EndsWithJohnRule>;
247238

248-
assert!(GreetingRule::validate("Hello! Nice to meet you John".to_string()).is_ok());
249-
assert!(GreetingRule::validate("Bye! Have a good day John".to_string()).is_ok());
250-
assert!(GreetingRule::validate("How are you? Have a good day John".to_string()).is_err());
251-
assert!(GreetingRule::validate("Bye! Have a good day Tom".to_string()).is_err());
239+
assert!(GreetingRule::validate(&"Hello! Nice to meet you John".to_string()).is_ok());
240+
assert!(GreetingRule::validate(&"Bye! Have a good day John".to_string()).is_ok());
241+
assert!(GreetingRule::validate(&"How are you? Have a good day John".to_string()).is_err());
242+
assert!(GreetingRule::validate(&"Bye! Have a good day Tom".to_string()).is_err());
252243
}
253244
```
254245

@@ -261,16 +252,14 @@ and `Deserialize`.
261252
### Serialize
262253

263254
```rust
264-
type NonEmptyString = Refined<NonEmptyStringRule>;
265-
266-
#[derive(Serialize)]
267-
struct Human {
255+
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
256+
struct Human2 {
268257
name: NonEmptyString,
269258
age: u8,
270259
}
271260

272-
fn main() -> anyhow::Result<()> {
273-
let john = Human {
261+
fn example_9() -> anyhow::Result<()> {
262+
let john = Human2 {
274263
name: NonEmptyString::new("john".to_string())?,
275264
age: 8,
276265
};
@@ -288,24 +277,16 @@ fn main() -> anyhow::Result<()> {
288277
### Deserialize
289278

290279
```rust
291-
type NonEmptyString = Refined<NonEmptyStringRule>;
292-
293-
#[derive(Debug, Eq, PartialEq, Deserialize)]
294-
struct Human {
295-
name: NonEmptyString,
296-
age: u8,
297-
}
298-
299-
fn main() -> anyhow::Result<()> {
280+
fn example_10() -> anyhow::Result<()> {
300281
let json = json! {{
301282
"name": "john",
302283
"age": 8
303284
}}
304285
.to_string();
305286

306-
let actual = serde_json::from_str::<Human>(&json)?;
287+
let actual = serde_json::from_str::<Human2>(&json)?;
307288

308-
let expected = Human {
289+
let expected = Human2 {
309290
name: NonEmptyString::new("john".to_string())?,
310291
age: 8,
311292
};
@@ -338,15 +319,46 @@ type TargetAgeRule = And<TargetAge18OrMore, TargetAge80OrLess>;
338319
```
339320

340321
# Iterator
322+
I have also prepared several useful refined types for Iterators.
323+
## `ForAll`
324+
`ForAll` is a rule that applies a specific rule to all elements in the Iterator.
325+
```rust
326+
fn example_11() -> anyhow::Result<()> {
327+
let vec = vec!["Hello".to_string(), "World".to_string()];
328+
let for_all_ok = ForAll::<NonEmptyStringRule, _>::new(vec.clone())?;
329+
assert_eq!(vec, for_all_ok.into_value());
330+
331+
let vec = vec!["Hello".to_string(), "".to_string()];
332+
let for_all_err = ForAll::<NonEmptyStringRule, _>::new(vec.clone());
333+
assert!(for_all_err.is_err());
334+
Ok(())
335+
}
336+
```
341337

338+
## `Exists`
339+
`Exists` is a rule that applies a specific rule to at least one element in the Iterator.
340+
```rust
341+
fn example_12() -> anyhow::Result<()> {
342+
let vec = vec!["Hello".to_string(), "".to_string()];
343+
let exists_ok = Exists::<NonEmptyStringRule, _>::new(vec.clone())?;
344+
assert_eq!(vec, exists_ok.into_value());
345+
346+
let vec = vec!["".to_string(), "".to_string()];
347+
let exists_err = Exists::<NonEmptyStringRule, _>::new(vec.clone());
348+
assert!(exists_err.is_err());
349+
Ok(())
350+
}
351+
```
352+
---
353+
## `into_iter()` and `iter()`
342354
The Iterator I’ve prepared has `into_iter` and `iter` implemented.
343355
Therefore, you can easily map or convert it to a different Iterator using `collect`.
344356
Feel free to explore the capabilities of the Iterator you’ve been given!
345357

346358
### `into_iter()`
347359

348360
```rust
349-
fn main() -> anyhow::Result<()> {
361+
fn example_11() -> anyhow::Result<()> {
350362
let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?;
351363
let ne_vec: NonEmptyVec<i32> = ne_vec.into_iter().map(|n| n * 2).map(|n| n * 3).collect();
352364
assert_eq!(ne_vec.into_value(), vec![6, 12, 18]);
@@ -357,7 +369,7 @@ fn main() -> anyhow::Result<()> {
357369
### `iter()`
358370

359371
```rust
360-
fn main() -> anyhow::Result<()> {
372+
fn example_12() -> anyhow::Result<()> {
361373
let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?;
362374
let ne_vec: NonEmptyVec<i32> = ne_vec.iter().map(|n| n * 2).map(|n| n * 3).collect();
363375
assert_eq!(ne_vec.into_value(), vec![6, 12, 18]);
@@ -368,7 +380,7 @@ fn main() -> anyhow::Result<()> {
368380
### `NonEmptyVec` to `NonEmptyVecDeque` using `collect()`
369381

370382
```rust
371-
fn main() -> anyhow::Result<()> {
383+
fn example_13() -> anyhow::Result<()> {
372384
let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?;
373385
let ne_vec_deque: NonEmptyVecDeque<i32> = ne_vec.into_iter().collect();
374386
assert_eq!(ne_vec_deque.into_value(), vec![1, 2, 3]);
@@ -384,7 +396,7 @@ without downgrading the type level.
384396
### NonEmptyString
385397

386398
```rust
387-
fn main() -> anyhow::Result<()> {
399+
fn example_14() -> anyhow::Result<()> {
388400
let non_empty_string_1 = NonEmptyString::new("Hello".to_string())?;
389401
let non_empty_string_2 = NonEmptyString::new("World".to_string())?;
390402
let non_empty_string = non_empty_string_1 + non_empty_string_2; // This is also `NonEmptyString` type
@@ -397,7 +409,7 @@ fn main() -> anyhow::Result<()> {
397409
### NonEmptyVec
398410

399411
```rust
400-
fn main() -> anyhow::Result<()> {
412+
fn example_15() -> anyhow::Result<()> {
401413
let ne_vec_1 = NonEmptyVec::new(vec![1, 2, 3])?;
402414
let ne_vec_2 = NonEmptyVec::new(vec![4, 5, 6])?;
403415
let ne_vec = ne_vec_1 + ne_vec_2; // This is also `NonEmptyVec` type

src/refined.rs

+19-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::rule::Rule;
33
use serde::{Deserialize, Deserializer, Serialize, Serializer};
44
use std::fmt::{Display, Formatter};
55
use std::marker::PhantomData;
6+
use std::ops::Deref;
67

78
/// Refined is a versatile type in ensuring that `T` satisfies the conditions of `RULE` (predicate type)
89
/// # Example
@@ -58,18 +59,18 @@ impl<RULE, T> Refined<RULE>
5859
where
5960
RULE: Rule<Item = T>,
6061
{
61-
pub fn new(value: T) -> Result<Self, Error<T>> {
62+
pub fn new(value: T) -> Result<Self, Error> {
63+
RULE::validate(&value).map_err(|e| Error::new(e.to_string()))?;
6264
Ok(Self {
63-
value: RULE::validate(value)?,
65+
value,
6466
_rule: Default::default(),
6567
})
6668
}
6769

6870
pub fn unsafe_new(value: T) -> Self {
71+
RULE::validate(&value).expect("initialization by `unsafe_new` failed");
6972
Self {
70-
value: RULE::validate(value)
71-
.ok()
72-
.expect("initialization by `unsafe_new` failed"),
73+
value,
7374
_rule: Default::default(),
7475
}
7576
}
@@ -93,6 +94,17 @@ where
9394
}
9495
}
9596

97+
impl<RULE> Deref for Refined<RULE>
98+
where
99+
RULE: Rule,
100+
{
101+
type Target = RULE::Item;
102+
103+
fn deref(&self) -> &Self::Target {
104+
&self.value
105+
}
106+
}
107+
96108
#[cfg(test)]
97109
mod test {
98110
use crate::refined::Refined;
@@ -115,7 +127,7 @@ mod test {
115127
}
116128

117129
#[test]
118-
fn test_refined_non_empty_string_ok() -> Result<(), Error<String>> {
130+
fn test_refined_non_empty_string_ok() -> Result<(), Error> {
119131
let non_empty_string = Refined::<NonEmptyStringRule>::new("Hello".to_string())?;
120132
assert_eq!(non_empty_string.value, "Hello");
121133
Ok(())
@@ -129,7 +141,7 @@ mod test {
129141
}
130142

131143
#[test]
132-
fn test_refined_display() -> Result<(), Error<String>> {
144+
fn test_refined_display() -> Result<(), Error> {
133145
let non_empty_string = Refined::<NonEmptyStringRule>::new("Hello".to_string())?;
134146
assert_eq!(format!("{}", non_empty_string), "Hello");
135147
Ok(())

0 commit comments

Comments
 (0)