Skip to content

Commit 7076b62

Browse files
authored
Merge pull request #14 from tomoikey/length
Implement Refined Length
2 parents 5ce188c + 01e6a62 commit 7076b62

11 files changed

+676
-35
lines changed

Cargo.toml

+6-6
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ repository = "https://github.com/tomoikey/refined-type"
66
readme = "README.md"
77
categories = ["accessibility", "development-tools", "rust-patterns"]
88
license = "MIT"
9-
version = "0.5.1"
9+
version = "0.5.2"
1010
edition = "2021"
1111

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

1414
[dependencies]
15-
paste = "1.0.14"
16-
regex = "1.10.3"
17-
serde = { version = "1.0.197", features = ["derive"] }
15+
paste = "1.0.15"
16+
regex = "1.10.4"
17+
serde = { version = "1.0.203", features = ["derive"] }
1818

1919
[dev-dependencies]
20-
anyhow = "1.0.80"
21-
serde_json = "1.0.114"
20+
anyhow = "1.0.86"
21+
serde_json = "1.0.117"

README.md

+129-1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ By using Rule Composer, composite rules can be easily created.
107107

108108
```rust
109109
struct ContainsHelloRule;
110+
110111
struct ContainsWorldRule;
111112

112113
impl Rule for ContainsHelloRule {
@@ -195,7 +196,9 @@ Therefore, it can be treated much like a composite function
195196

196197
```rust
197198
struct StartsWithHelloRule;
199+
198200
struct StartsWithByeRule;
201+
199202
struct EndsWithJohnRule;
200203

