#rule #non-empty #refined #type #string #target #define

refined_type

一个将规则注入类型并提升类型鲁棒性的库

22 个版本

0.5.5 2024 年 8 月 13 日
0.5.4 2024 年 5 月 29 日
0.4.16 2024 年 4 月 3 日
0.4.14 2024 年 3 月 31 日
0.1.5 2023 年 11 月 12 日

#174 in Rust 模式

Download history 13/week @ 2024-05-20 680/week @ 2024-05-27 24/week @ 2024-06-03 6/week @ 2024-06-10 123/week @ 2024-08-12

123 每月下载量

MIT 许可证

90KB
2K SLoC

Refined-Type

refined-type 是一个为 Rust 开发的库。它增强了你的类型,使它们更加鲁棒,并扩展了你的应用程序可以静态确保的范围。

概述

你可以为某些类型创建各种规则,例如电话号码、地址、时间等。一旦你建立了规则,你可以轻松地将它们组合起来。具体来说,如果你为“非空字符串”和“仅由字母组成的字符串”创建了规则,你不需要为“仅由字母组成的非空字符串”重新定义一个新的规则。只要目标类型匹配,所有规则都可以任意组合和扩展。享受美好的类型生活!

示例用法

例如,让我们将 JSON 转换为结构体。

// define the constraints you expect by combining 'Refined' and 'Rule'.
type MyNonEmptyString = Refined<NonEmptyRule<String>>;
type MyNonEmptyVec<T> = Refined<NonEmptyRule<Vec<T>>>;

// define a struct for converting from JSON.
#[derive(Debug, Eq, PartialEq, Deserialize)]
struct Human {
    name: MyNonEmptyString,
    friends: MyNonEmptyVec<String>,
}

fn example_1() -> anyhow::Result<()> {
    let json = json! {{
        "name": "john",
        "friends": ["tom", "taro"]
    }}
        .to_string();

    let actual = serde_json::from_str::<Human>(&json)?;
    let expected = Human {
        name: MyNonEmptyString::new("john".to_string())?,
        friends: MyNonEmptyVec::new(vec!["tom".to_string(), "taro".to_string()])?,
    };
    assert_eq!(actual, expected);
    Ok(())
}

// In the second example, while `friends` meets the rule, `name` does not, causing the conversion from JSON to fail
fn example_2() -> anyhow::Result<()> {
    let json = json! {{
        "name": "",
        "friends": ["tom", "taro"]
    }}
        .to_string();

    // because `name` is empty
    assert!(serde_json::from_str::<Human>(&json).is_err());
    Ok(())
}

// In the third example, while `name` satisfies the rule, `friends` does not, causing the conversion from JSON to fail.
fn example_3() -> anyhow::Result<()> {
    let json = json! {{
        "name": "john",
        "friends": []
    }}
        .to_string();

    // because `friends` is empty
    assert!(serde_json::from_str::<Human>(&json).is_err());
    Ok(())
}

安装

cargo add refined_type

自定义规则

有许多情况你可能想要定义自定义规则。为了为特定目标类型定义规则,你首先需要定义一个结构体。在结构体中,定义用于指定详细条件的字段。一旦定义完成,你需要实现 Rule 特性。你可以像喜欢的那样添加你喜欢的条件。

fn example_4() -> anyhow::Result<()> {
    let non_empty_string_result = Refined::<NonEmptyStringRule>::new("Hello World".to_string())?;
    assert_eq!(non_empty_string_result.into_value(), "Hello World");

    let empty_string_result = Refined::<NonEmptyStringRule>::new("".to_string());
    assert!(empty_string_result.is_err())
    Ok(())
}

组合规则

如前所述,只要目标类型匹配,就可以组合任何规则。在下面的示例中,有针对“包含 Hello 的字符串”和“包含 World 的字符串”的独立规则。由于它们的目标类型是 String,因此可以组合。我已经准备了一个名为 Rule Composer(AndOrNot)的东西。通过使用 Rule Composer,可以轻松创建组合规则。

原始规则

struct ContainsHelloRule;

struct ContainsWorldRule;

impl Rule for ContainsHelloRule {
    type Item = String;

