#error #error-message #smart #library

smarterr-macro

智能错误处理库

8个版本

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

#1105过程宏

Download history 15/week @ 2024-07-07 73/week @ 2024-07-28

每月88次下载

Apache-2.0

41KB
663

SmartErr

SmartErr,一个错误处理库,在库和/或应用中引入了多种方便的方法来抛出、收集和分配特定领域的错误。

使用 SmartErr,您将能够

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

快速概述

请参阅下面的示例

抛出错误

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

源类型 throw 发生错误的条件 raise 发生错误的条件
数字(i32、usize等) != 0 == 0
布尔值 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 相反。

定义错误

定义错误有 2 种方法

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

两者语法相同,对于完全风格,继承有限。

完全风格

完全风格主要用于独立域特定的错误。以下示例演示了 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-> "Mercury 错误"
dyn Error MercuryError<> -> "Mercury 错误"
特定错误 MercuryError<SourceError> -> "Mercury 错误"
dyn Debug MercuryError<<>> -> "Mercury 错误"
特定 Debug MercuryError<<i32>> -> "Mercury 错误"

引发错误非常简单

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

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

基于函数的风格

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

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

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

  • 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() })?,
        })
    }
}

依赖关系

~0.8–1.3MB
~25K SLoC