201204
impl Rule for StartsWithHelloRule {
@@ -320,15 +323,19 @@ type TargetAgeRule = And<TargetAge18OrMore, TargetAge80OrLess>;
320323
```
321324

322325
# Iterator
326+
323327
I have also prepared several useful refined types for Iterators.
328+
324329
## `ForAll`
330+
325331
`ForAll` is a rule that applies a specific rule to all elements in the Iterator.
332+
326333
```rust
327334
fn example_11() -> anyhow::Result<()> {
328335
let vec = vec!["Hello".to_string(), "World".to_string()];
329336
let for_all_ok = ForAll::<NonEmptyStringRule, _>::new(vec.clone())?;
330337
assert_eq!(vec, for_all_ok.into_value());
331-
338+
332339
let vec = vec!["Hello".to_string(), "".to_string()];
333340
let for_all_err = ForAll::<NonEmptyStringRule, _>::new(vec.clone());
334341
assert!(for_all_err.is_err());
@@ -337,7 +344,9 @@ fn example_11() -> anyhow::Result<()> {
337344
```
338345

339346
## `Exists`
347+
340348
`Exists` is a rule that applies a specific rule to at least one element in the Iterator.
349+
341350
```rust
342351
fn example_12() -> anyhow::Result<()> {
343352
let vec = vec!["Hello".to_string(), "".to_string()];
@@ -350,8 +359,11 @@ fn example_12() -> anyhow::Result<()> {
350359
Ok(())
351360
}
352361
```
362+
353363
---
364+
354365
## `into_iter()` and `iter()`
366+
355367
The Iterator I’ve prepared has `into_iter` and `iter` implemented.
356368
Therefore, you can easily map or convert it to a different Iterator using `collect`.
357369
Feel free to explore the capabilities of the Iterator you’ve been given!
@@ -420,6 +432,122 @@ fn example_15() -> anyhow::Result<()> {
420432
}
421433
```
422434

435+
# Length
436+
437+
You can impose constraints on objects that have a length, such as `String` or `Vec`.
438+
439+
### String
440+
441+
```rust
442+
fn example_16() -> Result<(), Error> {
443+
length_greater_than!(5);
444+
length_equal!(5, 10);
445+
length_less_than!(10);
446+
447+
type Password = Refined<From5To10Rule<String>>;
448+
449+
type From5To10Rule<T> = And<
450+
Or<LengthEqualRule5<T>, LengthGreaterThanRule5<T>>,
451+
Or<LengthLessThanRule10<T>, LengthEqualRule10<T>>,
452+
>;
453+
454+
// length is 8. so, this is valid
455+
let raw_password = "password";
456+
let password = Password::new(raw_password.to_string())?;
457+
assert_eq!(password.into_value(), "password");
458+
459+
// length is 4. so, this is invalid
460+
let raw_password = "pswd";
461+
let password = Password::new(raw_password.to_string());
462+
assert!(password.is_err());
463+
464+
// length is 17. so, this is invalid
465+
let raw_password = "password password";
466+
let password = Password::new(raw_password.to_string());
467+
assert!(password.is_err());
468+
469+
Ok(())
470+
}
471+
```
472+
473+
### Vec
474+
475+
```rust
476+
#[test]
477+
fn example_17() -> anyhow::Result<()> {
478+
length_greater_than!(5);
479+
length_equal!(5, 10);
480+
length_less_than!(10);
481+
482+
type Friends = Refined<From5To10Rule<Vec<String>>>;
483+
484+
type From5To10Rule<T> = And<
485+
Or<LengthEqualRule5<T>, LengthGreaterThanRule5<T>>,
486+
Or<LengthLessThanRule10<T>, LengthEqualRule10<T>>,
487+
>;
488+
489+
// length is 6. so, this is valid
490+
let raw_friends = vec![
491+
"Tom".to_string(),
492+
"Taro".to_string(),
493+
"Jiro".to_string(),
494+
"Hanako".to_string(),
495+
"Sachiko".to_string(),
496+
"Yoshiko".to_string(),
497+
];
498+
let friends = Friends::new(raw_friends.clone())?;
499+
assert_eq!(friends.into_value(), raw_friends);
500+
501+
// length is 2. so, this is invalid
502+
let raw_friends = vec!["Tom".to_string(), "Taro".to_string()];
503+
let friends = Friends::new(raw_friends.clone());
504+
assert!(friends.is_err());
505+
506+
// length is 11. so, this is invalid
507+
let raw_friends = vec![
508+
"Tom".to_string(),
509+
"Taro".to_string(),
510+
"Jiro".to_string(),
511+
"Hanako".to_string(),
512+
"Sachiko".to_string(),
513+
"Yuiko".to_string(),
514+
"Taiko".to_string(),
515+
"John".to_string(),
516+
"Jane".to_string(),
517+
"Jack".to_string(),
518+
"Jill".to_string(),
519+
];
520+
let friends = Friends::new(raw_friends.clone());
521+
assert!(friends.is_err());
522+
523+
Ok(())
524+
}
525+
```
526+
527+
### Custom Length
528+
529+
You can define a length for any type. Therefore, if you want to implement a length that is not provided
530+
by `refined_type`, you can easily do so using `LengthDefinition`.
531+
532+
```rust
533+
#[test]
534+
fn example_18() -> anyhow::Result<()> {
535+
length_equal!(5);
536+
537+
#[derive(Debug, PartialEq)]
538+
struct Hello;
539+
impl LengthDefinition for Hello {
540+
fn length(&self) -> usize {
541+
5
542+
}
543+
}
544+
545+
let hello = Refined::<LengthEqualRule5<Hello>>::new(Hello)?;
546+
assert_eq!(hello.into_value(), Hello);
547+
Ok(())
548+
}
549+
```
550+
423551
# Tips
424552

425553
Directly writing `And`, `Or`, `Not` or `Refined` can often lead to a decrease in readability.

src/refined.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use crate::result::Error;
2-
use crate::rule::Rule;
3-
use serde::{Deserialize, Deserializer, Serialize, Serializer};
41
use std::fmt::{Display, Formatter};
52
use std::marker::PhantomData;
6-
use std::ops::Deref;
3+
4+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
5+
6+
use crate::result::Error;
7+
use crate::rule::Rule;
78

89
/// Refined is a versatile type in ensuring that `T` satisfies the conditions of `RULE` (predicate type)
910
/// # Example
@@ -96,11 +97,12 @@ where
9697

9798
#[cfg(test)]
9899
mod test {
100+
use serde::{Deserialize, Serialize};
101+
use serde_json::json;
102+
99103
use crate::refined::Refined;
100104
use crate::result::Error;
101105
use crate::rule::{NonEmptyString, NonEmptyStringRule, NonEmptyVec};
102-
use serde::{Deserialize, Serialize};
103-
use serde_json::json;
104106

105107
#[test]
106108
fn test_unsafe_new_success() {

src/rule.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1+
pub use empty::*;
2+
pub use exists::*;
3+
pub use for_all::*;
4+
pub use length::*;
5+
pub use non_empty::*;
6+
pub use number::*;
7+
pub use string::*;
8+
9+
use crate::result::Error;
10+
111
pub mod composer;
212
mod empty;
313
mod exists;
414
mod for_all;
15+
mod length;
516
mod non_empty;
617
mod number;
718
mod string;
819

9-
use crate::result::Error;
10-
pub use empty::*;
11-
pub use exists::*;
12-
pub use for_all::*;
13-
pub use non_empty::*;
14-
pub use number::*;
15-
pub use string::*;
16-
1720
/// This is a `trait` that specifies the conditions a type `T` should satisfy
1821
pub trait Rule {
1922
type Item;

src/rule/length.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub use length_definition::*;
2+
3+
mod equal;
4+
mod grater;
5+
mod length_definition;
6+
mod less;

src/rule/length/equal.rs

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/// This macro generates a rule that checks if the length of the target is equal to `N`
2+
/// # Example
3+
/// ```rust
4+
/// use refined_type::length_equal;
5+
/// length_equal!(5);
6+
///
7+
/// let target = "12345";
8+
/// let refined = LengthEqual5::new(target).unwrap();
9+
/// assert_eq!(refined.into_value(), "12345");
10+
///
11+
/// let target = "1234";
12+
/// let refined = LengthEqual5::new(target);
13+
/// assert!(refined.is_err());
14+
/// ```
15+
#[macro_export]
16+
macro_rules! length_equal {
17+
($length:literal) => {
18+
paste::item! {
19+
/// A type that holds a value satisfying the LengthEqualN rule.
20+
#[allow(dead_code)]
21+
pub type [<LengthEqual $length>]<ITEM> = $crate::Refined<[<LengthEqualRule $length>]<ITEM>>;
22+
23+
/// Rule where the length of the input value is equal to N
24+
#[allow(dead_code)]
25+
pub struct [<LengthEqualRule $length>]<ITEM> {
26+
_phantom: ::std::marker::PhantomData<ITEM>,
27+
}
28+
29+
impl<ITEM> $crate::rule::Rule for [<LengthEqualRule $length>]<ITEM> where ITEM: $crate::rule::LengthDefinition {
30+
type Item = ITEM;
31+
fn validate(target: &Self::Item) -> Result<(), $crate::result::Error> {
32+
if target.length() == $length {
33+
Ok(())
34+
} else {
35+
Err($crate::result::Error::new(format!("target length is not equal to {}", $length)))
36+
}
37+
}
38+
}
39+
}
40+
};
41+
($length:literal, $($lengths:literal),+) => {
42+
length_equal!($length);
43+
length_equal!($($lengths),+);
44+
};
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use crate::result::Error;
50+
51+
length_equal!(5, 10);
52+
53+
#[test]
54+
fn test_length_equal_5() -> Result<(), Error> {
55+
let target = "12345";
56+
let refined = LengthEqual5::new(target)?;
57+
assert_eq!(refined.into_value(), "12345");
58+
Ok(())
59+
}
60+
61+
#[test]
62+
fn test_length_equal_5_fail() {
63+
let target = "1234";
64+
let refined = LengthEqual5::new(target);
65+
assert!(refined.is_err());
66+
}
67+
68+
#[test]
69+
fn test_length_equal_10() -> Result<(), Error> {
70+
let target = "1234567890";
71+
let refined = LengthEqual10::new(target)?;
72+
assert_eq!(refined.into_value(), "1234567890");
73+
Ok(())
74+
}
75+
76+
#[test]
77+
fn test_length_equal_10_fail() {
78+
let target = "123456789";
79+
let refined = LengthEqual10::new(target);
80+
assert!(refined.is_err());
81+
}
82+
}

0 commit comments

Comments
 (0)