8 个版本

0.1.7 2024 年 2 月 16 日
0.1.6 2023 年 12 月 30 日
0.1.0 2022 年 12 月 22 日

270Rust 模式

Download history 35/week @ 2024-03-08 90/week @ 2024-03-15 7/week @ 2024-03-29

每月 95 次下载

Apache-2.0

30KB
227 代码行

SmartErr

SmartErr,一个错误处理库,引入了在库和/或应用程序中引发、收集和分配特定领域错误的几个便捷方法。

使用 SmartErr,您将能够

  • 在常规类型(数字、字符串、布尔值、OptionResult 等)上使用 raisethrow 方法作为错误源来引发错误。查看 引发错误 部分,以获取更多详细信息。
  • 定义函数发出的确切错误集,或为公共 API 引入全局集。
  • 传递调用函数未处理的错误,或使用为特定情况生成的特殊 handle 方法完全或部分地处理这些错误。
  • 上下文 附带到错误上,并为新错误和非处理的错误定义特定消息。定义错误 部分描述了这种方法。

快速概述

请参阅下方的 示例

引发错误

一些函数可能返回简单类型而不是 Result。库的这一部分致力于处理此类结果。简单值使用 Throwable 特性中的 raise(或 raise_with)和 throw(或 throw_with)方法进行转换。如果源不是作为失败处理,则 raise 发出错误,如果它已经在失败状态,则 throw 发出错误。以下是具有 Throwable 特性实现的类型的参考表

源类型 throw 错误条件 raise 条件
数字(i32、usize 等) != 0 == 0
bool false true
字符串(&str、String 等) is_empty() !is_empty()
Option Some None
Result Ok Err

如果不满足条件,则返回原始值。

假设有一些数值输入。使用 Throwable 将其转换为 Result

fn raw_throwable(val: i32) -> Result<i32, RawError<i32>> {
    val.throw()
    //val.throw_with("raw error")
}

#[test]
pub fn test_throwable()  {
    assert_eq!(raw_throwable(0).unwrap(), 0);
    assert_eq!(raw_throwable(10).is_err(), true);
    assert_eq!(format!("{}", raw_throwable(10).unwrap_err()), 
        "raw error { value: 10 }"
    );
}

使用 Erroneous 转换

smarterr_fledged!(DomainErrors{
    DomainError<<i32>> -> "Domain error"
});

fn raw_erroneous(val: i32) -> Result<i32, RawError<i32>> {
    val.throw_err(RawError::new_with(val, "raw error"))
}

fn raw_erroneous_then(val: i32) -> Result<i32, RawError<i32>> {
    val.throw_then(|v| RawError::new_with(v, "raw error"))
}

fn raw_erroneous_ctx(val: i32) -> Result<i32, DomainErrors> {
    val.throw_ctx(DomainErrorCtx{})
}

#[test]
pub fn test_erroneous()  {
    assert_eq!(raw_erroneous(0).unwrap(), 0);
    assert_eq!(raw_erroneous_then(10).is_err(), true);
    assert_eq!(format!("{}", raw_erroneous_then(10).unwrap_err()), 
        "raw error { value: 10 }"
    );
    assert_eq!(format!("{}", raw_erroneous_ctx(10).unwrap_err()), 
        "Domain error, caused by: raw error { value: 10 }"
    );
}

领域错误处理在 定义错误 部分中描述。

raise 也可以用作 throw 的替代方案。唯一的区别是 raise 的条件与 throw 相反。

定义错误

定义错误有两种方法

  • "完备":域错误在全局范围内定义(在选择的可视范围内)
  • 基于函数:错误集对每个函数都是特定的

两者语法相同,完备风格的继承有限。

完备风格

完备风格主要适用于独立域特定的错误。以下示例展示了 smarterr_fledged 宏的使用,该宏旨在支持完备方法。

smarterr_fledged!(pub PlanetsError {
    MercuryError{} -> "Mercury error",
    pub MarsError{ind: usize} -> "Mars Error",
    SaturnError<<i32>> -> "Saturn error",
    EarthError<ParseIntError> -> "EarthError",
});