    fn validate(target: &Self::Item) -> Result<(), Error> {
        if target.contains("Hello") {
            Ok(())
        } else {
            Err(Error::new(format!("{} does not contain `Hello`", target)))
        }
    }
}

impl Rule for ContainsWorldRule {
    type Item = String;

    fn validate(target: &Self::Item) -> Result<(), Error> {
        if target.contains("World") {
            Ok(())
        } else {
            Err(Error::new(format!("{} does not contain `World`", target)))
        }
    }
}

1: And 规则组合器

And 规则组合器是一个满足两个规则之一的规则。当你想要缩小条件范围时,通常很有效。

fn example_5() {
    type HelloAndWorldRule = And<ContainsHelloRule, ContainsWorldRule>;

    let rule_ok = Refined::<HelloAndWorldRule>::new("Hello! World!".to_string());
    assert!(rule_ok.is_ok());

    let rule_err = Refined::<HelloAndWorldRule>::new("Hello, world!".to_string());
    assert!(rule_err.is_err());
}

2: Or 规则组合器

Or 规则组合器是一个满足两个规则之一的规则。当你想要扩大条件范围时,通常很有效。

fn example_6() {
    type HelloOrWorldRule = Or<ContainsHelloRule, ContainsWorldRule>;

    let rule_ok_1 = Refined::<HelloOrWorldRule>::new("Hello! World!".to_string());
    assert!(rule_ok_1.is_ok());

    let rule_ok_2 = Refined::<HelloOrWorldRule>::new("hello World!".to_string());
    assert!(rule_ok_2.is_ok());

    let rule_err = Refined::<HelloOrWorldRule>::new("hello, world!".to_string());
    assert!(rule_err.is_err());
}

3: Not 规则组合器

Not 规则合成器是一个不满足特定条件的规则。当你只想排除某些情况时,它通常很有效。

fn example_7() {
    type NotHelloRule = Not<ContainsHelloRule>;

    let rule_ok = Refined::<NotHelloRule>::new("hello! World!".to_string());
    assert!(rule_ok.is_ok());

    let rule_err = Refined::<NotHelloRule>::new("Hello, World!".to_string());
    assert!(rule_err.is_err());
}

4: 合成规则合成器

规则合成器也是一个规则。因此,它可以被处理得就像一个组合函数一样。

struct StartsWithHelloRule;

struct StartsWithByeRule;

struct EndsWithJohnRule;

impl Rule for StartsWithHelloRule {
    type Item = String;

    fn validate(target: &Self::Item) -> Result<(), Error> {
        if target.starts_with("Hello") {
            Ok(())
        } else {
            Err(Error::new(format!("{} does not start with `Hello`", target)))
        }
    }
}

impl Rule for StartsWithByeRule {
    type Item = String;

    fn validate(target: &Self::Item) -> Result<(), Error> {
        if target.starts_with("Bye") {
            Ok(())
        } else {
            Err(Error::new(format!("{} does not start with `Bye`", target)))
        }
    }
}

impl Rule for EndsWithJohnRule {
    type Item = String;

    fn validate(target: &Self::Item) -> Result<(), Error> {
        if target.ends_with("John") {
            Ok(())
        } else {
            Err(Error::new(format!("{} does not end with `John`", target)))
        }
    }
}

fn example_8() {
    type GreetingRule = And<Or<StartsWithHelloRule, StartsWithByeRule>, 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());
}

JSON

refined_typeserde_json 兼容。这确保了类型安全的通信,并消除了编写新验证过程的需要。你所要做的就是实现一套规则,并实现 serdeSerializeDeserialize

序列化

#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
struct Human2 {
    name: NonEmptyString,
    age: u8,
}

fn example_9() -> anyhow::Result<()> {
    let john = Human2 {
        name: NonEmptyString::new("john".to_string())?,
        age: 8,
    };

    let actual = json!(john);
    let expected = json! {{
        "name": "john",
        "age": 8
    }};
    assert_eq!(actual, expected);
    Ok(())
}

反序列化

