Skip to content

Commit

Permalink
fix rule signature
Browse files Browse the repository at this point in the history
  • Loading branch information
tomoikey committed Sep 17, 2024
1 parent b4859dd commit 44eb1b6
Show file tree
Hide file tree
Showing 38 changed files with 592 additions and 354 deletions.
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
19 changes: 13 additions & 6 deletions src/result.rs
Original file line number Diff line number Diff line change
@@ -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<T> = std::result::Result<T, Error<T>>;

/// A type indicating a failure to convert to `Refined`
#[derive(Debug)]
pub struct Error {
pub struct Error<T> {
value: T,
message: String,
}

impl std::error::Error for Error {}

impl Error {
pub fn new(message: impl Into<String>) -> Self {
impl<T> Error<T> {
pub fn new(value: T, message: impl Into<String>) -> Self {
Self {
value,
message: message.into(),
}
}

pub fn into_value(self) -> T {
self.value
}
}

impl Display for Error {
impl<T> Display for Error<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
Expand Down
4 changes: 1 addition & 3 deletions src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Self::Item>;
}
3 changes: 2 additions & 1 deletion src/rule/collection/exists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ pub type ExistsStringRule<RULE> = ExistsRule<RULE, String>;

#[cfg(test)]
mod tests {
use crate::result::Error;
use crate::rule::{Exists, NonEmptyStringRule};

#[test]
fn exists_1() -> anyhow::Result<()> {
fn exists_1() -> Result<(), Error<Vec<String>>> {
let value = vec!["good morning".to_string(), "hello".to_string()];
let exists: Exists<NonEmptyStringRule, Vec<_>> = Exists::new(value.clone())?;
assert_eq!(exists.into_value(), value);
Expand Down
10 changes: 5 additions & 5 deletions src/rule/collection/for_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<String>>> {
let value = vec!["good morning".to_string(), "hello".to_string()];
let for_all: ForAllVec<NonEmptyStringRule> = ForAll::new(value.clone())?;
assert_eq!(for_all.into_value(), value);
Expand All @@ -62,16 +62,16 @@ mod tests {
}

#[test]
fn for_all_3() -> anyhow::Result<()> {
fn for_all_3() -> Result<(), Error<String>> {
struct CharRule;
impl Rule for CharRule {
type Item = char;

fn validate(target: &Self::Item) -> Result<(), Error> {
fn validate(target: Self::Item) -> Result<Self::Item, Error<Self::Item>> {
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)))
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/rule/collection/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ pub type HeadStringRule<RULE> = HeadRule<RULE, String>;

#[cfg(test)]
mod tests {
use crate::result::Error;
use crate::rule::{HeadVec, NonEmptyStringRule};

#[test]
fn head_valid() -> anyhow::Result<()> {
fn head_valid() -> Result<(), Error<Vec<String>>> {
let table = vec![
vec!["good morning".to_string(), "".to_string()],
vec!["hello".to_string(), "hello".to_string()],
Expand Down
72 changes: 49 additions & 23 deletions src/rule/collection/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,22 @@ macro_rules! define_index_rule {
impl <RULE, ITEM> $crate::rule::Rule for [<Index $lit Rule>]<RULE, Vec<ITEM>> where RULE: $crate::rule::Rule<Item = ITEM> {
type Item = Vec<ITEM>;

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<Self::Item, $crate::result::Error<Self::Item>> {
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)))
}
}
}
}
Expand All @@ -50,12 +60,20 @@ macro_rules! define_index_rule {
impl <RULE, ITEM> $crate::rule::Rule for [<Index $lit Rule>]<RULE, ::std::collections::VecDeque<ITEM>> where RULE: $crate::rule::Rule<Item = ITEM> {
type Item = ::std::collections::VecDeque<ITEM>;

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<Self::Item, $crate::result::Error<Self::Item>> {
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)))
}
}
}
}
Expand All @@ -64,25 +82,33 @@ macro_rules! define_index_rule {
impl <RULE> $crate::rule::Rule for [<Index $lit Rule>]<RULE, String> where RULE: $crate::rule::Rule<Item = char> {
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<Self::Item, $crate::result::Error<Self::Item>> {
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)))
}
}
}
}

impl <'a, RULE> $crate::rule::Rule for [<Index $lit Rule>]<RULE, &'a str> where RULE: $crate::rule::Rule<Item = char> {
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<Self::Item, $crate::result::Error<Self::Item>> {
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)))
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/rule/collection/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ pub type InitStringRule<RULE> = InitRule<RULE, String>;

#[cfg(test)]
mod tests {
use crate::result::Error;
use crate::rule::{InitVec, NonEmptyStringRule};

#[test]
fn init_valid() -> anyhow::Result<()> {
fn init_valid() -> Result<(), Error<Vec<String>>> {
let table = vec![
vec![
"hello".to_string(),
Expand Down
Loading

0 comments on commit 44eb1b6

Please sign in to comment.