12 个版本

0.4.3 2024 年 7 月 6 日
0.4.2 2024 年 4 月 7 日
0.4.1-beta.12024 年 1 月 28 日
0.4.0 2023 年 11 月 20 日
0.1.1 2023 年 2 月 11 日

#2783 in Rust 模式

Download history 6671/week @ 2024-04-30 9440/week @ 2024-05-07 14494/week @ 2024-05-14 14106/week @ 2024-05-21 40230/week @ 2024-05-28 37833/week @ 2024-06-04 43201/week @ 2024-06-11 39778/week @ 2024-06-18 41827/week @ 2024-06-25 34938/week @ 2024-07-02 54736/week @ 2024-07-09 49800/week @ 2024-07-16 56217/week @ 2024-07-23 44752/week @ 2024-07-30 59809/week @ 2024-08-06 52698/week @ 2024-08-13

220,023 每月下载量
29 个 crate 中使用 (via nutype)

MIT 许可证

255KB
6K SLoC

Rust Nutype Logo

具有保证的新类型。

Nutype Build Status Nutype Documentation

Nutype 是一个 proc macro,允许为常规的 newtype 模式 添加额外的约束,如 清理验证。生成的代码使得无法通过检查来实例化一个值。即使在 serde 反序列化中也以这种方式工作。

快速入门

use nutype::nutype;

// Define newtype Username
#[nutype(
    sanitize(trim, lowercase),
    validate(not_empty, len_char_max = 20),
    derive(Debug, PartialEq, Clone),
)]
pub struct Username(String);

// We can obtain a value of Username with `::try_new()`.
// Note that Username holds a sanitized string
assert_eq!(
    Username::try_new("   FooBar  ").unwrap().into_inner(),
    "foobar"
);

// It's impossible to obtain an invalid Username
// Note that we also got `UsernameError` enum generated implicitly
// based on the validation rules.
assert_eq!(
    Username::try_new("   "),
    Err(UsernameError::NotEmptyViolated),
);
assert_eq!(
    Username::try_new("TheUserNameIsVeryVeryLong"),
    Err(UsernameError::LenCharMaxViolated),
);

更多请参阅

内部类型

可用的清理器、验证器和可派生特质由内部类型决定,该类型属于以下类别

  • 字符串
  • 整数 (u8, u16,u32, u64, u128, i8, i16, i32, i64, i128, usize, isize)
  • 浮点数 (f32, f64)
  • 其他任何内容

字符串

目前字符串内部类型仅支持 String (所有者) 类型。

字符串清理器

清理器 描述 示例
trim 移除前导和尾部空白字符 trim
lowercase 将字符串转换为小写 lowercase
uppercase 将字符串转换为大写 uppercase
with 自定义清理器。接收 String 并返回 String 的函数或闭包 with = |mut s:字符串| {s.truncate(5);s}

字符串验证器

验证器 描述 错误类型 示例
len_char_min 字符串的最小长度(以字符计,不是字节) LenCharMinViolated len_char_min= 5
len_char_max 字符串的最大长度(以字符计,不是字节) LenCharMaxViolated len_char_max= 255
not_empty 拒绝空字符串 NotEmptyViolated not_empty
regex 使用正则表达式验证格式。需要 regex 功能。 RegexViolated regex = "^[0-9]{7}$"regex = ID_REGEX
predicate 自定义验证器。接收 &str 并返回 bool 的函数或闭包 PredicateViolated predicate = |s: &str| s.contains('@')

正则表达式验证

要求

  • regex 特性在 nutype 中已启用。
  • 您必须显式包含 regexlazy_static 作为依赖项。

您可以使用正则表达式的方法有很多。

正则表达式可以直接在位置定义

#[nutype(validate(regex = "^[0-9]{3}-[0-9]{3}$"))]
pub struct PhoneNumber(String);

或者使用 lazy_static 定义

use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
    static ref PHONE_NUMBER_REGEX: Regex = Regex::new("^[0-9]{3}-[0-9]{3}$").unwrap();
}

#[nutype(validate(regex = PHONE_NUMBER_REGEX))]
pub struct PhoneNumber(String);

或者 once_cell

use once_cell::sync::Lazy;
use regex::Regex;

