#thiserror #try-from #derive-error #convert #derive #proc-macro

try_convert

自动生成 TryFrom 和错误类型,以最小化模板代码

2 个版本

0.1.1 2024 年 2 月 11 日
0.1.0 2024 年 2 月 11 日

#1791 in Rust 模式

MIT 许可证

38KB
639

try_convert

crates.io docs.rs

自动生成 TryFrom 和错误类型,以最小化模板代码。

用法

将此添加到您的 Cargo.toml

[dependencies]
try_convert = "0.1"
thiserror = "1.0" # Optional, but recommended

功能

  • thiserror (默认启用): 使用 thiserror 为错误类型派生 Error

容器属性

  • #[derive(TryConvert)]: 为结构体或枚举派生 TryFrom
  • #[try_convert(from = "source::Struct")]: 为注解类型派生 TryFrom<source::Struct>
  • #[try_convert(from = "source::Enum", exclude("D { .. }", "E ( .. )"))]: 为注解类型派生 TryFrom<source::Enum>,排除模式 D { .. }E (..)
  • #[try_convert(from = "source::Enum", error = "MyEnumError")]:为注释类型推导出 TryFrom<source::Enum>,使用 MyEnumError 作为错误类型。
  • #[try_convert(from = "source::Struct", error = "MyEnumError", description = "the struct is invalid")]:为注释类型推导出 TryFrom<source::Struct>,使用 MyEnumError 作为错误类型,描述为 "the struct is invalid"。

当未指定 error 时,错误类型将被生成为 AnnotatedTypeFromSourceTypeError

字段属性

  • #[try_convert(from = "i32")]:使用 TryFrom<i32> 转换注释字段。您还可以指定 errordescription 以覆盖默认的错误变体和消息。如果未指定 error,错误变体将为 <field>To<type>
  • #[try_convert(unwrap, from = "Option<i32>")]:尝试展开源字段,如果其为 None 则生成错误。您还可以指定 errordescription 以覆盖默认的错误变体和消息。如果未指定 error,错误变体将为 SomeFieldIsNone
  • #[try_convert(map, from = "Vec<i32>")]:将源 Vec 的每个元素映射到目标类型,使用 TryFrom。您还可以指定 errordescription 来覆盖默认的错误变体和消息。
  • #[try_convert(filter = "|x| x.is_empty()", error = "EmptyString", description = "字符串为空")]:过滤源字段,如果过滤器返回 false 则生成错误。需要指定 error
  • #[try_convert(get = "from.some_string")]:从源字段 some_string 获取值。在所有其他操作之前执行。只能指定一次。

可以将这些属性链在一起以对单个字段执行多个操作。

变体属性

  • #[try_convert(from = "source::Enum::A(f0)")]:当源为 source::Enum::A(f0) 时,将其转换为注解变体。

示例

以下代码

mod source {
    pub struct Struct {
        pub some_passthrough: usize,
        pub some_string: String,
        pub some_option: Option<i32>,
        pub some_vec: Option<Vec<i32>>,
        pub some_enum: Enum,
    }

    pub(crate) enum Enum {
        A(String),
        B { a: i32, b: i32 },
        C,
        D { c: i32 },
        E,
    }
}

use try_convert::TryConvert;

#[derive(TryConvert)]
#[try_convert(from = "source::Struct")]
pub struct MyStruct {
    some_passthrough: usize,
    #[try_convert(unwrap, from = "Option<i32>")]
    some_option: i32,
    #[try_convert(unwrap, from = "Option<Vec<i32>>")]
    #[try_convert(map, from = "Vec<i32>", error = "CustomErrorVariant")]
    some_vec: Vec<i16>,
    #[try_convert(from = "source::Enum")]
    some_enum: MyEnum,
    #[try_convert(get = "from.some_string")]
    #[try_convert(
        filter = "|x| !x.is_empty()",
        error = "EmptyString",
        description = "the string is empty"
    )]
    some_renamed_string: String,
}

#[derive(TryConvert)]
#[try_convert(from = "source::Enum", exclude("D { .. }", "E"), error = "MyEnumError")]
pub(crate) enum MyEnum {
    #[try_convert(from = "source::Enum::A(f0)")]
    A(String),
    #[try_convert(from = "source::Enum::B { a, b }")]
    B {
        #[try_convert(from = "i32")]
        a: i8,
        #[try_convert(get = "b")]
        #[try_convert(from = "i32")]
        renamed: i16,
    },
    C,
}

将扩展为

#[derive(Debug, thiserror::Error)]
pub enum MyStructFromSourceStructError {
    #[error("some option is none")]
    SomeOptionIsNone,
    #[error("some vec is none")]
    SomeVecIsNone,
    #[error("custom error variant: {0:?}")]
    CustomErrorVariant(<i16 as TryFrom<i32>>::Error),
    #[error("some enum to my enum: {0:?}")]
    SomeEnumToMyEnum(<MyEnum as TryFrom<source::Enum>>::Error),
    #[error("the string is empty")]
    EmptyString,
}

impl TryFrom<source::Struct> for MyStruct {
    type Error = MyStructFromSourceStructError;

    fn try_from(from: source::Struct) -> Result<Self, Self::Error> {
        Ok({
            let some_passthrough = from.some_passthrough.into();
            let some_option = from
                .some_option
                .ok_or(MyStructFromSourceStructError::SomeOptionIsNone)?
                .into();
            let some_vec = from
                .some_vec
                .ok_or(MyStructFromSourceStructError::SomeVecIsNone)?
                .into_iter()
                .map(TryFrom::try_from)
                .collect::<Result<Vec<i16>, _>>()
                .map_err(MyStructFromSourceStructError::CustomErrorVariant)?
                .into();
            let some_enum = <MyEnum as TryFrom<source::Enum>>::try_from(from.some_enum)
                .map_err(MyStructFromSourceStructError::SomeEnumToMyEnum)?
                .into();
            let some_renamed_string = Some(from.some_string)
                .filter(|x| !x.is_empty())
                .ok_or(MyStructFromSourceStructError::EmptyString)?
                .into();
            Self {
                some_passthrough,
                some_option,
                some_vec,
                some_enum,
                some_renamed_string,
            }
        })
    }
}

#[derive(Debug, thiserror::Error)]
pub(crate) enum MyEnumError {
    #[error("a to i 8: {0:?}")]
    AToI8(<i8 as TryFrom<i32>>::Error),
    #[error("renamed to i 16: {0:?}")]
    RenamedToI16(<i16 as TryFrom<i32>>::Error),
    #[error("d")]
    D,
    #[error("e")]
    E,
}

impl TryFrom<source::Enum> for MyEnum {
    type Error = MyEnumError;

    fn try_from(from: source::Enum) -> Result<Self, Self::Error> {
        Ok(
            match from {
                source::Enum::D { .. } => return Err(MyEnumError::D),
                source::Enum::E => return Err(MyEnumError::E),
                source::Enum::A(f0) => {
                    let f0 = f0.into();
                    Self::A(f0)
                }
                source::Enum::B { a, b } => {
                    let a = <i8 as TryFrom<i32>>::try_from(a)
                        .map_err(MyEnumError::AToI8)?
                        .into();
                    let renamed = <i16 as TryFrom<i32>>::try_from(b)
                        .map_err(MyEnumError::RenamedToI16)?
                        .into();
                    Self::B { a, renamed }
                }
                source::Enum::C => Self::C,
            },
        )
    }
}

依赖关系

~1.3–1.8MB
~34K SLoC