Rust错误处理库actix-web-thiserror的使用:为actix-web框架提供自定义错误类型和thiserror集成支持

Rust错误处理库actix-web-thiserror的使用:为actix-web框架提供自定义错误类型和thiserror集成支持

简介

actix-web-thiserror是一个扩展thiserror crate功能的库,能够自动返回适当的actix-web响应。

License Contributors GitHub Repo stars crates.io

错误定义示例

use actix_web_thiserror::ResponseError;
use thiserror::Error;

#[derive(Debug, Error, ResponseError)]
pub enum Base64ImageError {
  #[response(reason = "INVALID_IMAGE_FORMAT")]
  #[error("invalid image format")]
  InvalidImageFormat,
  #[response(reason = "INVALID_STRING")]
  #[error("invalid string")]
  InvalidString,
}

错误实现示例

pub async fn error_test() -> Result<HttpResponse, Error> {
  Err(Base64ImageError::InvalidImageFormat)?
}

错误响应示例

reason是一个字符串,可以以某种形式提供给客户端来解释错误(如果适用)。这里它是一个可以本地化的枚举。

{
    "result": 0,
    "reason": "INVALID_IMAGE_FORMAT"
}

错误日志记录

当错误通过HTTP响应返回时,错误文本会自动打印到日志中。

Apr 23 02:19:35.211 ERROR Response error: invalid image format
    Base64ImageError(InvalidImageFormat), place: example/src/handler.rs:5 example::handler

完整示例代码

use actix_web::{web, App, HttpResponse, HttpServer, Error};
use actix_web_thiserror::ResponseError;
use thiserror::Error;

// 1. 定义自定义错误类型
#[derive(Debug, Error, ResponseError)]
pub enum MyError {
    #[response(reason = "NOT_FOUND")]
    #[error("resource not found")]
    NotFound,
    
    #[response(reason = "UNAUTHORIZED")]
    #[error("unauthorized access")]
    Unauthorized,
    
    #[response(reason = "INTERNAL_ERROR")]
    #[error("internal server error")]
    InternalError,
}

