跳到主要内容

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))
}
关键原则
  1. 不要向客户端暴露内部错误细节(如数据库错误信息)
  2. 对内部错误记录日志,对外返回通用消息
  3. 使用 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()))?;
// ...
}

相关链接