2个版本
0.18.1 | 2024年2月8日 |
---|---|
0.18.0 | 2024年2月8日 |
#85 在 国际化 (i18n)
93KB
2K SLoC
Guard-FR
Guard的修改版,在等待Garde库的国际化之前,错误消息主要为法语
Guard —
Rust验证库
- 基本用法示例
- 验证规则
- 长度模式
- 内部类型验证
- Newtypes
- 处理Option
- 自定义验证
- 上下文/Self访问
- 实现规则
- 实现
Validate
- 规则适配器
- 与Web框架集成
- 特性标志
- 为什么选择
garde
?
基本用法示例
要开始使用,请安装 garde
cargo add garde -F full
并将 Validate
derive附加到您的类型。 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}");
}
Guard也可以验证枚举
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}");
}
可用的验证规则
名称 | 格式 | 验证 | 特性标志 |
---|---|---|---|
必需的 | #[garde(必需的)] |
是否设置值 | - |
ASCII | #[garde(ASCII)] |
仅包含ASCII | - |
字母数字 | #[garde(字母数字)] |
仅字母和数字 | - |
电子邮件 | #[garde(电子邮件)] |
根据HTML5规范[^1]的电子邮件 | 电子邮件 |
URL | #[garde(URL)] |
一个URL | URL |
IP地址 | #[garde(IP地址)] |
一个IP地址(IPv4或IPv6) | - |
IPv4 | #[garde(IPv4)] |
一个IPv4地址 | - |
IPv6 | #[garde(IPv6)] |
一个IPv6地址 | - |
信用卡 | #[garde(credit_card)] |
信用卡号 | credit-card |
电话号码 | #[garde(phone_number)] |
一个电话号码 | phone-number |
长度 | #[garde(长度(<mode>,最小值=<usize>,最大值=<usize>)] |
一个长度在 min..=max 的容器 |
- |
范围 | #[garde(范围(最小值=<expr>,最大值=<expr>))] |
一个在 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
包的默认功能 - 如果你需要额外的正则表达式功能(例如 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
包,并验证字符群数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()`
}
内部类型验证
如果需要验证容器“内部”类型,例如 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 字符
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
字段将继承 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
。
上下文/Self访问
通常也可以访问上下文和 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框架集成
特性标志
名称 | 描述 | 额外依赖 |
---|---|---|
derive |
启用使用 derive(Validate) 宏 |
garde_derive |
URL |
通过 url 包验证 URL。 |
URL |
电子邮件 |
根据 HTML5 验证电子邮件。 | regex , once_cell |
email-idna |
在电子邮件地址中支持 国际化域名应用。 | idna |
正则表达式 |
通过 regex 包在 pattern 中支持正则表达式。 |
regex , once_cell |
credit-card |
通过 card-validate 包验证信用卡卡号。 |
card-validate |
phone-number |
通过 phonenumber 包验证电话号码。 |
phonenumber |
unicode |
通过 unicode-segmentation 包验证图形字符数。 |
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–5.5MB
~45K SLoC