首先应该定义错误集的名称以及(可选)其可见性。然后是花括号内的错误定义。它遵循以下简单模式

    [visibility] name[<[< source error type >]>] [{ context struct }] -> "error message",

以下代码将在底层生成(此处省略了次要细节,并仅显示了 MarsError

#[derive(Debug)]
pub enum PlanetsError {
    MercuryError(MercuryError),
    MarsError(MarsError),
    SaturnError(SaturnError),
    EarthError(EarthError),
}

/* cutted: Error and Display implementations for PlanetsError */

#[derive(Debug)]
pub struct MarsError {
    ctx: MarsErrorCtx,
}

impl MarsError {
    pub fn new<ES>(_src: ES, ctx: MarsErrorCtx) -> Self {
        MarsError { ctx }
    }
    pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
    pub fn default_message(&self) -> &'static str {
        "Mars Error"
    }
}

/* cutted: Display implementation for MarsError */

#[derive(Debug)]
#[allow(dead_code)]
pub struct MarsErrorCtx {
    ind: usize,
}

impl<ES: std::fmt::Debug + 'static> smarterr::IntoError<PlanetsError, ES> for MercuryErrorCtx {
    fn into_error(self, source: ES) -> PlanetsError {
        PlanetsError::MercuryError(MercuryError::new(source, self))
    }
}
impl<ES: std::fmt::Debug + 'static> smarterr::IntoError<PlanetsError, ES> for MarsErrorCtx {
    fn into_error(self, source: ES) -> PlanetsError {
        PlanetsError::MarsError(MarsError::new(source, self))
    }
}
impl smarterr::IntoError<PlanetsError, i32> for SaturnErrorCtx {
    fn into_error(self, source: i32) -> PlanetsError {
        PlanetsError::SaturnError(SaturnError::new(source, self))
    }
}
impl smarterr::IntoError<PlanetsError, ParseIntError> for EarthErrorCtx {
    fn into_error(self, source: ParseIntError) -> PlanetsError {
        PlanetsError::EarthError(EarthError::new(source, self))
    }
}

生成代码的几个关键细节

  1. 域错误集是枚举。
  2. 对于每个错误(枚举值),将创建一个额外的结构,其名称与错误的名称相同。
  3. 如果已定义上下文,则将创建相应的结构。其名称为错误名称后跟 Ctx 后缀。

上面的示例相当简单,并未展示源错误定义。通常您会设置源错误。有几种可能性

定义示例
无源 MercuryError-> "水星错误"
动态 Error MercuryError<> -> "水星错误"
特定错误 MercuryError<SourceError> -> "水星错误"
动态 Debug MercuryError<<>> -> "水星错误"
特定 Debug MercuryError<<i32>> -> "水星错误"

引发错误非常简单

"z12".parse::<i32>().throw_ctx(EarthErrorCtx{})

注意,这是通过 *Ctx 结构(此例中的 EarthErrorCtx)完成的,它实现了 smarterr::IntoError 特性。

基于函数的风格

这是一个常见的情况,其中多个函数相互调用。通常,每个函数返回自己的错误集和从被调用函数中的一些未处理的错误。通常,可以使用一个错误集(枚举)来处理所有函数,但这并不完全正确。函数的合约不准确,因为它们返回了公共枚举的子集,并且某些错误可能永远不会发生。如果某些函数是公共的,那么可能存在一个问题,即从内部隐藏未使用的错误。

更精确的解决方案是为每个函数定义自己的错误集。但这除了难度较大外,还产生了另一个问题。某些错误可能在每个错误集中定义了多次,并且需要它们之间的映射,即使它们是相同的。SmartErr 通过幕后提供所有必要的优化功能来解决这个问题。

为此,引入了两个额外的关键字

  • from 关键字。如果需要重新抛出调用函数中的一些错误,则应使用它。
  • handle 关键字。它用于标记将被处理的来自调用函数的错误。

以下是其工作原理

FBS 示例

