#derive #macro #control #error #derived #err-per-field #field-level

err-per-field-derive

为err-per-field crate提供的派生宏

1个不稳定版本

0.1.0 2022年1月14日

#23 in #derived


err-per-field中使用

MIT/Apache

9KB
188

err-per-field

该crate提供了对字段级别错误处理的更精细的控制。

当我们编写一个复杂的大型项目时,我们可能会将大量信息收集到一个单独的结构体中,例如

struct GatheredInformation {
    info1: Info1,
    info2: Info2,
    info3: Info3,
}

fn generate_info1() -> Result<Info1, Error1> { /* ... */ }
fn generate_info2() -> Option<Info2> { /* ... */ }
fn generate_info3() -> Info3 { /* ... */ }

然而,当生成这样的子信息时,我们可能会遇到ResultOption。核心逻辑是在所有子信息成功检索后处理GatheredInformation。如果某些字段未能检索到,例如info2,我们应该通知用户失败,并可能提供其他成功检索到的信息(例如info1info3)给用户。

为了优雅地处理这个问题,动态类型语言如JavaScript可以相当直接

const gathered_information = gather_information();
if (gathered_information.info1 && gathered_information.info2) {
    deal_with_gathered_information(gathered_information);
} else {
    deal_with_error();
}

Rust作为静态类型语言,事情变得复杂

struct GatheredInformationWrapper {
    info1: Result<Info1, Error1>,
    info2: Option<Info2>,
    info3: Info3,
}

let gathered_information_wrapper = gather_information();
if let GatheredInformationWrapper {
    info1: Ok(info1),
    info2: Some(info2),
    info3,
} = gathered_information_wrappper {
    let gathered_information = GatheredInformation {
        info1,
        info2,
        info3,
    };
    deal_with_gathered_information(gathered_information);
} else {
    deal_with_error();
}

我们必须

  1. 定义一个GatheredInformation的包装结构体,其字段为根据生成函数定义的ResultOption;
  2. 在验证检查后构建真正的GatheredInformation;
  3. 将真实收集到的信息传递给下游函数。

这是一个无聊的过程,额外的包装结构体非常分散注意力。

为了处理这个问题,该crate提供了一个相当简单且优雅的方法。

示例

use err_per_field::{ErrPerField, Wrapper};

#[derive(Debug)]
struct AnError;

#[derive(ErrPerField)]
struct Foo {
    pub bar1: u8,
    #[err_per_field(maybe_none)]
    pub bar2: u16,
    #[err_per_field(maybe_error = "AnError")]
    pub bar3: u32,
    pub bar4: u64,
}

fn baz1() -> u8 { 0 }
fn baz2() -> Option<u16> { None }
fn baz3() -> Result<u32, AnError> { Err(AnError) }
fn baz4() -> u64 { 0 }

fn generate_foo() -> Wrapper<Foo> {
    let bar1 = baz1();
    let bar2 = baz2();
    let bar3 = baz3();
    let bar4 = baz4();
    Wrapper::<Foo> {
        bar1,
        bar2,
        bar3,
        bar4,
    }
}

// If we write this in another file, we can simply import `Wrapper` type
// from `err_per_field` crate.
let result: Result<Foo, Wrapper<Foo>> = generate_foo().try_into();
assert!(result.is_err());
match result {
    Ok(foo) => {
        // `foo` is of type `Foo`, and you can directly use it without any worries
    },
    Err(foo_wrapper) => {
        // `foo_wrapper` has the same fields as `foo`, but in different types,
        // such as `foo_wrapper.bar2` is of type `Option<u16>` and
        // `foo_wrapper.bar3` is of type `Result<u32, AnError>`
    }
}

通过使用此crate,我们不需要自己编写包装结构体,我们可以专注于产品的核心逻辑。我们只需要做的是

  1. 使用宏ErrPerField派生核心结构体Foo,并用属性标记可能为ResultOption的字段;
  2. 当生成结构体 Foo 时,使用 Wrapper<Foo> 作为返回类型。这个包装结构体与 Foo 具有相同的字段名,它们的类型可能是我们在声明时标记的 ResultOption
  3. 当使用生成的结构体时,我们可以通过调用 try_into 来验证其字段,如果它是有效的,结果是 Ok,内部类型是 Foo,我们可以直接处理它作为核心逻辑;如果它无效,结果是 Err,内部类型是 Foo 的包装器,我们可以检查每个字段以进行错误处理。

用法

当我们对结构体 Foo 使用 #[derive(ErrPerField)] 时,宏会生成一个包装结构体,我们可以使用 Wrapper<Foo> 来访问它。这个包装结构体与 Foo 具有相同的字段名,对于字段 bar,如果 foo.bar 的类型是 Bar,那么 foo_wrapper.bar 的类型是

  • Result<Bar, AnError> 如果存在字段级属性 #[err_per_field(maybe_error = "AnError")]
  • Option<Bar> 如果存在字段级属性 #[err_per_field(maybe_none)]
  • Bar 否则。

Foo 将自动实现 TryFrom<Wrapper<Foo>>。如果存在字段被 ErrNone 标记,转换失败,结果为 Err,内部值是未更改的 foo 的包装器;否则,转换成功,结果为 Ok,内部类型是最终的 foo,所有字段都从 ResultOption 中提取。

依赖

~1.5MB
~35K SLoC