fn example_10() -> anyhow::Result<()> {
    let json = json! {{
        "name": "john",
        "age": 8
    }}
        .to_string();

    let actual = serde_json::from_str::<Human2>(&json)?;

    let expected = Human2 {
        name: NonEmptyString::new("john".to_string())?,
        age: 8,
    };
    assert_eq!(actual, expected);
    Ok(())
}

数字

你还可以用类型来表示数字的大小。我已经准备了一些宏,可以轻松地定义数字的大小。让我们使用它们来定义一个 Age 类型,该类型被限制在 18 到 80 岁之间。

greater_rule!((18, u8));
less_rule!((80, u8));
equal_rule!((18, u8), (80, u8));

type Age = Refined<TargetAgeRule>;

// 18 <= age
type TargetAge18OrMore = Or<EqualRule18u8, GreaterRule18u8>;

// age <= 80
type TargetAge80OrLess = Or<EqualRule80u8, LessRule80u8>;

// 18 <= age <= 80
type TargetAgeRule = And<TargetAge18OrMore, TargetAge80OrLess>;

迭代器

我还准备了几种有用的精炼类型用于迭代器。

ForAll

ForAll 是一个将特定规则应用于迭代器中所有元素的规则。

fn example_11() -> anyhow::Result<()> {
    let vec = vec!["Hello".to_string(), "World".to_string()];
    let for_all_ok = ForAll::<NonEmptyStringRule, _>::new(vec.clone())?;
    assert_eq!(vec, for_all_ok.into_value());

    let vec = vec!["Hello".to_string(), "".to_string()];
    let for_all_err = ForAll::<NonEmptyStringRule, _>::new(vec.clone());
    assert!(for_all_err.is_err());
    Ok(())
}

Exists

Exists 是一个将特定规则应用于迭代器中至少一个元素的规则。

fn example_12() -> anyhow::Result<()> {
    let vec = vec!["Hello".to_string(), "".to_string()];
    let exists_ok = Exists::<NonEmptyStringRule, _>::new(vec.clone())?;
    assert_eq!(vec, exists_ok.into_value());

    let vec = vec!["".to_string(), "".to_string()];
    let exists_err = Exists::<NonEmptyStringRule, _>::new(vec.clone());
    assert!(exists_err.is_err());
    Ok(())
}

into_iter()iter()

我准备的迭代器实现了 into_iteriter。因此,你可以通过使用 collect 来轻松地将它映射或转换为不同的迭代器。尽情探索你获得的迭代器的功能吧!

into_iter()

fn example_11() -> 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]);
    Ok(())
}

iter()

fn example_12() -> 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]);
    Ok(())
}

使用 collect()NonEmptyVec 转换为 NonEmptyVecDeque

fn example_13() -> 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(())
}

添加特质

我已经为提供的部分 Refined 实现了 Add 特质。因此,可以在不降低类型级别的条件下执行操作。

NonEmptyString

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

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(())
}

长度

你可以对具有长度的对象施加约束,例如 StringVec

字符串

fn example_16() -> Result<(), Error> {
    length_greater_than!(5);
    length_equal!(5, 10);
    length_less_than!(10);

    type Password = Refined<From5To10Rule<String>>;

    type From5To10Rule<T> = And<
        Or<LengthEqualRule5<T>, LengthGreaterThanRule5<T>>,
        Or<LengthLessThanRule10<T>, LengthEqualRule10<T>>,
    >;

    // length is 8. so, this is valid
    let raw_password = "password";
    let password = Password::new(raw_password.to_string())?;
    assert_eq!(password.into_value(), "password");

    // length is 4. so, this is invalid
    let raw_password = "pswd";
    let password = Password::new(raw_password.to_string());
    assert!(password.is_err());

    // length is 17. so, this is invalid
    let raw_password = "password password";
    let password = Password::new(raw_password.to_string());
    assert!(password.is_err());

    Ok(())
}

向量

