8 个版本
0.1.7 | 2024 年 2 月 16 日 |
---|---|
0.1.6 | 2023 年 12 月 30 日 |
0.1.0 | 2022 年 12 月 22 日 |
270 在 Rust 模式
每月 95 次下载
30KB
227 代码行
SmartErr
SmartErr,一个错误处理库,引入了在库和/或应用程序中引发、收集和分配特定领域错误的几个便捷方法。
使用 SmartErr,您将能够
- 在常规类型(数字、字符串、布尔值、Option、Result 等)上使用
raise
和throw
方法作为错误源来引发错误。查看 引发错误 部分,以获取更多详细信息。 - 定义函数发出的确切错误集,或为公共 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))
}
}
生成代码的几个关键细节
- 域错误集是枚举。
- 对于每个错误(枚举值),将创建一个额外的结构,其名称与错误的名称相同。
- 如果已定义上下文,则将创建相应的结构。其名称为错误名称后跟
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() })?,
})
}
}