#configuration #macro #struct-fields

optional_struct

定义一个宏,从结构体生成另一个只包含 Option 字段的结构的包

13 个不稳定版本 (4 个破坏性更新)

0.5.2 2024 年 7 月 31 日
0.5.1 2024 年 7 月 31 日
0.4.1 2023 年 12 月 31 日
0.3.2 2023 年 12 月 11 日
0.1.3 2017 年 10 月 24 日

#16 in 配置

Download history 1352/week @ 2024-05-03 2431/week @ 2024-05-10 1383/week @ 2024-05-17 963/week @ 2024-05-24 1134/week @ 2024-05-31 1230/week @ 2024-06-07 1061/week @ 2024-06-14 1596/week @ 2024-06-21 1383/week @ 2024-06-28 1110/week @ 2024-07-05 1693/week @ 2024-07-12 2069/week @ 2024-07-19 2176/week @ 2024-07-26 2262/week @ 2024-08-02 1524/week @ 2024-08-09 864/week @ 2024-08-16

7,095 每月下载量
8 个包中使用了 (7 个直接使用)

Apache-2.0

15KB
84

OptionalStruct

Crates.io

快速入门

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

但存在明显的局限性。这个包旨在填补这个空白,允许可选值,并提供一种简单的方法将来自不同来源的值应用于构建我们的配置。

使用 optional_struct,可以定义所需的配置,就像它将被使用一样,并且只使用生成的结构体来处理配置/缺失值/默认值。

如何使用

optional_struct 生成一个包含与被标记的结构体相同字段的另一个结构体,但字段被 Option 包装。新结构体上的一个函数允许将它的值应用到原始结构体上(如果 Option 不是 None)。这可以多次调用,以从不同的来源应用配置,同时让调用者完全控制如何设置值,因为生成的结构体可以轻松地操作和传递,在构建最终配置之前。

特性

  1. 重命名生成的结构体
#[optional_struct(HeyU)]
struct Config();

fn main() {
    let me = HeyU();
}
  1. 处理递归类型
#[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,
}
  1. 处理原始结构体中的 Option(通过忽略它们)
#[optional_struct]
struct Foo {
    bar: Option<u8>,
}

fn main() {
    let opt_f = OptionalFoo { bar: Some(1) };
}
  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) };
}
  1. 更改默认的包装行为
#[optional_struct(OptionalFoo, false)]
struct Foo {
    bar: u8,
    #[optional_wrap]
    baz: i8,
}

fn main() {
    let opt_f = OptionalFoo { bar: 1, baz: None };
}
  1. 将 serde 的 skip_serializing_if = ""Option::is_none" 属性添加到生成的结构体中

通过将属性 #[optional_serde_skip_none] 添加到字段中,生成的结构体将具有相同的字段标签 #[serde(skip_serializing_if = "Option::is_none")]。此属性使 serde 在 Option 的值为 none 时(而不是在序列化到 json 时保存例如 "value" = null)完全跳过字段。

applybuildtry_build

这三个函数用于构建结构的最终版本,通过合并“左侧”的值。

函数的签名(伪代码)

impl OptionalStruct {
    fn build(self, s: Struct) -> Struct;
    fn try_build(self) -> Result<Struct, OptionalStruct>;
    fn apply(self, other: OptionalStruct) -> OptionalStruct;
}

这些函数的功能

  1. build 接收一个真实的 Struct 并根据 OptionalStruct 中设置的哪些字段设置其所有字段。缺失的字段保持不变。具有强制包装属性的 Option 字段将不会覆盖值,例如 Some(1) 不会覆盖 Some(2)(请参阅初始示例以获取具体示例)。

  2. try_build 尝试从 OptionalStruct 中构建整个 Struct,如果一切顺利,则返回 Ok(Struct),如果缺少某些内容,则返回初始的 OptionalStructErr(OptionalStruct)

  3. apply 接收一个 OptionalStruct 作为参数并将其字段应用于 左侧(即 self)。如果 selfother 都定义了某些内容,则取 other 的值。如果 self 定义了某些内容但 other 没有定义,则保留该值。当然,如果 self 没有定义但 other 定义了,则使用该值。

依赖项

~0.4–1MB
~22K SLoC