#[test]
fn example_17() -> anyhow::Result<()> {
    length_greater_than!(5);
    length_equal!(5, 10);
    length_less_than!(10);

    type Friends = Refined<From5To10Rule<Vec<String>>>;

    type From5To10Rule<T> = And<
        Or<LengthEqualRule5<T>, LengthGreaterThanRule5<T>>,
        Or<LengthLessThanRule10<T>, LengthEqualRule10<T>>,
    >;

    // length is 6. so, this is valid
    let raw_friends = vec![
        "Tom".to_string(),
        "Taro".to_string(),
        "Jiro".to_string(),
        "Hanako".to_string(),
        "Sachiko".to_string(),
        "Yoshiko".to_string(),
    ];
    let friends = Friends::new(raw_friends.clone())?;
    assert_eq!(friends.into_value(), raw_friends);

    // length is 2. so, this is invalid
    let raw_friends = vec!["Tom".to_string(), "Taro".to_string()];
    let friends = Friends::new(raw_friends.clone());
    assert!(friends.is_err());

    // length is 11. so, this is invalid
    let raw_friends = vec![
        "Tom".to_string(),
        "Taro".to_string(),
        "Jiro".to_string(),
        "Hanako".to_string(),
        "Sachiko".to_string(),
        "Yuiko".to_string(),
        "Taiko".to_string(),
        "John".to_string(),
        "Jane".to_string(),
        "Jack".to_string(),
        "Jill".to_string(),
    ];
    let friends = Friends::new(raw_friends.clone());
    assert!(friends.is_err());

    Ok(())
}

自定义长度

你可以为任何类型定义一个长度。因此,如果你想实现 refined_type 未提供的长度,你可以轻松地使用 LengthDefinition 来实现。

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

    #[derive(Debug, PartialEq)]
    struct Hello;
    impl LengthDefinition for Hello {
        fn length(&self) -> usize {
            5
        }
    }

    let hello = Refined::<LengthEqualRule5<Hello>>::new(Hello)?;
    assert_eq!(hello.into_value(), Hello);
    Ok(())
}

A!(与), O!(或) 宏

AndOr 有时会结合多个规则。但是,每次都定义深层嵌套类型并不是一个理想的方法。
因此,我定义了 A! 宏和 O! 宏,这样即使组合三个或更多的规则,代码也可以简洁地编写。
用法如下

fn example_19() -> anyhow::Result<()> {
    type Sample = Refined<A![ContainsHelloRule, ContainsCommaRule, ContainsHelloRule]>;

    let sample = Sample::new("Hello, World!".to_string())?;
    assert_eq!(sample.into_value(), "Hello, World!");

    let sample = Sample::new("Hello World!".to_string());
    assert!(sample.is_err());
    Ok(())
}
fn example_20() -> anyhow::Result<()> {
    type Sample = Refined<O![ContainsHelloRule, ContainsCommaRule, ContainsWorldRule]>;

    let sample = Sample::new("Foo! World!".to_string())?;
    assert_eq!(sample.into_value(), "Foo! World!");

    let sample = Sample::new("Hello World!".to_string())?;
    assert_eq!(sample.into_value(), "Hello World!");

    let sample = Sample::new("World!".to_string())?;
    assert_eq!(sample.into_value(), "World!");

    let sample = Sample::new("".to_string());
    assert!(sample.is_err());
    Ok(())
}

提示

直接编写 AndOrNotRefined 往往会导致可读性降低。因此,使用 类型别名 可以帮助使你的代码更清晰。

type ContainsHelloAndWorldRule = And<ContainsHelloRule, ContainsWorldRule>;

type ContainsHelloAndWorld = Refined<ContainsHelloAndWorldRule>;

许可

MIT 许可证

版权所有(c)2024 汤木希也

本文件授予任何人免费获得本软件及其相关文档文件(以下简称“软件”)的副本的权利,允许在不作限制的情况下处理软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件的副本,并允许将软件提供给他人以进行此类操作,但需遵守以下条件:

上述版权声明和本许可声明应包含在软件的所有副本或实质性部分中。

软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、适用于特定目的和不受侵犯的保证。在任何情况下,作者或版权所有者不对任何索赔、损害或其他责任(无论是基于合同、侵权或其他方式)承担责任,这些索赔、损害或其他责任源于、产生于或与软件或其使用或其他操作有关。

依赖项

~2.5–4MB
~76K SLoC