错误处理 #

错误类型 #

Actix Web 使用 actix_web::Error 类型表示错误,它实现了 std::error::Error trait。

text
┌─────────────────────────────────────────────────────────────┐
│                      错误处理流程                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  处理函数返回 Result<T, Error>                               │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              错误转换                                 │   │
│  │  实现 ResponseError trait                            │   │
│  │  将错误转换为 HTTP 响应                               │   │
│  └─────────────────────────────────────────────────────┘   │
│         │                                                    │
│    ┌────┴────┐                                              │
│    │         │                                              │
│    ▼         ▼                                              │
│  Ok(T)    Err(Error)                                        │
│    │         │                                              │
│    ▼         ▼                                              │
│  正常响应   错误响应                                          │
│                                                              │
└─────────────────────────────────────────────────────────────┘

ResponseError Trait #

基本实现 #

rust
use actix_web::{HttpResponse, ResponseError};
use std::fmt;

#[derive(Debug)]
pub struct AppError {
    message: String,
    status: actix_web::http::StatusCode,
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl ResponseError for AppError {
    fn error_response(&self) -> HttpResponse {
        HttpResponse::build(self.status)
            .json(serde_json::json!({
                "error": self.message
            }))
    }
}

自定义错误类型 #

rust
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ApiError {
    #[error("Not found: {0}")]
    NotFound(String),
    
    #[error("Bad request: {0}")]
    BadRequest(String),
    
    #[error("Unauthorized: {0}")]
    Unauthorized(String),
    
    #[error("Internal server error: {0}")]
    InternalError(String),
}

impl ResponseError for ApiError {
    fn error_response(&self) -> HttpResponse {
        match self {
            ApiError::NotFound(msg) => HttpResponse::NotFound()
                .json(serde_json::json!({ "error": msg })),
            ApiError::BadRequest(msg) => HttpResponse::BadRequest()
                .json(serde_json::json!({ "error": msg })),
            ApiError::Unauthorized(msg) => HttpResponse::Unauthorized()
                .json(serde_json::json!({ "error": msg })),
            ApiError::InternalError(msg) => HttpResponse::InternalServerError()
                .json(serde_json::json!({ "error": msg })),
        }
    }
    