static PHONE_NUMBER_REGEX: Lazy<Regex> =
    Lazy::new(|| Regex::new("[0-9]{3}-[0-9]{3}$").unwrap());

#[nutype(validate(regex = PHONE_NUMBER_REGEX))]
pub struct PhoneNumber(String);

字符串可派生特性

以下特性可以派生自基于字符串的类型:DebugClonePartialEqEqPartialOrdOrdFromStrAsRefDerefFromTryFromIntoHashBorrowDisplayDefaultSerializeDeserialize

整数

整数内部类型包括:u8u16u32u64u128i8i16i32i64i128usizeisize

整数清理器

清理器 描述 示例
with 自定义清理器。 with = |raw| raw.clamp(0, 100)

整数验证器

验证器 描述 错误类型 示例
less 排他性上限 LessViolated less= 100
less_or_equal 包含性上限 LessOrEqualViolated less_or_equal= 99
greater 排他性下限 GreaterViolated greater= 17
greater_or_equal 包含性下限 GreaterOrEqualViolated greater_or_equal= 18
predicate 自定义谓词 PredicateViolated predicate = |num| num% 2 == 0

整数可派生特性

以下是可以为基于整型的类型推导出的特性:DebugCloneCopyPartialEqEqPartialOrdOrdFromStrAsRefDerefIntoFromTryFromHashBorrowDisplayDefaultSerializeDeserialize

浮点数

浮点数内部类型包括:f32f64

浮点数清理器

清理器 描述 示例
with 自定义清理器。 with = |val| val.clamp(0.0, 100.0)

浮点数验证器

验证器 描述 错误类型 示例
less 排他性上限 LessViolated less= 100.0
less_or_equal 包含性上限 LessOrEqualViolated less_or_equal= 100.0
greater 排他性下限 GreaterViolated greater= 0.0
greater_or_equal 包含性下限 GreaterOrEqualViolated greater_or_equal= 0.0
有限 检查NaN和无穷大 有限违反 有限
predicate 自定义谓词 PredicateViolated predicate = |val| val!= 50.0

浮点数可推导特性

以下是可以为基于浮点数的类型推导出的特性:DebugCloneCopyPartialEqEqPartialOrdOrdFromStrAsRefDerefIntoFromTryFromHashBorrowDisplayDefaultSerializeDeserialize

如果验证规则保证了排除NaN,则还可以推导出EqOrd。这可以通过应用finite验证来实现。例如

#[nutype(
    validate(finite),
    derive(PartialEq, Eq, PartialOrd, Ord),
)]
struct Size(f64);

其他内部类型和泛型

对于任何其他类型,都可以使用with定义自定义清理器,并使用predicate定义自定义验证器。

use nutype::nutype;

#[nutype(
    derive(Debug, PartialEq, AsRef, Deref),
    sanitize(with = |mut guests| { guests.sort(); guests }),
    validate(predicate = |guests| !guests.is_empty() ),
)]
pub struct GuestList(Vec<String>);

也可以使用泛型

#[nutype(
    sanitize(with = |mut v| { v.sort(); v }),
    validate(predicate = |vec| !vec.is_empty()),
    derive(Debug, PartialEq, AsRef, Deref),
)]
struct SortedNotEmptyVec<T: Ord>(Vec<T>);

let wise_friends = SortedNotEmptyVec::try_new(vec!["Seneca", "Zeno", "Plato"]).unwrap();
assert_eq!(wise_friends.as_ref(), &["Plato", "Seneca", "Zeno"]);
assert_eq!(wise_friends.len(), 3);

let numbers = SortedNotEmptyVec::try_new(vec![4, 2, 7, 1]).unwrap();
assert_eq!(numbers.as_ref(), &[1, 2, 4, 7]);
assert_eq!(numbers.len(), 4);

自定义清理器

可以使用with选项设置自定义清理器。自定义清理器是一个接收内部类型值的所有权和返回清理后值的函数或闭包。

例如,这个

#[nutype(sanitize(with = new_to_old))]
pub struct CityName(String);

fn new_to_old(s: String) -> String {
    s.replace("New", "Old")
}

等于以下这个

#[nutype(sanitize(with = |s| s.replace("New", "Old") ))]
pub struct CityName(String);

