29个版本 (16个破坏性更新)
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日 |
#1523 in 过程宏
每月28,536次下载
用于 22 个crate(3个直接使用)
82KB
1.5K SLoC
Garde —
Rust验证库
- 基本使用示例
- 验证规则
- 长度模式
- 内部类型验证
- 新类型
- 处理Option
- 自定义验证
- 上下文/自我访问
- 实现规则
- 实现
Validate
- 规则适配器
- 与Web框架集成
- 功能标志
- 为什么选择
garde
?
基本使用示例
要开始使用,请安装 garde
cargo add garde -F full
并将 Validate
派生宏附加到您的类型。 garde
将为您生成 Validate
特性的实现,允许您调用 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}");
}
Garde还可以验证枚举
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}");
}
可用的验证规则
name | format | validation | feature flag |
---|---|---|---|
required | #[garde(required)] |
is value set | - |
ascii | #[garde(ascii)] |
only contains ASCII | - |
alphanumeric | #[garde(alphanumeric)] |
only letters and digits | - |
#[garde(email)] |
an email according to the HTML5 spec[^1] | email |
|
url | #[garde(url)] |
a URL | url |
ip | #[garde(ip)] |
an IP address (either IPv4 or IPv6) | - |
ipv4 | #[garde(ipv4)] |
an IPv4 address | - |
ipv6 | #[garde(ipv6)] |
an IPv6 address | - |
credit card | #[garde(credit_card)] |
a credit card number | credit-card |
phone number | #[garde(phone_number)] |
a phone number | phone-number |
长度 | #[garde(长度(<模式>,最小值=<usize>,最大值=<usize>)] |
一个长度在 min..=max 范围内的容器 |
- |
匹配 | #[garde(匹配(<字段>))] |
一个字段匹配另一个字段 | - |
范围 | #[garde(范围(最小值=<表达式>,最大值=<表达式>))] |
范围 min..=max 内的数字 |
- |
包含 | #[garde(包含(<字符串>))] |
包含子字符串的字符串值 | - |
前缀 | #[garde(前缀(<字符串>))] |
以某些字符串为前缀的字符串值 | - |
后缀 | #[garde(后缀(<字符串>))] |
以某些字符串为后缀的字符串值 | - |
模式 | #[garde(模式("<正则表达式>"))] |
匹配某些正则表达式的字符串值 | 正则表达式 |
模式 | #[garde(模式(<匹配器>))] |
由某些 匹配器 匹配的字符串值 | - |
深入 | #[garde(深入)] |
嵌套验证,对值调用 validate |
- |
跳过 | #[garde(跳过)] |
跳过验证 | - |
自定义 | #[garde(自定义(<函数或闭包>))] |
自定义验证器 | - |
附加说明
required
仅适用于Option
字段。- 对于
length
和range
,可以省略min
或max
,但不能同时省略。 length
和range
使用 包含 的上限(min..=max
)。length
的mode
参数的 说明 在这里- 对于
contains
、prefix
和suffix
,模式必须是字符串字面量,因为Pattern
API 目前是不稳定的。 - Garde 不启用
regex
crate 的默认功能 - 如果您需要额外的正则表达式功能(例如 Unicode)或更好的性能,请将regex = "1"
添加到您的Cargo.toml
中。
如果您结构体上的大多数字段都使用 #[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
库,并验证了 graphemes 的数量utf16
使用encode_utf16
,并验证了 UTF-16 code points 的数量chars
使用chars
,并验证了 unicode scalar values 的数量
#[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()`
}
内部类型验证
如果需要验证容器“内部”的类型,例如 String
(位于 Vec<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>>,
}
新类型
要在字段上重用验证规则,最佳方式是使用 newtype idiom 与 #[garde(transparent)]
#[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
字段将继承 String
字段在 Username
上的所有验证规则。结果是错误路径将简化一个级别,从而产生更清晰的错误信息
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
,因为它们在 proc 宏的输出作用域内
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框架集成
功能标志
name | 描述 | 额外依赖 |
---|---|---|
derive |
启用使用 derive(Validate) 宏 |
garde_derive |
url |
通过 url 包验证URL。 |
url |
email |
根据 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.3–1.3MB
~27K SLoC