3个版本
0.5.2 | 2024年7月31日 |
---|---|
0.5.1 | 2024年7月31日 |
0.5.0 | 2024年7月18日 |
在过程宏中排名2072
每月下载量1,394
被optional_struct使用
26KB
688 行
OptionalStruct
快速开始
从tests/builder.rs
文件开始
use optional_struct::*;
#[optional_struct]
#[derive(Eq, PartialEq, Debug)]
struct Foo {
paf: u16,
bar: Option<u8>,
#[optional_wrap]
baz: Option<char>,
#[optional_rename(OptionalMiaou)]
#[optional_wrap]
miaou: Miaou,
}
#[optional_struct]
#[derive(Eq, PartialEq, Debug)]
struct Miaou {
a: i8,
b: i16,
}
#[test]
fn test_builder() {
let default = Foo {
paf: 12,
bar: None,
baz: Some('a'),
miaou: Miaou {
a: 1,
b: -1,
},
};
let first = OptionalFoo {
paf: Some(42),
bar: Some(7),
baz: Some(None),
miaou: None,
};
let second = OptionalFoo {
paf: Some(24),
bar: None,
baz: Some(Some('c')),
miaou: Some(OptionalMiaou {
a: Some(2),
b: None,
}),
};
let collapsed = first.apply(second).build(default);
assert_eq!(collapsed, Foo {
paf: 24,
bar: Some(7),
baz: Some('c'),
miaou: Miaou { a: 2, b: -1 },
});
}
目标
由于Rust没有默认参数,并且一些工具在反序列化数据时(例如serde)对配置值非常严格,缺失的配置值处理起来可能相当令人沮丧。例如
#[derive(Deserialize)]
struct Config {
log_file: PathBuf,
}
如果我们从文件中读取配置,而log_file
未指定,serde将无法创建结构体。虽然serde 提供了设置字段默认值的方法,例如
#[derive(Deserialize)]
struct Config {
#[serde(default = "get_next_log_filename")]
log_file: PathBuf,
}
存在明显的局限性。这个crate旨在填补这个空白,通过允许可选值,并提供一种简单的方法来应用从不同来源获得的价值来构建我们的配置。
使用optional_struct
,可以像使用它一样定义所需的配置,并且仅使用生成的结构体来处理配置/缺失值/默认值。
如何
宏optional_struct
生成一个包含与标记相同字段的结构体,但将其包装在Option
中。新结构体上的一个函数允许将其值应用于原始结构体(如果Option
不是None
)。这可以多次调用,以应用来自不同来源的配置,同时允许调用者完全控制如何设置值,因为生成的结构体可以轻松操作并在构建最终配置之前传递。
特性
- 重命名生成的结构体
#[optional_struct(HeyU)]
struct Config();
fn main() {
let me = HeyU();
}
- 处理递归类型
#[optional_struct]
struct Foo {
// Replaces Option<Bar> with OptionalBar
// To generate Option<OptionalBar> instead, add an extra #[optional_wrap]
// as described later
#[optional_rename(OptionalBar)]
bar: Bar,
}
- 处理原始结构体中的
Option
(通过忽略它们)
#[optional_struct]
struct Foo {
bar: Option<u8>,
}
fn main() {
let opt_f = OptionalFoo { bar: Some(1) };
}
- 强制包装(或不包装)字段
#[optional_struct]
struct Foo {
#[optional_skip_wrap]
bar: char,
// Useless here since we wrap by default
#[optional_wrap]
baz: bool,
}
fn main() {
let opt_f = OptionalFoo { bar: 'a', baz: Some(false) };
}
- 更改默认包装行为
#[optional_struct(OptionalFoo, false)]
struct Foo {
bar: u8,
#[optional_wrap]
baz: i8,
}
fn main() {
let opt_f = OptionalFoo { bar: 1, baz: None };
}
- 将serde的
skip_serializing_if = "Option::is_none"
属性添加到生成的结构体中
通过将属性 #[optional_serde_skip_none]
添加到字段中,生成的结构体会具有相同的字段标记 #[serde(skip_serializing_if = ""Option::is_none"")]
。此属性使 serde 在 Option
的值为 none 时完全跳过字段(而不是在序列化为 json 时保存例如 ""value"" = null
)。
apply
、build
和 try_build
这三个函数用于通过合并“左侧”的值来构建结构的最终版本。
函数的签名(伪代码)
impl OptionalStruct {
fn build(self, s: Struct) -> Struct;
fn try_build(self) -> Result<Struct, OptionalStruct>;
fn apply(self, other: OptionalStruct) -> OptionalStruct;
}
这些函数的作用
-
build
接收一个实际的Struct
并根据OptionalStruct
中设置的哪些字段来设置其所有字段。未设置的字段保持不变。具有强制包装属性的Option
字段将不会覆盖值,例如Some(1)
不会覆盖Some(2)
(请参阅初始示例以获取具体的情况)。 -
try_build
尝试从OptionalStruct
中构建整个Struct
,如果一切顺利,则返回Ok(Struct)
,如果缺少内容,则在Err(OptionalStruct)
中返回初始的OptionalStruct
。 -
apply
接收一个OptionalStruct
作为参数,并将其字段应用于 左侧(即self
)。如果self
和other
都定义了某些内容,则取other
的值。如果self
定义了某些内容但other
没有定义,则保留该值。当然,如果self
没有定义但other
定义了,则使用该值。
依赖项
~245–680KB
~16K SLoC