Rust错误处理库actix-web-thiserror-derive的使用:简化Actix-Web框架中的自定义错误类型定义与派生

Rust错误处理库actix-web-thiserror-derive的使用:简化Actix-Web框架中的自定义错误类型定义与派生

介绍

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

安装

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

cargo add actix-web-thiserror-derive

或者在Cargo.toml中添加:

actix-web-thiserror-derive = "0.2.7"

错误定义示例

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,
}

完整示例Demo

use actix_web::{get, App, HttpResponse, HttpServer, Result};
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. 创建使用自定义错误的路由处理器
#[get("/error")]
async fn error_test() -> Result<HttpResponse, MyError> {
    Err(MyError::NotFound)?
}

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

错误响应

当错误发生时,将返回如下格式的JSON响应:

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

错误日志

错误文本会自动打印到日志中:

ERROR Response error: Resource not found
    MyError(NotFound), place: src/main.rs:10

特性

  1. 简化了Actix-Web框架中的错误处理
  2. 自动生成适当的HTTP响应
  3. 内置错误日志记录
  4. 支持自定义错误原因(reason)
  5. 可以与thiserror无缝集成

注意事项

  • reason是一个字符串,可以以某种形式提供给客户端来解释错误
  • 可以通过实现ResponseTransform并添加#[response(transform = custom)]来指定自定义响应转换

1 回复

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

概述

actix-web-thiserror-derive 是一个结合了 thiserroractix-web 的派生宏库,它简化了在 Actix-Web 框架中定义和使用自定义错误类型的过程。这个库让你能够轻松地创建既符合 thiserror 风格又兼容 actix-web 错误处理机制的自定义错误类型。

主要特性

  1. 自动为自定义错误类型实现 ResponseError trait
  2. 保留 thiserror 的所有功能
  3. 简化 Actix-Web 应用中的错误处理流程

安装

Cargo.toml 中添加依赖:

[dependencies]
actix-web-thiserror-derive = "0.1"
thiserror = "1.0"
actix-web = "4"

基本用法

定义自定义错误

use actix_web_thiserror_derive::ResponseError;
use thiserror::Error;

#[derive(Debug, Error, ResponseError)]
pub enum MyError {
    #[error("Not found error: {0}")]
    NotFound(String),
    
    #[error("Internal server error")]
    InternalError,
    
    #[error("Unauthorized access")]
    Unauthorized,
}

在 Actix-Web 处理函数中使用

use actix_web::{get, web, App, HttpServer, Responder};
use actix_web::http::StatusCode;

#[get("/items/{id}")]
async fn get_item(id: web::Path<u32>) -> Result<String, MyError> {
    if *id == 0 {
        Err(MyError::NotFound(format!("Item {} not found", id)))
    } else if *id == 999 {
        Err(MvError::InternalError)
    } else {
        Ok(format!("Item {} found", id))
    }
}

高级用法

自定义 HTTP 状态码

#[derive(Debug, Error, ResponseError)]
pub enum MyError {
    #[error("Not found error: {0}")]
    #[response(status = 404)]
    NotFound(String),
    
    #[error("Internal server error")]
    #[response(status = 500)]
    InternalError,
    
    #[error("Unauthorized access")]
    #[response(status = 401)]
    Unauthorized,
}

包含额外数据

#[derive(Debug, Error, ResponseError)]
pub enum ApiError {
    #[error("Validation error in field {field}: {reason}")]
    #[response(status = 400)]
    ValidationError {
        field: String,
        reason: String,
        #[serde(skip)]  // 不序列化到响应中
        debug_info: String,
    },
}

在 Actix-Web 应用中注册

use actix_web::{App, HttpServer};

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

实际示例

完整的 API 错误处理

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

#[derive(Debug, Serialize)]
struct ErrorResponse {
    error: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    details: Option<String>,
}

#[derive(Debug, Error, ResponseError)]
enum ApiError {
    #[error("Resource not found")]
    #[response(status = 404)]
    NotFound,
    
    #[error("Invalid input: {0}")]
    #[response(status = 400)]
    BadRequest(String),
    
