第七章:错误处理与统一响应
博客v1.0系列教程(Rust)博客 v1.0 系列教程 (Rust)
7.1 错误码定义
// src/common/error.rs
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::Serialize;
#[derive(Debug, Clone, Copy, Serialize)]
pub struct ErrorCode(pub i32);
impl ErrorCode {
pub const SUCCESS: Self = Self(0);
pub const NOT_FOUND: Self = Self(1001);
pub const UNAUTHORIZED: Self = Self(1002);
pub const FORBIDDEN: Self = Self(1003);
pub const BAD_REQUEST: Self = Self(1004);
pub const VALIDATION_ERROR: Self = Self(1005);
pub const USER_EXISTS: Self = Self(2001);
pub const USER_BLACKLISTED: Self = Self(2004);
pub const DATABASE_ERROR: Self = Self(5001);
pub const INTERNAL_ERROR: Self = Self(5000);
}
7.2 统一响应格式
#[derive(Debug, Serialize)]
pub struct ApiResult<T: Serialize> {
pub code: i32,
pub message: String,
pub data: Option<T>,
}
impl<T: Serialize> ApiResult<T> {
pub fn success(data: T) -> Self {
ApiResult {
code: ErrorCode::SUCCESS.0,
message: "success".into(),
data: Some(data),
}
}
pub fn error(code: ErrorCode, message: &str) -> Self {
ApiResult {
code: code.0,
message: message.into(),
data: None,
}
}
}
7.3 自定义错误类型
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("Not found: {0}")]
NotFound(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Forbidden")]
Forbidden,
#[error("Bad request: {0}")]
BadRequest(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("User already exists")]
UserAlreadyExists,
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("JWT error: {0}")]
Jwt(#[from] jsonwebtoken::errors::Error),
#[error("BCrypt error: {0}")]
Bcrypt(#[from] bcrypt::BcryptError),
#[error("Internal error: {0}")]
Internal(String),
}
7.4 转换为 HTTP 响应
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, code, message) = match &self {
AppError::NotFound(msg) => (
StatusCode::NOT_FOUND,
ErrorCode::NOT_FOUND,
msg.clone(),
),
AppError::Unauthorized(msg) => (
StatusCode::UNAUTHORIZED,
ErrorCode::UNAUTHORIZED,
msg.clone(),
),
AppError::Forbidden => (
StatusCode::FORBIDDEN,
ErrorCode::FORBIDDEN,
"Forbidden".into(),
),
AppError::BadRequest(msg) => (
StatusCode::BAD_REQUEST,
ErrorCode::BAD_REQUEST,
msg.clone(),
),
AppError::Validation(msg) => (
StatusCode::UNPROCESSABLE_ENTITY,
ErrorCode::VALIDATION_ERROR,
msg.clone(),
),
AppError::UserAlreadyExists => (
StatusCode::CONFLICT,
ErrorCode::USER_EXISTS,
"User already exists".into(),
),
AppError::Database(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
ErrorCode::DATABASE_ERROR,
"Database error".into(),
),
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
ErrorCode::INTERNAL_ERROR,
"Internal server error".into(),
),
};
(
status,
Json(ApiResult::<()>::error(code, &message)),
).into_response()
}
}
7.5 全局异常捕获
pub async fn handle_panic(
err: Box<dyn std::any::Any + Send + 'static>,
) -> Response {
let message = if let Some(s) = err.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = err.downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic".into()
};
tracing::error!("Panic: {}", message);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ApiResult::<()>::error(
ErrorCode::INTERNAL_ERROR,
"Internal server error",
)),
).into_response()
}
7.6 在服务层使用
// src/services/article_service.rs
impl ArticleService {
pub async fn get_article(&self, id: i64) -> Result<Article, AppError> {
let article = sqlx::query_as::<_, Article>(
"SELECT * FROM article_models WHERE id = $1"
)
.bind(id)
.fetch_optional(&self.pool)
.await?
.ok_or_else(|| AppError::NotFound("Article not found".into()))?;
Ok(article)
}
pub async fn create_article(&self, dto: CreateArticleDto) -> Result<Article, AppError> {
// 参数验证
if dto.title.trim().is_empty() {
return Err(AppError::Validation("Title cannot be empty".into()));
}
let article = sqlx::query_as::<_, Article>(
"INSERT INTO article_models (title, content, category)
VALUES ($1, $2, $3) RETURNING *"
)
.bind(&dto.title)
.bind(&dto.content)
.bind(&dto.category)
.fetch_one(&self.pool)
.await?;
Ok(article)
}
}
7.7 端点中使用
async fn get_article_handler(
State(pool): State<PgPool>,
Path(id): Path<i64>,
) -> Result<Json<ApiResult<Article>>, AppError> {
let service = ArticleService::new(pool);
let article = service.get_article(id).await?;
Ok(Json(ApiResult::success(article)))
}
下一章将实现文件上传与中间件。
rusterror-handlingthiserroraxum