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 过程宏

Download history 3433/week @ 2024-04-15 3251/week @ 2024-04-22 2984/week @ 2024-04-29 3268/week @ 2024-05-06 3032/week @ 2024-05-13 3140/week @ 2024-05-20 4128/week @ 2024-05-27 5048/week @ 2024-06-03 6919/week @ 2024-06-10 7717/week @ 2024-06-17 7431/week @ 2024-06-24 6475/week @ 2024-07-01 7546/week @ 2024-07-08 5811/week @ 2024-07-15 7999/week @ 2024-07-22 6840/week @ 2024-07-29

每月28,536次下载
用于 22 个crate(3个直接使用)

MIT/Apache

82KB
1.5K SLoC

Garde — 文档 最新版本

Rust验证库

基本使用示例

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

bytesgraphemesutf16chars 主要用于字符串验证

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

上述类型在以下情况下将失败验证:

  • 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,因为它们在 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 验证电子邮件。 regexonce_cell
email-idna 支持电子邮件地址中的应用程序国际化域名 idna
正则表达式 通过regex crate在pattern中支持正则表达式 regexonce_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.3–1.3MB
~27K SLoC