// 2. 创建会返回这些错误的路由处理函数
async fn test_route() -> Result<HttpResponse, Error> {
    // 模拟一个错误情况
    Err(MyError::NotFound)?
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/test", web::get().to(test_route))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

安装

在项目目录中运行以下Cargo命令:

cargo add actix-web-thiserror

或者在Cargo.toml中添加以下行:

actix-web-thiserror = "0.2.7"

特性

  1. 与thiserror无缝集成,简化错误定义
  2. 自动生成适当的actix-web响应
  3. 内置错误日志记录
  4. 支持自定义错误消息和原因代码
  5. 轻量级实现(仅3.4 KiB)

完整示例demo

以下是一个更完整的示例,展示了如何在真实项目中使用actix-web-thiserror:

use actix_web::{get, post, web, App, HttpResponse, HttpServer, Error};
use actix_web_thiserror::ResponseError;
use thiserror::Error;
use serde::Serialize;

// 自定义错误类型
#[derive(Debug, Error, ResponseError)]
pub enum ApiError {
    #[response(reason = "USER_NOT_FOUND")]
    #[error("user not found")]
    UserNotFound,
    
    #[response(reason = "INVALID_INPUT")]
    #[error("invalid input data")]
    InvalidInput,
    
    #[response(reason = "DATABASE_ERROR")]
    #[error("database operation failed")]
    DatabaseError,
}

// 模拟用户数据结构
#[derive(Debug, Serialize)]
struct User {
    id: u32,
    name: String,
}

// 获取用户信息的处理函数
#[get("/users/{id}")]
async fn get_user(id: web::Path<u32>) -> Result<HttpResponse, Error> {
    let user_id = id.into_inner();
    
    // 模拟从数据库获取用户
    if user_id == 0 {
        return Err(ApiError::UserNotFound)?;
    }
    
    Ok(HttpResponse::Ok().json(User {
        id: user_id,
        name: "John Doe".to_string(),
    }))
}

// 创建用户的处理函数
#[post("/users")]
async fn create_user(user: web::Json<User>) -> Result<HttpResponse, Error> {
    if user.name.is_empty() {
        return Err(ApiError::InvalidInput)?;
    }
    
    // 模拟数据库操作失败
    if user.id == 999 {
        return Err(ApiError::DatabaseError)?;
    }
    
    Ok(HttpResponse::Created().json(user.into_inner()))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(get_user)
            .service(create_user)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

这个完整示例展示了:

  1. 定义了一个包含多种错误的ApiError枚举
  2. 实现了获取用户和创建用户的两个端点
  3. 演示了不同错误情况的处理
  4. 展示了如何返回JSON响应
  5. 包含了数据库操作和输入验证的模拟场景

当访问不存在的用户ID(0)时,会返回:

{
    "result": 0,
    "reason": "USER_NOT_FOUND"
}

当创建用户时提供空名称,会返回:

{
    "result": 0,
    "reason": "INVALID_INPUT"
}

同时错误信息也会自动记录到日志中。


1 回复

Rust错误处理库actix-web-thiserror的使用指南

actix-web-thiserror 是一个为 Actix-Web 框架提供自定义错误类型和 thiserror 集成支持的库。它简化了在 Actix-Web 应用中定义和使用自定义错误类型的过程。

主要特性

  • 无缝集成 thiserror
  • 自动实现 ResponseError trait
  • 简化错误到 HTTP 响应的转换
  • 支持自定义错误响应格式

安装

Cargo.toml 中添加依赖:

[dependencies]
actix-web-thiserror = "1.0"
thiserror = "1.0"
actix-web = "4.0"

基本用法

1. 定义自定义错误类型

use actix_web_thiserror::ResponseError;
use thiserror::Error;

#[derive(Debug, Error, ResponseError)]
enum MyError {
    #[error("Not Found: {0}")]
    NotFound(String),
    
    #[error("Internal Server Error")]
    InternalError,
    
    #[error("Bad Request: {0}")]
    BadRequest(String),
    
    #[error("Unauthorized")]
    Unauthorized,
}

2. 在 Actix-Web 路由中使用

use actix_web::{get, web, App, HttpServer, Responder};
use actix_web_thiserror::ResponseError;
use thiserror::Error;

#[derive(Debug, Error, ResponseError)]
enum ApiError {
    #[error("User not found")]
    UserNotFound,
    
    #[error("Invalid input: {0}")]
    InvalidInput(String),
}

#[get("/user/{id}")]
async fn get_user(id: web::Path<u32>) -> Result<impl Responder, ApiError> {
    if *id == 0 {
        return Err(ApiError::InvalidInput("ID cannot be zero".to_string()));
    }
    
    if *id == 404 {
        return Err(ApiError::UserNotFound);
    }
    
    Ok(format!("User ID: {}", id))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(get_user))
        .bind("127.0.0.1:8080")?
        .run()
        .await
}

高级用法

自定义 HTTP 状态码

use actix_web::http::StatusCode;
use actix_web_thiserror::ResponseError;
use thiserror::Error;

#[derive(Debug, Error, ResponseError)]
enum CustomError {
    #[error("Payment Required")]
    #[response_error(status_code = 402)]
    PaymentRequired,
    
    #[error("Teapot")]
    #[response_error(status_code = 418)]
    ImATeapot,
}

自定义错误响应格式

use actix_web::HttpResponse;
use actix_web_thiserror::{ResponseError, ResponseErrorWrapper};
use serde_json::json;
use thiserror::Error;

#[derive(Debug, Error, ResponseError)]
#[response_error(render = "render_custom_error")]
enum JsonError {
    #[error("Validation failed")]
    ValidationError,
    
    #[error("Database error")]
    DatabaseError,
}

fn render_custom_error(err: &ResponseErrorWrapper<JsonError>) -> HttpResponse {
    let status_code = err.status_code();
    
    let body = json!({
        "error": err.to_string(),
        "code": status_code.as_u16(),
        "details": match err.inner() {
            JsonError::ValidationError => "Please check your input",
            JsonError::DatabaseError => "Please try again later",
        }
    });
    
    HttpResponse::build(status_code).json(body)
}

与 Actix-Web 中间件集成

use actix_web::{middleware, web, App, HttpServer};
use actix_web_thiserror::ResponseError;
use thiserror::Error;

#[derive(Debug, Error, ResponseError)]
enum MiddlewareError {
    #[error("Missing API Key")]
    MissingApiKey,
    
    #[error("Invalid API Key")]
    InvalidApiKey,
}

async fn check_api_key(req: actix_web::HttpRequest) -> Result<(), MiddlewareError> {
    let api_key = req.headers().get("x-api-key");
    
    if api_key.is_none() {
        return Err(MiddlewareError::MissingApiKey);
    }
    
    if api_key.unwrap() != "secret" {
        return Err(MiddlewareError::InvalidApiKey);
    }
    
    Ok(())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(middleware::NormalizePath::trim())
            .service(
                web::resource("/protected")
                    .to(|| async { "Protected content" })
                    .wrap_fn(|req, srv| {
                        let fut = check_api_key(req);
                        async move {
                            fut.await?;
                            srv.call(req).await
                        }
                    }),
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

完整示例

下面是一个完整的 actix-web-thiserror 使用示例,展示了错误处理的全流程:

use actix_web::{get, web, App, HttpServer, Responder, HttpResponse};
use actix_web_thiserror::{ResponseError, ResponseErrorWrapper};
use serde_json::json;
use thiserror::Error;

// 1. 定义自定义错误类型
#[derive(Debug, Error, ResponseError)]
enum AppError {
    #[error("Authentication failed")]
    Unauthorized,
    
    #[error("Resource not found: {0}")]
    NotFound(String),
    
    #[error("Invalid input: {0}")]
    BadRequest(String),
    
    #[error("Internal server error")]
    #[response_error(status_code = 500)]
    InternalError,
}

// 2. 自定义错误响应格式
fn custom_error_render(err: &ResponseErrorWrapper<AppError>) -> HttpResponse {
    let status_code = err.status_code();
    
    let body = json!({
        "success": false,
        "error": err.to_string(),
        "status": status_code.as_u16(),
        "timestamp": chrono::Utc::now().to_rfc3339()
    });
    
    HttpResponse::build(status_code).json(body)
}

// 3. 业务路由处理
#[get("/items/{id}")]
async fn get_item(id: web::Path<u32>) -> Result<impl Responder, AppError> {
    match *id {
        0 => Err(AppError::BadRequest("ID cannot be zero".into())),
        404 => Err(AppError::NotFound("Item not found".into())),
        401 => Err(AppError::Unauthorized),
        500 => Err(AppError::InternalError),
        _ => Ok(format!("Item ID: {}", id))
    }
}

// 4. 主函数
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            // 应用自定义错误处理器
            .app_data(web::JsonConfig::default().error_handler(|err, _req| {
                AppError::BadRequest(format!("JSON error: {}", err)).into()
            }))
            .service(get_item)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

最佳实践

  1. 为不同的错误类别创建不同的错误枚举
  2. 为错误提供有意义的错误消息
  3. 考虑安全性,不要暴露内部错误细节
  4. 为前端消费设计一致的错误响应格式
  5. 记录内部错误以便调试

actix-web-thiserror 通过简化错误处理流程,让开发者能够更专注于业务逻辑的实现,同时保持错误处理的健壮性和一致性。

回到顶部