原文链接 https://edgarluque.com/blog/wrapping-errors-in-rust
当我在开发 paypal-rs
库时, 注意到了错误处理不是很好.
在这个库中, 我需要处理 2 中不同类型的错误:
- HTTP 相关错误,
reqwest::Error
- Paypal API 错误, 用了一个自定义结构
PaypalError
表示
一开始我用了 anyhow
, 但是我发现这个库只使用于应用程序开发, 而不适用于开发库.
一个好的解决方案是将错误重新封装包裹成一个类型提供给库的使用者.
包裹错误
首先我们要知道哪些错误需要被包裹, 在我的例子中有 PaypalError
:
// 表示已 Paypal 错误
#[derive(Debug, Serialize, Deserialize)]
pub struct PaypalError {
//...
}
还有一个错误类型来自于 reqwest
库: reqwest::Error
.
首先我们定义一个枚举来表示库中所有可能出现的错误:
#[derive(Debug)]
pub enum ResponseError {
ApiError(PaypalError),
HttpError(reqwest::Error)
}
对于任意的错误类型, 我们需要实现 fmt::Display
和 Error
trait:
impl fmt::Display for ResponseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResponseError::ApiError(e) => write!(f, "{}", e),
ResponseError::HttpError(e) => write!(f, "{}", e),
}
}
}
impl Error for ResponseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ResponseError::ApiError(e) => Some(e),
ResponseError::HttpError(e) => Some(e),
}
}
}
现在我们可以将所有返回 Result
类型的函数的错误类型改成 ResponseError
.
但是这个时候我们还不能直接使用 ?
来将错误类型自动转换成 ResponseError
, 因为我们还没有实现 From<reqwest::Error> for ResponseError
和 From<PaypalError> for ResponseError
. 一个解决方法是使用 Result
的 map_err
方法:
/// func_call_returns_error() 返回一个 Result<(), reqwest::Error>
pub fn some_func() -> Result<(), ResponseError> {
func_call_returns_error().map_err(ResponseError::HttpError)?;
Ok(())
}
为了可以使用 ?
语法糖, 我们可以添加 From<reqwest::Error> for ResponseError
和 From<PaypalError> for ResponseError
.
impl From<PaypalError> for ResponseError {
fn from(e: PaypalError) -> Self {
ResponseError::ApiError(e)
}
}
impl From<reqwest::Error> for ResponseError {
fn from(e: reqwest::Error) -> Self {
ResponseError::HttpError(e)
}
}
那么之前的例子可以改写成:
pub fn some_func() -> Result<(), ResponseError> {
func_call_returns_error()?;
Ok(())
}
使用 thiserror
快速实现
thiserror
库提供了一些宏, 可以帮助我们减少手动实现上面各个 trait, 例如:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ResponseError {
/// A paypal api error.
#[error("api error {0}")]
ApiError(#[from] PaypalError),
/// A http error.
#[error("http error {0}")]
HttpError(#[from] reqwest::Error)
}
使用 thiserror
库不仅比上面我们手动实现简单, 而且对库的使用者也是透明的, 他们不需要知道我们在库中 thiserror
.