32 个版本 (18 个破坏性更改)
0.20.0 | 2024 年 7 月 5 日 |
---|---|
0.18.0 | 2024 年 1 月 28 日 |
0.16.3 | 2023 年 11 月 26 日 |
0.13.0 | 2023 年 7 月 28 日 |
0.9.2 | 2023 年 3 月 31 日 |
#75 in Rust 模式
34,074 每月下载量
用于 24 个包 (12 个直接)
99KB
2K SLoC
加德
Rust 验证库
- 基本用法示例
- 验证规则
- 长度模式
- 内部类型验证
- Newtypes
- 处理 Option
- 自定义验证
- 上下文/Self 访问
- 实现规则
- 实现
Validate
- 规则适配器
- 与 Web 框架集成
- 功能标志
- 为什么选择
garde
?
基本用法示例
要开始使用,请安装 garde
cargo add garde -F full
并将 Validate
derive附加到您的类型上。 garde
将为您生成一个 Validate
trait 的实现,允许您调用 validate
方法。
以下是一个完整的示例
use garde::{Validate, Valid};
#[derive(Validate)]
struct User<'a> {
#[garde(ascii, length(min=3, max=25))]
username: &'a str,
#[garde(length(min=15))]
password: &'a str,
}
let user = User {
username: "test",
password: "not_a_very_good_password",
};
if let Err(e) = user.validate() {
println!("invalid user: {e}");
}
加德还可以验证枚举
use garde::{Validate, Valid};
#[derive(Validate)]
enum Data {
Struct {
#[garde(range(min=-10, max=10))]
field: i32,
},
Tuple(
#[garde(ascii)]
String
),
}
let data = Data::Struct { field: 100 };
if let Err(e) = data.validate() {
println!("invalid data: {e}");
}
可用的验证规则
名称 | 格式 | 验证 | 功能标志 |
---|---|---|---|
必需 | #[加德(必需)] |
值已设置 | - |
ASCII | #[加德(ASCII)] |
仅包含 ASCII | - |
字母数字 | #[加德(字母数字)] |
仅包含字母和数字 | - |
电子邮件 | #[加德(电子邮件)] |
符合 HTML5 规范的电子邮件[^1] | 电子邮件 |
URL | #[加德(URL)] |
一个 URL | URL |
IP 地址 | #[加德(IP 地址)] |
一个 IP 地址(IPv4 或 IPv6) | - |
IPv4 | #[加德(IPv4)] |
一个 IPv4 地址 | - |
IPv6 | #[加德(IPv6)] |
一个 IPv6 地址 | - |
信用卡 | #[加德(credit_card)] |
信用卡号码 | credit-card |
电话号码 | #[加德(phone_number)] |
一个电话号码 | phone-number |
长度 | #[加德(长度(<模式>,最小=<usize>,最大=<usize>)] |
一个长度在 min..=max 的容器 |
- |
匹配 | #[加德(匹配(<字段>))] |
一个字段与另一个字段匹配 | - |
范围 | #[加德(范围(最小=<表达式>,最大=<表达式>))] |
一个在 min..=max 范围内的数字 |
- |
包含 | #[加德(包含(<字符串>))] |
包含子字符串的字符串值 | - |
前缀 | #[加德(前缀(<字符串>))] |
以某些字符串为前缀的字符串值 | - |
后缀 | #[加德(后缀(<字符串>))] |
以某些字符串为后缀的字符串值 | - |
模式 | #[加德(模式("<正则表达式>"))] |
匹配某些正则表达式的字符串值 | 正则表达式 |
模式 | #[加德(模式(<匹配器>))] |
由某些匹配器匹配的字符串值 | - |
深入 | #[加德(深入)] |
嵌套验证,在值上调用 validate |
- |
跳过 | #[加德(跳过)] |
跳过验证 | - |
自定义 | #[加德(自定义(<函数或闭包>))] |
自定义验证器 | - |
附加说明
required
仅适用于Option
字段。- 对于
length
和range
,可以省略min
或max
中的任意一个,但不能同时省略。 length
和range
使用一个 包含 的上界(min..=max
)。length
的mode
参数的 解释 在这里- 对于
contains
、prefix
和suffix
,模式必须是字符串字面量,因为当前的Pattern
API 是 不稳定的。 - Garde 默认不启用
regex
crate 的功能 - 如果您需要额外的正则表达式功能(例如 Unicode)或更好的性能,请在您的Cargo.toml
中添加对regex = "1"
的依赖。
如果您的结构体上的大多数字段都使用 #[garde(skip)]
进行注释,则可以使用 #[garde(allow_unvalidated)]
代替
#[derive(garde::Validate)]
struct Foo<'a> {
#[garde(length(min = 1))]
a: &'a str,
#[garde(skip)]
b: &'a str, // this field will not be validated
}
#[derive(garde::Validate)]
#[garde(allow_unvalidated)]
struct Bar<'a> {
#[garde(length(min = 1))]
a: &'a str,
b: &'a str, // this field will not be validated
// note the lack of `#[garde(skip)]`
}
长度模式
length
规则接受一个可选的 mode
参数,该参数确定要验证的 类型 的长度。
选项有
简单
字节
图形符号
utf16
字符
simple
是当省略 mode
参数时使用的默认值。简单长度的含义取决于类型。它目前在字符串中实现,验证字节数,以及在 std::collections
中实现,验证项目数。
#[derive(garde::Validate)]
struct Foo {
#[garde(length(min = 1, max = 100))]
string: String,
#[garde(length(min = 1, max = 100))]
collection: Vec<u32>
}
bytes
、graphemes
、utf16
和 chars
主要用于字符串验证
bytes
验证字节数graphemens
使用unicode-segmentation
crate,并验证图形符号数utf16
使用encode_utf16
,并验证 UTF-16 代码点数chars
使用chars
,并验证 Unicode 标量值数
#[derive(garde::Validate)]
struct Foo {
#[garde(length(bytes, min = 1, max = 100))]
a: String, // `a.len()`
#[garde(length(graphemes, min = 1, max = 100))]
b: String, // `b.graphemes().count()`
#[garde(length(utf16, min = 1, max = 100))]
c: String, // `c.encode_utf16().count()`
#[garde(length(chars, min = 1, max = 100))]
d: String, // `d.chars().count()`
}
内部类型验证
如果您需要验证容器中的"内部"类型,例如在Vec<String>
中的String
,则使用inner
修饰符
#[derive(garde::Validate)]
struct Test {
#[garde(
length(min = 1),
inner(ascii, length(min = 1)), // wrap the rule in `inner`
)]
items: Vec<String>,
}
如果上述类型满足以下条件,则验证将失败
Vec
为空- 内部
String
元素中的任何一个为空 - 内部
String
元素中的任何一个包含非ASCII字符
要验证深层嵌套的类型,例如Vec<Option<String>>
,则必须在泛型每个级别嵌套使用inner
修饰符
#[derive(garde::Validate)]
struct Test {
#[garde(
length(min = 1), // applies to `Vec`
inner(inner(ascii, length(min = 1))), // applies to `String`
)]
items: Vec<Option<String>>,
}
您可以针对嵌套类型的每一层应用不同的规则
#[derive(garde::Validate)]
struct Test {
#[garde(
length(min = 1), // applies to `Vec`
inner(required), // applies to `Option`
inner(inner(ascii, length(min = 1))), // applies to `String`
)]
items: Vec<Option<String>>,
}
Newtypes
在字段上重用验证规则的最佳方式是使用与#[garde(transparent)]
一起的newtype 习语
#[derive(garde::Validate)]
#[garde(transparent)]
struct Username(#[garde(length(min = 3, max = 20))] String);
#[derive(garde::Validate)]
struct User {
// later used with `dive`:
#[garde(dive)]
username: Username,
}
在上述示例中,username
字段将继承来自Username
上的String
字段的全部验证规则。结果是错误路径将减少一个级别,从而产生更清晰的错误消息
User {
username: Username("")
}.validate()
"username: length is lower than 3"
如果没有#[garde(transparent)]
属性,则错误路径将是
User {
username: Username("")
}.validate()
"username[0]: length is lower than 3"
具有#[garde(transparent)]
属性的结构的字段可以多于一个,但必须有且只有一个未跳过的字段。这意味着除了您希望验证的字段之外的所有字段都必须有#[garde(skip)]
属性。
处理 Option
每个规则都适用于Option<T>
字段。字段只有在它是Some
时才会进行验证。如果您还想验证Option<T>
字段是Some
,请使用required
规则
#[derive(garde::Validate)]
struct Test {
#[garde(required, ascii, length(min = 1))]
value: Option<String>,
}
如果上述类型满足以下条件,则验证将失败
value
是None
- 内部
value
为空 - 内部
value
包含非ASCII字符
自定义验证
可以通过custom
规则和context
属性自定义验证
上下文可以是任何没有泛型参数的类型。默认情况下,上下文是 ()
。
#[derive(garde::Validate)]
#[garde(context(PasswordContext))]
struct User {
#[garde(custom(is_strong_password))]
password: String,
}
struct PasswordContext {
min_entropy: f32,
entropy: cracken::password_entropy::EntropyEstimator,
}
fn is_strong_password(value: &str, context: &PasswordContext) -> garde::Result {
let bits = context.entropy.estimate_password_entropy(value.as_bytes())
.map(|e| e.mask_entropy)
.unwrap_or(0.0);
if bits < context.min_entropy {
return Err(garde::Error::new("password is not strong enough"));
}
Ok(())
}
let ctx = PasswordContext { /* ... */ };
let user = User { /* ... */ };
user.validate(&ctx)?;
验证函数可以接受任何类型的引用,并将其解引用。在上面的例子中,可以使用 &str
,因为 password
是一个 String
,而 String
解引用为 &str
。
属性 #[garde(custom(...))]
接受任何计算结果为以下特质的表达式的值
FnOnce(&T, &<T as Validate>::Context) -> garde::Result
这意味着可以使用高阶函数
// Returns a function which does the actual validation.
fn my_equals(other: &str) -> impl FnOnce(&str, &()) -> garde::Result + '_ {
move |value, _| {
if value != other {
return Err(garde::Error::new(format!("not equal to {other}")));
}
Ok(())
}
}
#[derive(garde::Validate)]
struct User {
#[garde(length(min = 1, max = 255))]
password: String,
// Combined with `self` access in rules:
#[garde(custom(my_equals(&self.password2)))]
password2: String,
}
上下文/Self 访问
通常也可以访问上下文和 self
,因为它们在宏输出的作用域内
struct Limits {
min: usize,
max: usize,
}
struct Config {
username: Limits,
}
#[derive(garde::Validate)]
#[garde(context(Config as ctx))]
struct User {
#[garde(length(min = ctx.username.min, max = ctx.username.max))]
username: String,
}
实现规则
假设你想要为自定义字符串类型实现长度检查。为此,你需要为它实现一个 length
特质,具体取决于你需要的验证类型。
#[repr(transparent)]
pub struct MyString(String);
impl garde::rules::length::HasSimpleLength for MyString {
fn length(&self) -> usize {
self.0.len()
}
}
#[derive(garde::Validate)]
struct Foo {
// Now the `length` check may be used with `MyString`
#[garde(length(min = 1, max = 1000))]
field: MyString,
}
每个规则都附带一个特质,可以在你的代码中的自定义类型中实现。它们都可在 garde::rules
下找到。
实现 Validate
如果你有一个想要支持嵌套验证(使用 #[garde(dive)]
规则)的容器类型,你可以为它实现 Validate
。
#[repr(transparent)]
struct MyVec<T>(Vec<T>);
impl<T: garde::Validate> garde::Validate for MyVec<T> {
type Context = T::Context;
fn validate_into(
&self,
ctx: &Self::Context,
mut parent: &mut dyn FnMut() -> garde::Path,
report: &mut garde::Report
) {
for (index, item) in self.0.iter().enumerate() {
let mut path = garde::util::nested_path!(parent, index);
item.validate_into(ctx, &mut path, report);
}
}
}
#[derive(garde::Validate)]
struct Foo {
#[garde(dive)]
field: MyVec<Bar>,
}
#[derive(garde::Validate)]
struct Bar {
#[garde(range(min = 1, max = 10))]
value: u32,
}
规则适配器
适配器允许你在不使用新类型的情况下为第三方类型实现验证。
适配器可能看起来像这样
mod my_str_adapter {
#![allow(unused_imports)]
pub use garde::rules::*; // re-export garde's rules
pub mod length {
pub use garde::rules::length::*; // re-export `length` rules
pub mod simple {
// re-implement `simple`, but _only_ for the concrete type &str!
pub fn apply(v: &str, (min, max): (usize, usize)) -> garde::Result {
if !(min..=max).contains(&v.len()) {
Err(garde::Error::new("my custom error message"))
} else {
Ok(())
}
}
}
}
}
你创建一个模块,在其中添加 garde::rules
的公共全局重导出,然后重新实现你感兴趣的特定规则。这是一种 鸭子类型。任何未重新实现的规则将简单地委托给 garde
的实现。
这相当冗长,但换来的灵活性最大化。要使用适配器,向字段添加一个 adapt
属性
#[derive(garde::Validate)]
struct Stuff<'a> {
#[garde(
adapt(my_str_adapter),
length(min = 1),
ascii,
)]
v: &'a str,
}
length
规则现在将使用你的自定义实现,但 ascii
规则将继续使用 garde
的实现。
与 Web 框架集成
功能标志
名称 | 描述 | 额外依赖 |
---|---|---|
derive |
启用使用 derive(Validate) 宏 |
garde_derive |
URL |
通过 url crate 进行 URL 验证。 |
URL |
电子邮件 |
根据HTML5验证电子邮件 | regex , once_cell |
email-idna |
支持电子邮件地址中的应用程序国际化域名 | idna |
正则表达式 |
通过regex crate在pattern 中支持正则表达式 |
regex , once_cell |
credit-card |
通过card-validate crate验证信用卡号码 |
card-validate |
phone-number |
通过phonenumber crate验证电话号码 |
phonenumber |
unicode |
通过unicode-segmentation crate验证图形计数 |
unicode-segmentation |
为什么选择 garde
?
法语中“Garde”意为“保护”。我既不是法国人,也不说法语,但是guard
已被占用,这已经很接近了 :).
开发
为garde
做出贡献只需要安装一个相对较新的Rust
版本。
此存储库还使用了以下工具,但它们是可选的
insta
用于快照测试(tests/rules)。just
用于运行定义在justfile
中的配方。运行just -l
以查看可用的配方。
许可证
根据以下任一许可协议
- Apache License,版本2.0(LICENSE-APACHE或https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT许可(LICENSE-MIT或http://opensource.org/licenses/MIT)
根据您的选择。
贡献
除非您明确声明,否则根据Apache-2.0许可协议定义,您提交的任何有意提交以包含在作品中的贡献,将按上述方式双重许可,不附加任何额外条款或条件。
致谢
此crate受到validator crate的极大启发。它基本上是对validator
的全面重写。此crate的创建是由此评论和几篇关于潜在重写的其他文章引发的。
[^1]: HTML5表单 - 有效电子邮件地址
依赖关系
~0.5–6MB
~50K SLoC