并且工作方式相同

let city = CityName::new("New York");
assert_eq!(city.into_inner(), "Old York");

自定义验证器

以类似的方式可以定义自定义验证器,但验证函数接收一个引用并返回bool。将其视为谓词。

#[nutype(validate(predicate = is_valid_name))]
pub struct Name(String);

fn is_valid_name(name: &str) -> bool {
    // A fancy way to verify if the first character is uppercase
    name.chars().next().map(char::is_uppercase).unwrap_or(false)
}

食谱

推导Default

#[nutype(
    derive(Default),
    default = "Anonymous",
)]
pub struct Name(String);

在浮点类型上推导EqOrd

使用nutype,如果设置了finite验证,则可以推导出EqOrdfinite验证确保有效的值排除NaN

#[nutype(
    validate(finite),
    derive(PartialEq, Eq, PartialOrd, Ord),
)]
pub struct Weight(f64);

使用 new_unchecked 破坏约束

虽然不鼓励,但可以通过启用new_unchecked crate功能并将类型标记为new_unchecked来绕过约束

#[nutype(
    new_unchecked,
    sanitize(trim),
    validate(len_char_min = 8)
)]
pub struct Name(String);

// Yes, you're forced to use `unsafe` here, so everyone will point fingers at YOU.
let name = unsafe { Name::new_unchecked(" boo ".to_string()) };

// `name` violates the sanitization and validation rules!!!
assert_eq!(name.into_inner(), " boo ");

功能标志

  • arbitrary - 启用派生 arbitrary::Arbitrary
  • new_unchecked - 启用生成不安全的 ::new_unchecked() 函数。
  • regex - 允许在基于字符串的类型上使用 regex = 验证。注意:您的crate还必须在依赖项中显式包含 regexlazy_static
  • serde - 与 serde crate 集成。允许派生 SerializeDeserialize 特性。
  • schemars08 - 允许派生 JsonSchema 特性,来自 schemars crate。注意,目前验证规则不被遵守。
  • std - 默认启用。使用 default-features = false 禁用。

何时 nutype 适合您?

  • 如果您喜欢 newtype 模式,并且您喜欢利用Rust类型系统来强制执行业务逻辑的正确性。
  • 如果您想使用类型系统来保持不变性
  • 如果您是DDD爱好者,nutype是使您的领域模型更具表达力的绝佳助手。
  • 您希望在保证质量的同时快速原型化。

何时 nutype 不太适合?

  • 您非常关心编译时间(nutype依赖于proc宏的重度使用)。
  • 您认为元编程太过于隐秘的魔法。
  • IDE可能无法很好地为您提供关于proc宏的提示。
  • nutype的设计可能迫使您执行不必要的验证(例如,从数据库加载数据时),这可能会对您追求极端性能的目标产生负面影响。

支持乌克兰军队

今天我住在柏林,我有幸过着身体安全的 生活。但我是一名乌克兰人。我生命的前25年在乌克兰第二大城市 哈尔科夫 度过,离俄罗斯边界有60公里。今天大约 三分之一的我的家乡被俄国人摧毁。我的父母、亲戚和朋友们不得不在炮击和空袭中生存,在地下室里生活了一个多月。

其中一些人设法逃到了欧盟。其他人正在哈尔科夫尝试过着“正常的生活”,在那里履行日常职责。还有一些人现在正在前线,每秒钟都冒着生命危险保护其他人。

我鼓励您向 谢尔盖·普里图拉慈善基金会 捐款。只需选择您喜欢的项目并进行捐赠即可。这是最知名的基金会之一,您可以观看一个 简短的纪录片 了解它。您对乌克兰军队的贡献是对我平静的捐献,这样我可以有更多的时间开发这个项目。

谢谢。

类似项目

  • prae - 一个非常相似的crate,旨在解决相同的问题,但采用略有不同的方法。
  • bounded-integer - Rust的有限整数。
  • refinement - 基于泛型的类型安全精炼类型的便捷创建。
  • semval - Rust的语义验证。
  • validator - Rust结构体的简单验证(由宏驱动)。

许可证

MIT © 谢尔盖·波塔波夫

依赖项

~0.9–1.9MB
~34K SLoC