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
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 次
