12 个版本
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.1 | 2023 年 2 月 11 日 |
#2783 in Rust 模式
220,023 每月下载量
在 29 个 crate 中使用 (via nutype)
255KB
6K SLoC
具有保证的新类型。
Nutype 是一个 proc macro,允许为常规的 newtype 模式 添加额外的约束,如 清理 和 验证。生成的代码使得无法通过检查来实例化一个值。即使在 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 = |raw| raw.clamp(0, 100) |
整数验证器
验证器 | 描述 | 错误类型 | 示例 |
---|---|---|---|
less |
排他性上限 | LessViolated |
less= 100 |
less_or_equal |
包含性上限 | LessOrEqualViolated |
less_or_equal= 99 |
greater |
排他性下限 | GreaterViolated |
greater= 17 |
greater_or_equal |
包含性下限 | GreaterOrEqualViolated |
greater_or_equal= 18 |
predicate |
自定义谓词 | PredicateViolated |
predicate = |num| num% 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.clamp(0.0, 100.0) |
浮点数验证器
验证器 | 描述 | 错误类型 | 示例 |
---|---|---|---|
less |
排他性上限 | LessViolated |
less= 100.0 |
less_or_equal |
包含性上限 | LessOrEqualViolated |
less_or_equal= 100.0 |
greater |
排他性下限 | GreaterViolated |
greater= 0.0 |
greater_or_equal |
包含性下限 | GreaterOrEqualViolated |
greater_or_equal= 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
- 启用派生arbitrary::Arbitrary
。new_unchecked
- 启用生成不安全的::new_unchecked()
函数。regex
- 允许在基于字符串的类型上使用regex =
验证。注意:您的crate还必须在依赖项中显式包含regex
和lazy_static
。serde
- 与serde
crate 集成。允许派生Serialize
和Deserialize
特性。schemars08
- 允许派生JsonSchema
特性,来自 schemars crate。注意,目前验证规则不被遵守。std
- 默认启用。使用default-features = false
禁用。
何时 nutype 适合您?
- 如果您喜欢 newtype 模式,并且您喜欢利用Rust类型系统来强制执行业务逻辑的正确性。
- 如果您想使用类型系统来保持不变性
- 如果您是DDD爱好者,nutype是使您的领域模型更具表达力的绝佳助手。
- 您希望在保证质量的同时快速原型化。
何时 nutype 不太适合?
- 您非常关心编译时间(nutype依赖于proc宏的重度使用)。
- 您认为元编程太过于隐秘的魔法。
- IDE可能无法很好地为您提供关于proc宏的提示。
- nutype的设计可能迫使您执行不必要的验证(例如,从数据库加载数据时),这可能会对您追求极端性能的目标产生负面影响。
支持乌克兰军队
今天我住在柏林,我有幸过着身体安全的 生活。但我是一名乌克兰人。我生命的前25年在乌克兰第二大城市 哈尔科夫 度过,离俄罗斯边界有60公里。今天大约 三分之一的我的家乡被俄国人摧毁。我的父母、亲戚和朋友们不得不在炮击和空袭中生存,在地下室里生活了一个多月。
其中一些人设法逃到了欧盟。其他人正在哈尔科夫尝试过着“正常的生活”,在那里履行日常职责。还有一些人现在正在前线,每秒钟都冒着生命危险保护其他人。
我鼓励您向 谢尔盖·普里图拉慈善基金会 捐款。只需选择您喜欢的项目并进行捐赠即可。这是最知名的基金会之一,您可以观看一个 简短的纪录片 了解它。您对乌克兰军队的贡献是对我平静的捐献,这样我可以有更多的时间开发这个项目。
谢谢。
类似项目
- prae - 一个非常相似的crate,旨在解决相同的问题,但采用略有不同的方法。
- bounded-integer - Rust的有限整数。
- refinement - 基于泛型的类型安全精炼类型的便捷创建。
- semval - Rust的语义验证。
- validator - Rust结构体的简单验证(由宏驱动)。
许可证
MIT © 谢尔盖·波塔波夫
依赖项
~0.9–1.9MB
~34K SLoC