Skip to content

Commit

Permalink
Merge pull request #20 from tomoikey/impl/collection
Browse files Browse the repository at this point in the history
Impl/collection
  • Loading branch information
tomoikey authored Sep 18, 2024
2 parents 912875a + 150602f commit f52145d
Show file tree
Hide file tree
Showing 49 changed files with 1,615 additions and 418 deletions.
237 changes: 199 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down Expand Up @@ -345,6 +345,198 @@ 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::<NonEmptyStringRule>::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::<NonEmptyStringRule>::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<()> {
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::<NonEmptyStringRule>::new(value.clone());
assert_eq!(tail.is_ok(), ok);
}

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<()> {
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::<NonEmptyStringRule>::new(value.clone());
assert_eq!(init.is_ok(), ok);
}

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<()> {
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::<NonEmptyStringRule>::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<Vec<i32>>> {
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::<Index0VecRule<NonEmptyStringRule>, _>::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<Vec<i32>>> {
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::<NonEmptyStringRule, SkipFirst<_>>::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<T> {
_phantom_data: std::marker::PhantomData<T>,
}

impl<ITEM> SkipOption for NoSkip<ITEM> {
type Item = ITEM;
fn should_skip(_: usize, _: &Self::Item) -> bool {
false
}
}
```

---

## `into_iter()` and `iter()`
Expand All @@ -356,7 +548,7 @@ Feel free to explore the capabilities of the Iterator you’ve been given!
### `into_iter()`

```rust
fn example_11() -> anyhow::Result<()> {
fn example_20() -> anyhow::Result<()> {
let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?;
let ne_vec: NonEmptyVec<i32> = ne_vec.into_iter().map(|n| n * 2).map(|n| n * 3).collect();
assert_eq!(ne_vec.into_value(), vec![6, 12, 18]);
Expand All @@ -367,7 +559,7 @@ fn example_11() -> anyhow::Result<()> {
### `iter()`

```rust
fn example_12() -> anyhow::Result<()> {
fn example_21() -> anyhow::Result<()> {
let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?;
let ne_vec: NonEmptyVec<i32> = ne_vec.iter().map(|n| n * 2).map(|n| n * 3).collect();
assert_eq!(ne_vec.into_value(), vec![6, 12, 18]);
Expand All @@ -378,53 +570,22 @@ fn example_12() -> anyhow::Result<()> {
### `NonEmptyVec` to `NonEmptyVecDeque` using `collect()`

```rust
fn example_13() -> anyhow::Result<()> {
fn example_22() -> anyhow::Result<()> {
let ne_vec = NonEmptyVec::new(vec![1, 2, 3])?;
let ne_vec_deque: NonEmptyVecDeque<i32> = ne_vec.into_iter().collect();
assert_eq!(ne_vec_deque.into_value(), vec![1, 2, 3]);
Ok(())
}
```

# 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`.

### String

```rust
fn example_16() -> Result<(), Error> {
fn example_23() -> Result<(), Error> {
length_greater_than!(5);
length_equal!(5, 10);
length_less_than!(10);
Expand Down Expand Up @@ -459,7 +620,7 @@ fn example_16() -> Result<(), Error> {

```rust
#[test]
fn example_17() -> anyhow::Result<()> {
fn example_24() -> anyhow::Result<()> {
length_greater_than!(5);
length_equal!(5, 10);
length_less_than!(10);
Expand Down Expand Up @@ -516,7 +677,7 @@ by `refined_type`, you can easily do so using `LengthDefinition`.

```rust
#[test]
fn example_18() -> anyhow::Result<()> {
fn example_25() -> anyhow::Result<()> {
length_equal!(5);

#[derive(Debug, PartialEq)]
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pub use refined::Refined;
mod refined;
pub mod result;
pub mod rule;

pub use result::Result;
35 changes: 22 additions & 13 deletions src/refined.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -57,13 +56,23 @@ impl<RULE, T> Refined<RULE>
where
RULE: Rule<Item = T>,
{
pub fn new(value: T) -> Result<Self, Error> {
RULE::validate(&value).map_err(|e| Error::new(e.to_string()))?;
pub fn new(value: T) -> Result<Self, Error<T>> {
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 }
}

Expand Down Expand Up @@ -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<String>> {
let non_empty_string = Refined::<NonEmptyStringRule>::new("Hello".to_string())?;
assert_eq!(non_empty_string.value, "Hello");
Ok(())
Expand All @@ -123,14 +132,14 @@ mod test {
}

#[test]
fn test_refined_display() -> Result<(), Error> {
fn test_refined_display() -> Result<(), Error<String>> {
let non_empty_string = Refined::<NonEmptyStringRule>::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<String>> {
let non_empty_string = Refined::<NonEmptyStringRule>::new("hello".to_string())?;

let actual = json!(non_empty_string);
Expand All @@ -140,7 +149,7 @@ mod test {
}

#[test]
fn test_refined_serialize_json_struct() -> anyhow::Result<()> {
fn test_refined_serialize_json_struct() -> Result<(), Error<String>> {
type NonEmptyString = Refined<NonEmptyStringRule>;
#[derive(Serialize)]
struct Human {
Expand Down Expand Up @@ -191,8 +200,8 @@ mod test {
let actual = serde_json::from_str::<Human>(&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);
Expand Down
Loading

0 comments on commit f52145d

Please sign in to comment.