    #[error("Authentication required")]
    #[response(status = 401)]
    Unauthorized,
    
    #[error("Internal server error")]
    #[response(status = 500)]
    Internal(String),
}

#[post("/login")]
async fn login() -> Result<HttpResponse, ApiError> {
    // 模拟认证失败
    Err(ApiError::Unauthorized)
}

#[get("/data/{id}")]
async fn get_data(id: web::Path<i32>) -> Result<HttpResponse, ApiError> {
    if *id < 0 {
        Err(ApiError::BadRequest("ID must be positive".to_string()))
    } else if *id == 404 {
        Err(ApiError::NotFound)
    } else if *id == 500 {
        Err(ApiError::Internal("Database connection failed".to_string()))
    } else {
        Ok(HttpResponse::Ok().body(format!("Data for ID {}", id)))
    }
}

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

最佳实践

  1. 为不同的错误场景定义明确的错误变体
  2. 使用有意义的错误信息和适当的 HTTP 状态码
  3. 考虑为生产环境提供更详细的错误信息(如错误代码)
  4. 区分面向用户的错误信息和内部调试信息

actix-web-thiserror-derive 通过结合 thiserror 的易用性和 actix-web 的错误处理能力,大大简化了 Rust Web 应用中的错误处理流程。

完整示例Demo

下面是一个完整的示例,展示了如何使用actix-web-thiserror-derive创建一个简单的REST API,包含自定义错误处理和状态码管理:

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

// 定义用户数据结构
#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u32,
    username: String,
    email: String,
}

// 定义自定义错误类型
#[derive(Debug, Error, ResponseError)]
enum AppError {
    #[error("User not found")]
    #[response(status = 404)]
    UserNotFound,
    
    #[error("Invalid user data: {0}")]
    #[response(status = 400)]
    InvalidUserData(String),
    
    #[error("Database error")]
    #[response(status = 500)]
    DatabaseError,
}

// 模拟数据库存储
static mut USERS: Vec<User> = Vec::new();

// 创建用户端点
#[post("/users")]
async fn create_user(user: web::Json<User>) -> Result<HttpResponse, AppError> {
    if user.username.is_empty() {
        return Err(AppError::InvalidUserData("Username cannot be empty".to_string()));
    }
    
    unsafe {
        USERS.push(user.into_inner());
        Ok(HttpResponse::Created().json(&USERS.last().unwrap()))
    }
}

// 获取用户端点
#[get("/users/{id}")]
async fn get_user(id: web::Path<u32>) -> Result<HttpResponse, AppError> {
    unsafe {
        USERS.iter()
            .find(|u| u.id == *id)
            .map(|user| HttpResponse::Ok().json(user))
            .ok_or(AppError::UserNotFound)
    }
}

// 获取所有用户端点
#[get("/users")]
async fn get_all_users() -> Result<HttpResponse, AppError> {
    unsafe {
        if USERS.is_empty() {
            Err(AppError::UserNotFound)
        } else {
            Ok(HttpResponse::Ok().json(&USERS))
        }
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // 初始化一些测试数据
    unsafe {
        USERS.push(User {
            id: 1,
            username: "testuser".to_string(),
            email: "test@example.com".to_string(),
        });
    }

    // 启动HTTP服务器
    HttpServer::new(|| {
        App::new()
            .service(create_user)
            .service(get_user)
            .service(get_all_users)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

这个完整示例展示了:

  1. 定义了一个简单的用户数据结构和内存"数据库"
  2. 创建了自定义错误类型AppError,包含三种不同的错误情况
  3. 实现了三个API端点:创建用户、获取单个用户、获取所有用户
  4. 每个端点都使用了自定义错误类型进行错误处理
  5. 包含了适当的HTTP状态码返回

运行这个示例后,你可以通过以下方式测试API:

  • POST /users 创建新用户
  • GET /users/1 获取ID为1的用户
  • GET /users 获取所有用户

每个端点都会根据不同的错误情况返回相应的HTTP状态码和错误信息。

回到顶部