Rust + Axum 统一错误处理:用 ApiResponse 收敛成功与失败

test12026-09-140 次阅读

为什么要统一

Axum 的 handler 可以返回实现了 IntoResponse 的任意类型。如果成功返回 Json,失败随手抛一个 (StatusCode, &str),前端拿到的响应结构就会乱七八糟。用统一的 AppError 和 ApiResponse 收敛后,所有接口都走同一套 {code, message, data},前端处理成本大幅下降。

定义业务错误

借助 thiserror 把常见错误归类,并让 sqlx 等底层错误能自动转换进来。

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde::Serialize;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AppError {
    #[error("资源不存在")]
    NotFound,
    #[error("参数错误: {0}")]
    BadRequest(String),
    #[error("数据库错误: {0}")]
    Db(#[from] sqlx::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, msg) = match self {
            AppError::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
            AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, m),
            AppError::Db(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
        };
        (
            status,
            Json(serde_json::json!({ "code": status.as_u16(), "message": msg })),
        )
            .into_response()
    }
}

统一响应结构

#[derive(Serialize)]
pub struct ApiResponse<T: Serialize> {
    pub code: u16,
    pub message: String,
    pub data: Option<T>,
}

impl<T: Serialize> ApiResponse<T> {
    pub fn ok(data: T) -> Self {
        Self {
            code: 0,
            message: "ok".into(),
            data: Some(data),
        }
    }
    pub fn err(code: u16, message: &str) -> ApiResponse<()> {
        ApiResponse {
            code,
            message: message.into(),
            data: None,
        }
    }
}

在 handler 里返回 Result

handler 直接返回 Result>, AppError>,业务里用 ? 把底层错误转成统一错误,成功路径包一层 ApiResponse::ok,整条链路干干净净。

async fn get_user(
    Path(id): Path<i64>,
) -> Result<Json<ApiResponse<User>>, AppError> {
    let user = user_repo::find(id)
        .await
        .map_err(|_| AppError::NotFound)?;
    Ok(Json(ApiResponse::ok(user)))
}

经验清单

  • 所有业务错误都汇入一个 AppError,再实现 IntoResponse,避免散落各处的手写响应。
  • 成功用 code = 0、失败用 HTTP 状态码,前后端约定清楚即可。
  • 用 #[from] 让 sqlx / 校验库的错误自动提升为 AppError,少写样板。
  • ApiResponse 的 data 用 Option,失败时给 null,前端不用区分字段是否存在。
T

test1

文章作者

为什么要统一 Axum 的 handler 可以返回实现了 IntoResponse 的任意类型。如果成功返回 Jso...

分类
技术
发布时间
2026-09-14
字数
约 2226 字
阅读
0 次