Web 错误处理
问题
Rust Web 应用中如何优雅地处理错误?
答案
统一错误类型
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use thiserror::Error;
// 定义应用错误
#[derive(Debug, Error)]
enum AppError {
#[error("未找到: {0}")]
NotFound(String),
#[error("未授权")]
Unauthorized,
#[error("参数错误: {0}")]
BadRequest(String),
#[error("内部错误: {0}")]
Internal(String),
#[error(transparent)]
Database(#[from] sqlx::Error),
}
// 实现 IntoResponse,自动转为 HTTP 响应
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match &self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "未授权".into()),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
AppError::Internal(msg) => {
// 内部错误只返回通用消息,避免泄露实现细节
tracing::error!("内部错误: {}", msg);
(StatusCode::INTERNAL_SERVER_ERROR, "服务器内部错误".into())
}
AppError::Database(e) => {
tracing::error!("数据库错误: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "服务器内部错误".into())
}
};
let body = json!({
"error": {
"code": status.as_u16(),
"message": message,
}
});
(status, Json(body)).into_response()
}
}
// Handler 中直接用 ? 传播错误
async fn get_user(Path(id): Path<u64>) -> Result<Json<User>, AppError> {
let user = db::find_user(id)
.await? // sqlx::Error → AppError::Database
.ok_or(AppError::NotFound(format!("用户 {} 不存在", id)))?;
Ok(Json(user))
}
关键原则
- 不要向客户端暴露内部错误细节(如数据库错误信息)
- 对内部错误记录日志,对外返回通用消息
- 使用
thiserror+IntoResponse实现自动转换
常见面试问题
Q1: 如何处理请求验证错误?
答案:
使用 validator crate 配合自定义提取器:
use validator::Validate;
#[derive(Deserialize, Validate)]
struct CreateUser {
#[validate(length(min = 1, max = 50))]
name: String,
#[validate(email)]
email: String,
}
async fn create_user(
Json(input): Json<CreateUser>,
) -> Result<Json<User>, AppError> {
input.validate()
.map_err(|e| AppError::BadRequest(e.to_string()))?;
// ...
}