13 个版本
0.4.3 | 2024年7月6日 |
---|---|
0.4.2 | 2024年4月7日 |
0.4.1-beta.1 | 2024年1月28日 |
0.4.0 | 2023年11月20日 |
0.1.0 | 2022年10月3日 |
#56 in Rust 模式
221,836 每月下载量
用于 28 个 包(直接使用 13 个)
62KB
633 行
带有保证的新类型。
Nutype 是一个过程宏,允许向常规的 新类型模式 添加额外的约束,例如 净化 和 验证。生成的代码使得在没有通过检查的情况下无法实例化值。即使与 serde
反序列化一起使用也是如此。
快速开始
use nutype::nutype;
// Define newtype Username
#[nutype(
sanitize(trim, lowercase),
validate(not_empty, len_char_max = 20),
derive(Debug, PartialEq, Clone),
)]
pub struct Username(String);
// We can obtain a value of Username with `::try_new()`.
// Note that Username holds a sanitized string
assert_eq!(
Username::try_new(" FooBar ").unwrap().into_inner(),
"foobar"
);
// It's impossible to obtain an invalid Username
// Note that we also got `UsernameError` enum generated implicitly
// based on the validation rules.
assert_eq!(
Username::try_new(" "),
Err(UsernameError::NotEmptyViolated),
);
assert_eq!(
Username::try_new("TheUserNameIsVeryVeryLong"),
Err(UsernameError::LenCharMaxViolated),
);
更多请参阅
内部类型
可用的净化器、验证器和可派生特性由内部类型决定,该类型属于以下类别
- 字符串
- 整数 (
u8
,u16
,u32
,u64
,u128
,i8
,i16
,i32
,i64
,i128
,usize
,isize
) - 浮点数 (
f32
,f64
) - 其他任何内容
字符串
目前字符串内部类型仅支持 String
(所有者) 类型。
字符串清理器
清理器 | 描述 | 示例 |
---|---|---|
trim |
删除前导和尾随空白字符 | trim |
lowercase |
将字符串转换为小写 | lowercase |
uppercase |
将字符串转换为大写 | uppercase |
with |
自定义清理器。接收 String 并返回 String 的函数或闭包 |
with = |mut s:字符串| {s.truncate(5);s} |
字符串验证器
验证器 | 描述 | 错误变体 | 示例 |
---|---|---|---|
len_char_min |
字符串的最小长度(以字符为单位,不是字节) | LenCharMinViolated |
len_char_min= 5 |
len_char_max |
字符串的最大长度(以字符为单位,不是字节) | LenCharMaxViolated |
len_char_max= 255 |
not_empty |
拒绝空字符串 | NotEmptyViolated |
not_empty |
regex |
使用正则表达式验证格式。需要 regex 功能。 |
RegexViolated |
regex = "^[0-9]{7}$" 或 regex = ID_REGEX |
predicate |
自定义验证器。接收 &str 并返回 bool 的函数或闭包 |
PredicateViolated |
predicate = |s: &str| s.contains('@') |
正则表达式验证
要求
regex
功能在nutype
中已启用。- 您必须显式包含
regex
和lazy_static
作为依赖项。
您可以使用多种方式使用正则表达式。
可以直接在正则表达式中定义正则表达式
#[nutype(validate(regex = "^[0-9]{3}-[0-9]{3}$"))]
pub struct PhoneNumber(String);
或使用 lazy_static
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref PHONE_NUMBER_REGEX: Regex = Regex::new("^[0-9]{3}-[0-9]{3}$").unwrap();
}
#[nutype(validate(regex = PHONE_NUMBER_REGEX))]
pub struct PhoneNumber(String);
或 once_cell
use once_cell::sync::Lazy;
use regex::Regex;
static PHONE_NUMBER_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new("[0-9]{3}-[0-9]{3}$").unwrap());
#[nutype(validate(regex = PHONE_NUMBER_REGEX))]
pub struct PhoneNumber(String);
字符串可推导特质
以下特质可以用于基于字符串的类型:Debug
、Clone
、PartialEq
、Eq
、PartialOrd
、Ord
、FromStr
、AsRef
、Deref
、From
、TryFrom
、Into
、Hash
、Borrow
、Display
、Default
、Serialize
、Deserialize
。
整数
整数内部类型包括: u8
、u16
、u32
、u64
、u128
、i8
、i16
、i32
、i64
、i128
、usize
、isize
。
整数清理器
清理器 | 描述 | 示例 |
---|---|---|
with |
自定义清理器。 | with = |原始| 原始.限制(0, 100) |
整数验证器
验证器 | 描述 | 错误变体 | 示例 |
---|---|---|---|
小于 |
排他上限 | 小于违反 |
小于= 100 |
小于等于 |
包含上限 | 小于等于违反 |
小于等于= 99 |
大于 |
排他下限 | 大于违反 |
大于= 17 |
大于等于 |
包含下限 | 大于等于违反 |
大于等于= 18 |
predicate |
自定义谓词 | PredicateViolated |
predicate = |数量| 数量% 2 == 0 |
整数可推导特质
以下特质可以用于基于整数的类型: Debug
、Clone
、Copy
、PartialEq
、Eq
、PartialOrd
、Ord
、FromStr
、AsRef
、Deref
、Into
、From
、TryFrom
、Hash
、Borrow
、Display
、Default
、Serialize
、Deserialize
。
浮点数
浮点内部类型包括: f32
、f64
。
浮点清理器
清理器 | 描述 | 示例 |
---|---|---|
with |
自定义清理器。 | with = |val| val.限制(0.0, 100.0) |
浮点验证器
验证器 | 描述 | 错误变体 | 示例 |
---|---|---|---|
小于 |
排他上限 | 小于违反 |
小于= 100.0 |
小于等于 |
包含上限 | 小于等于违反 |
小于等于= 100.0 |
大于 |
排他下限 | 大于违反 |
大于= 0.0 |
大于等于 |
包含下限 | 大于等于违反 |
大于等于= 0.0 |
有限 |
检查NaN和无穷大 | 有限违反 |
有限 |
predicate |
自定义谓词 | PredicateViolated |
predicate = |val| val!= 50.0 |
浮点可推导特质
以下特质可以用于基于浮点数的类型: Debug
、Clone
、Copy
、PartialEq
、Eq
、PartialOrd
、Ord
、FromStr
、AsRef
、Deref
、Into
、From
、TryFrom
、Hash
、Borrow
、Display
、Default
、Serialize
、Deserialize
。
如果验证规则保证排除了NaN,则也可以推导出Eq
和Ord
特质。这可以通过应用finite
验证来实现。例如
#[nutype(
validate(finite),
derive(PartialEq, Eq, PartialOrd, Ord),
)]
struct Size(f64);
其他内部类型和泛型
对于任何其他类型,可以使用with
定义自定义清理器,并使用predicate
定义自定义验证。
use nutype::nutype;
#[nutype(
derive(Debug, PartialEq, AsRef, Deref),
sanitize(with = |mut guests| { guests.sort(); guests }),
validate(predicate = |guests| !guests.is_empty() ),
)]
pub struct GuestList(Vec<String>);
也可以使用泛型
#[nutype(
sanitize(with = |mut v| { v.sort(); v }),
validate(predicate = |vec| !vec.is_empty()),
derive(Debug, PartialEq, AsRef, Deref),
)]
struct SortedNotEmptyVec<T: Ord>(Vec<T>);
let wise_friends = SortedNotEmptyVec::try_new(vec!["Seneca", "Zeno", "Plato"]).unwrap();
assert_eq!(wise_friends.as_ref(), &["Plato", "Seneca", "Zeno"]);
assert_eq!(wise_friends.len(), 3);
let numbers = SortedNotEmptyVec::try_new(vec![4, 2, 7, 1]).unwrap();
assert_eq!(numbers.as_ref(), &[1, 2, 4, 7]);
assert_eq!(numbers.len(), 4);
自定义清理器
可以使用with
选项设置自定义清理器。自定义清理器是一个接收内部类型的值并返回清理后的值的函数或闭包。
例如,这个
#[nutype(sanitize(with = new_to_old))]
pub struct CityName(String);
fn new_to_old(s: String) -> String {
s.replace("New", "Old")
}
等于下面的这个
#[nutype(sanitize(with = |s| s.replace("New", "Old") ))]
pub struct CityName(String);
并且工作方式相同
let city = CityName::new("New York");
assert_eq!(city.into_inner(), "Old York");
自定义验证器
以类似的方式,可以定义自定义验证器,但验证函数接收一个引用并返回 bool
。将其视为一个谓词。
#[nutype(validate(predicate = is_valid_name))]
pub struct Name(String);
fn is_valid_name(name: &str) -> bool {
// A fancy way to verify if the first character is uppercase
name.chars().next().map(char::is_uppercase).unwrap_or(false)
}
菜谱
派生 Default
#[nutype(
derive(Default),
default = "Anonymous",
)]
pub struct Name(String);
在浮点类型上派生 Eq
和 Ord
使用 nutype,如果存在 finite
验证集,则可以派生 Eq
和 Ord
。finite
验证确保有效值不包括 NaN
。
#[nutype(
validate(finite),
derive(PartialEq, Eq, PartialOrd, Ord),
)]
pub struct Weight(f64);
使用 new_unchecked 破坏约束
虽然不推荐,但可以通过启用 new_unchecked
crate 功能并使用 new_unchecked
标记类型来绕过约束。
#[nutype(
new_unchecked,
sanitize(trim),
validate(len_char_min = 8)
)]
pub struct Name(String);
// Yes, you're forced to use `unsafe` here, so everyone will point fingers at YOU.
let name = unsafe { Name::new_unchecked(" boo ".to_string()) };
// `name` violates the sanitization and validation rules!!!
assert_eq!(name.into_inner(), " boo ");
功能标志
arbitrary
- 启用 derivearbitrary::Arbitrary
。new_unchecked
- 启用生成 unsafe::new_unchecked()
函数。regex
- 允许在基于字符串的类型上使用regex =
验证。注意:您的 crate 还必须在依赖项中显式包含regex
和lazy_static
。serde
- 与serde
crate 的集成。允许 deriveSerialize
和Deserialize
特性。schemars08
- 允许 deriveJsonSchema
trait of schemars crate。注意,目前验证规则不受尊重。std
- 默认启用。使用default-features = false
禁用。
当 nutype 适合你时?
- 如果你喜欢 newtype 模式,并且你喜欢利用 Rust 类型系统来强制执行业务逻辑的正确性。
- 如果你想要使用类型系统来保持不变量
- 如果你是 DDD 粉丝,nutype 是一个很好的助手,可以使你的领域模型更具表现力。
- 你想要快速原型设计而不牺牲质量。
当 nutype 不那么好时?
- 你过于关心编译时间(nutype 依赖于 proc macros 的重用)。
- 你认为元编程太隐含了。
- IDEs 在提供有关 proc macros 的提示方面可能不太有帮助。
- nutype 的设计可能迫使你运行不必要的验证(例如,在从数据库加载数据时),这可能会对极端性能目标产生负面影响。
支持乌克兰军事力量
今天我住在柏林,我有幸过着安全的物理生活。但我是乌克兰人。我生命的前 25 年在乌克兰第二大城市、距离俄罗斯边境 60 公里的 哈尔科夫 度过。今天大约有 三分之一的家乡被俄罗斯摧毁。
其中一些人设法逃亡到欧盟。其他人正在尝试在哈尔科夫过“正常的生活”,在那里完成他们的日常职责。还有一些人现在正在前线,每分每秒都在冒着生命危险保护其他人。
我鼓励你向谢尔盖·普里图拉慈善基金会捐款。只需选择你喜欢的项目并捐款即可。这是最知名的基金会之一,你可以观看一个关于它的短片。你对乌克兰军队的贡献也是对我平静心情的贡献,这样我就能有更多时间开发项目。
谢谢。
类似项目
- prae - 一个旨在解决相同问题但采用略微不同方法的非常相似的crate。
- bounded-integer - Rust的有限整数。
- refinement - 基于泛型的便捷创建类型安全精炼类型。
- semval - Rust的语义验证。
- validator - Rust结构体的简单验证(由宏驱动)。
许可证
MIT © 谢尔盖·波塔波夫
依赖项
~0.9–2MB
~35K SLoC