#[smarterr(
    AlfaError{ind: i32, ext: String} -> "Alfa error",
    BetaError<>{ind: i32} -> "Beta error",
    BetaWrappedError<ParseIntError> -> "Beta Wrapped Error",
    GammaError<<>>{ext: String} -> "Gamma error",
    GammaWrappedError<<i32>>{ext: String} -> "Gamma Wrapped error",
)]
pub fn greek_func(err_ind: usize) -> String {
    let ok_str = "All is ok".to_string();
    let err_str = "Error raised".to_string();
    let ext = "ext".to_string();
    match err_ind {
        0 => Ok(ok_str),
        1 => err_str.raise_ctx(AlfaErrorCtx { ind: -1, ext }),
        2 => "z12".parse::<i32>().throw_ctx(BetaErrorCtx { ind: -2 }).map(|_| ok_str),
        3 => "z12".parse::<i32>().throw_ctx(BetaWrappedErrorCtx {}).map(|_| ok_str),
        4 => err_str.raise_ctx(GammaErrorCtx { ext }),
        5 => 5000000.throw_ctx(GammaWrappedErrorCtx { ext }).map(|_| ok_str),
        _ => Ok(ok_str),
    }
}

#[smarterr(
    from GreekFuncError { 
        AlfaError, BetaError<>, BetaWrappedError<ParseIntError>, GammaError<<>>, 
        handled GammaWrappedError
    },
    XError{ind: i32, ext: String} -> "X error",
    YError{ind: i32} -> "Y error",
    pub ZError<<String>>{ind: usize} -> "Z Error",
)]
fn latin_func(err_ind: usize) {
    greek_func(err_ind).handle(|h| match h {
        GreekFuncErrorHandled::GammaWrappedError(data) => 
            data.ctx.ext.throw_ctx(ZErrorCtx { ind: err_ind }),
    })?;
    Ok(())
}

#[smarterr(
    from GreekFuncError {
        AlfaError -> "Imported Alfa error",
        BetaError<> -> "Imported Beta error",
        BetaWrappedError<std::num::ParseIntError> -> "Imported Beta Wrapped Error",
        handled GammaError,
        handled GammaWrappedError,
    },
    from LatinFuncError {
        AlfaError, BetaError<>, BetaWrappedError<ParseIntError>, ZError<<String>>, 
        handled { GammaError, XError, YError }
    },
    FirstError{ind: i32, ext: String} -> "First error",
    SecondError{ind: i32} -> "Second error",
    ThirdError{} -> "Third Error",
)]
pub fn numeric_func(err_ind: usize) -> String {
    let g = greek_func(err_ind).handle(|h| match h {
        GreekFuncErrorHandled::GammaWrappedError(e) => 
            e.ctx.ext.clone().raise_ctx(FirstErrorCtx{ind: err_ind as i32, ext: e.ctx.ext}),
        GreekFuncErrorHandled::GammaError(e) => 
            e.ctx.ext.raise_ctx(SecondErrorCtx{ ind: err_ind as i32 }), 
    })?;

    latin_func(err_ind).handle(|h| match h {
        LatinFuncErrorHandled::XError(e)=>
            ().raise_ctx(FirstErrorCtx{ ind: err_ind as i32, ext: e.ctx.ext }),
        LatinFuncErrorHandled::YError(e)=>
            ().raise_ctx(SecondErrorCtx{ ind: e.ctx.ind }),
        LatinFuncErrorHandled::GammaError(_) => Ok(())
    })?;

    let t = ().raise_ctx(MarsErrorCtx{ind: err_ind});
    t.throw_ctx(BetaErrorCtx{ ind: err_ind as i32 })?;

    Ok(g)
}

也可以为方法定义错误。唯一的区别是,这些错误必须在实现块外部定义。smarterr_mod 宏用于此目的。它应作为实现块的属性使用。模块的名称应作为参数传递。

以下是一个示例

#[smarterr_mod(test_err)]
impl Test {
    #[smarterr(InitFailed{pub a: String, pub b: String} -> "Init error")]
    pub fn new(a: &str, b: &str) -> Self {
        Ok(Self {
            a: a.parse()
                .throw_ctx(test_err::InitFailedCtx { a: a.to_string(), b: b.to_string() })?,
            b: b.parse()
                .throw_ctx(test_err::InitFailedCtx { a: a.to_string(), b: b.to_string() })?,
        })
    }
}

无运行时依赖