    fn status_code(&self) -> StatusCode {
        match self {
            ApiError::NotFound(_) => StatusCode::NOT_FOUND,
            ApiError::BadRequest(_) => StatusCode::BAD_REQUEST,
            ApiError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
            ApiError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

pub type ApiResult<T> = Result<T, ApiError>;

在处理函数中使用 #

返回 Result #

rust
use actix_web::{web, Responder};

#[actix_web::get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> ApiResult<impl Responder> {
    let id = path.into_inner();
    
    if id > 100 {
        return Err(ApiError::NotFound(format!("User {} not found", id)));
    }
    
    Ok(web::Json(serde_json::json!({
        "id": id,
        "name": "Alice"
    })))
}

错误转换 #

rust
impl From<sqlx::Error> for ApiError {
    fn from(err: sqlx::Error) -> Self {
        match err {
            sqlx::Error::RowNotFound => ApiError::NotFound("Record not found".to_string()),
            _ => ApiError::InternalError(err.to_string()),
        }
    }
}

async fn get_user_from_db(id: u32) -> ApiResult<User> {
    let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
        .fetch_one(&pool)
        .await?;
    
    Ok(user)
}

错误中间件 #

全局错误处理 #

rust
use actix_web::{
    body::EitherBody,
    dev::{Service, ServiceRequest, ServiceResponse, Transform},
    http::StatusCode,
    middleware::{ErrorHandlerResponse, ErrorHandlers},
    Error, HttpResponse,
};

fn error_handler<B>(res: ServiceResponse<B>) -> actix_web::Result<ErrorHandlerResponse<B>> {
    let status = res.status();
    
    if status.is_client_error() || status.is_server_error() {
        let response = HttpResponse::build(status)
            .json(serde_json::json!({
                "error": status.canonical_reason().unwrap_or("Unknown error"),
                "status": status.as_u16()
            }));
        
        let res = ServiceResponse::new(res.request().clone(), response)
            .map_into_right_body();
        
        return Ok(ErrorHandlerResponse::Response(res));
    }
    
    Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
}

App::new()
    .wrap(ErrorHandlers::new()
        .handler(StatusCode::NOT_FOUND, error_handler)
        .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
    )

JSON 错误响应 #

统一错误格式 #

rust
use serde::Serialize;

#[derive(Serialize)]
struct ErrorResponse {
    success: bool,
    error: ErrorDetail,
}

#[derive(Serialize)]
struct ErrorDetail {
    code: u16,
    message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    details: Option<Vec<String>>,
}

impl ResponseError for ApiError {
    fn error_response(&self) -> HttpResponse {
        let status = self.status_code();
        
        HttpResponse::build(status)
            .json(ErrorResponse {
                success: false,
                error: ErrorDetail {
                    code: status.as_u16(),
                    message: self.to_string(),
                    details: None,
                },
            })
    }
}

验证错误 #

rust
use validator::ValidationErrors;

#[derive(Error, Debug)]
pub enum ValidationError {
    #[error("Validation failed")]
    InvalidInput { errors: ValidationErrors },
}

impl ResponseError for ValidationError {
    fn error_response(&self) -> HttpResponse {
        match self {
            ValidationError::InvalidInput { errors } => {
                HttpResponse::UnprocessableEntity()
                    .json(serde_json::json!({
                        "error": "Validation failed",
                        "details": errors.field_errors()
                            .into_iter()
                            .map(|(field, errs)| {
                                (field, errs.iter().map(|e| e.code.clone()).collect::<Vec<_>>())
                            })
                            .collect::<std::collections::HashMap<_, _>>()
                    }))
            }
        }
    }
    
    fn status_code(&self) -> StatusCode {
        StatusCode::UNPROCESSABLE_ENTITY
    }
}

完整示例 #

rust
use actix_web::{http::StatusCode, web, App, HttpResponse, HttpServer, Responder, ResponseError};
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ApiError {
    #[error("Not found: {0}")]
    NotFound(String),
    
    #[error("Bad request: {0}")]
    BadRequest(String),
    
    #[error("Unauthorized")]
    Unauthorized,
    
    #[error("Internal error: {0}")]
    Internal(String),
}

impl ResponseError for ApiError {
    fn error_response(&self) -> HttpResponse {
        let status = self.status_code();
        HttpResponse::build(status).json(ErrorResponse {
            success: false,
            error: self.to_string(),
            code: status.as_u16(),
        })
    }
    
    fn status_code(&self) -> StatusCode {
        match self {
            ApiError::NotFound(_) => StatusCode::NOT_FOUND,
            ApiError::BadRequest(_) => StatusCode::BAD_REQUEST,
            ApiError::Unauthorized => StatusCode::UNAUTHORIZED,
            ApiError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

#[derive(Serialize)]
struct ErrorResponse {
    success: bool,
    error: String,
    code: u16,
}

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

type Result<T> = std::result::Result<T, ApiError>;

#[actix_web::get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> Result<impl Responder> {
    let id = path.into_inner();
    
    if id > 100 {
        return Err(ApiError::NotFound(format!("User {} not found", id)));
    }
    
    Ok(web::Json(User {
        id,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    }))
}

#[actix_web::post("/users")]
async fn create_user(body: web::Json<CreateUser>) -> Result<impl Responder> {
    if body.name.is_empty() {
        return Err(ApiError::BadRequest("Name cannot be empty".to_string()));
    }
    
    if !body.email.contains('@') {
        return Err(ApiError::BadRequest("Invalid email format".to_string()));
    }
    
    Ok(HttpResponse::Created().json(User {
        id: 1,
        name: body.name.clone(),
        email: body.email.clone(),
    }))
}

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

下一步 #

现在你已经掌握了错误处理,继续学习 应用状态,深入了解状态管理!

最后更新:2026-03-29