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 模式

Download history 3689/week @ 2024-05-02 3314/week @ 2024-05-09 3281/week @ 2024-05-16 3402/week @ 2024-05-23 5030/week @ 2024-05-30 7002/week @ 2024-06-06 7321/week @ 2024-06-13 7993/week @ 2024-06-20 7153/week @ 2024-06-27 7351/week @ 2024-07-04 6780/week @ 2024-07-11 7242/week @ 2024-07-18 7924/week @ 2024-07-25 8148/week @ 2024-08-01 8541/week @ 2024-08-08 7358/week @ 2024-08-15

34,074 每月下载量
用于 24 个包 (12 个直接)

MIT/Apache

99KB
2K SLoC

加德   文档 最新版本

Rust 验证库

基本用法示例

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

bytesgraphemesutf16chars 主要用于字符串验证

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

如果上述类型满足以下条件,则验证将失败

  • 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

属性 #[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版本。

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

许可证

根据以下任一许可协议

根据您的选择。

贡献

除非您明确声明,否则根据Apache-2.0许可协议定义,您提交的任何有意提交以包含在作品中的贡献,将按上述方式双重许可,不附加任何额外条款或条件。

致谢

此crate受到validator crate的极大启发。它基本上是对validator的全面重写。此crate的创建是由此评论和几篇关于潜在重写的其他文章引发的。

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

依赖关系

~0.5–6MB
~50K SLoC