2个版本

0.18.1 2024年2月8日
0.18.0 2024年2月8日

#85国际化 (i18n)

MIT/Apache

93KB
2K SLoC

Guard-FR

Guard的修改版,在等待Garde库的国际化之前,错误消息主要为法语

Guard — 文档 最新版本

Rust验证库

基本用法示例

要开始使用,请安装 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 字段。
  • 对于 lengthrange,可以省略 minmax,但不能同时省略。
  • lengthrange 使用 包含 的上限(min..=max)。
  • length<mode> 参数的解释在这里说明了
  • 对于 containsprefixsuffix,模式必须是字符串字面量,因为 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>
}

bytesgraphemesutf16chars 主要用于字符串验证

  • 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>,
}

上述类型如果以下条件不满足,将无法通过验证:

  • valueNone
  • 内部 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 版本。

此仓库还使用了以下工具,但它们是可选的

许可

根据以下之一许可

您可以选择。

贡献

除非您明确声明,否则根据 Apache-2.0 许可证定义的,您有意提交的任何贡献,都应按上述方式双重许可,不附加任何额外的条款或条件。

致谢

此crate深受 validator crate 的启发。它基本上是对 validator 的全面重写。创建此crate的灵感来自于 这条评论 和一些关于潜在重写的讨论。

[^1]: HTML5 表单 - 有效电子邮件地址

依赖关系

~0.5–5.5MB
~45K SLoC