Skip to main content

wae_https/error/
mod.rs

1//! HTTP 错误处理模块
2//!
3//! 提供统一的错误处理机制,包括:
4//! - HTTP 错误类型枚举
5//! - 错误响应格式化
6//! - 错误扩展 trait
7//! - 与 axum 框架的集成
8
9use axum::{
10    body::Body,
11    http::{StatusCode, header},
12    response::{IntoResponse, Response},
13};
14use serde::{Deserialize, Serialize};
15use std::fmt;
16
17/// HTTP 错误类型枚举
18///
19/// 定义了 HTTP 服务中可能出现的各种错误类型,
20/// 每种错误类型对应特定的 HTTP 状态码。
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum HttpError {
23    /// 错误请求 (400)
24    BadRequest(String),
25    /// 未授权 (401)
26    Unauthorized(String),
27    /// 禁止访问 (403)
28    Forbidden(String),
29    /// 资源未找到 (404)
30    NotFound(String),
31    /// 方法不允许 (405)
32    MethodNotAllowed(String),
33    /// 请求超时 (408)
34    RequestTimeout(String),
35    /// 冲突 (409)
36    Conflict(String),
37    /// 请求实体过大 (413)
38    PayloadTooLarge(String),
39    /// URI 过长 (414)
40    UriTooLong(String),
41    /// 不支持的媒体类型 (415)
42    UnsupportedMediaType(String),
43    /// 请求格式错误 (422)
44    UnprocessableEntity(String),
45    /// 请求过于频繁 (429)
46    TooManyRequests(String),
47    /// 内部服务器错误 (500)
48    InternalServerError(String),
49    /// 服务不可用 (503)
50    ServiceUnavailable(String),
51    /// 网关超时 (504)
52    GatewayTimeout(String),
53}
54
55impl HttpError {
56    /// 获取错误对应的 HTTP 状态码
57    pub fn status_code(&self) -> StatusCode {
58        match self {
59            HttpError::BadRequest(_) => StatusCode::BAD_REQUEST,
60            HttpError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
61            HttpError::Forbidden(_) => StatusCode::FORBIDDEN,
62            HttpError::NotFound(_) => StatusCode::NOT_FOUND,
63            HttpError::MethodNotAllowed(_) => StatusCode::METHOD_NOT_ALLOWED,
64            HttpError::RequestTimeout(_) => StatusCode::REQUEST_TIMEOUT,
65            HttpError::Conflict(_) => StatusCode::CONFLICT,
66            HttpError::PayloadTooLarge(_) => StatusCode::PAYLOAD_TOO_LARGE,
67            HttpError::UriTooLong(_) => StatusCode::URI_TOO_LONG,
68            HttpError::UnsupportedMediaType(_) => StatusCode::UNSUPPORTED_MEDIA_TYPE,
69            HttpError::UnprocessableEntity(_) => StatusCode::UNPROCESSABLE_ENTITY,
70            HttpError::TooManyRequests(_) => StatusCode::TOO_MANY_REQUESTS,
71            HttpError::InternalServerError(_) => StatusCode::INTERNAL_SERVER_ERROR,
72            HttpError::ServiceUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
73            HttpError::GatewayTimeout(_) => StatusCode::GATEWAY_TIMEOUT,
74        }
75    }
76
77    /// 获取错误码字符串
78    pub fn error_code(&self) -> &'static str {
79        match self {
80            HttpError::BadRequest(_) => "BAD_REQUEST",
81            HttpError::Unauthorized(_) => "UNAUTHORIZED",
82            HttpError::Forbidden(_) => "FORBIDDEN",
83            HttpError::NotFound(_) => "NOT_FOUND",
84            HttpError::MethodNotAllowed(_) => "METHOD_NOT_ALLOWED",
85            HttpError::RequestTimeout(_) => "REQUEST_TIMEOUT",
86            HttpError::Conflict(_) => "CONFLICT",
87            HttpError::PayloadTooLarge(_) => "PAYLOAD_TOO_LARGE",
88            HttpError::UriTooLong(_) => "URI_TOO_LONG",
89            HttpError::UnsupportedMediaType(_) => "UNSUPPORTED_MEDIA_TYPE",
90            HttpError::UnprocessableEntity(_) => "UNPROCESSABLE_ENTITY",
91            HttpError::TooManyRequests(_) => "TOO_MANY_REQUESTS",
92            HttpError::InternalServerError(_) => "INTERNAL_SERVER_ERROR",
93            HttpError::ServiceUnavailable(_) => "SERVICE_UNAVAILABLE",
94            HttpError::GatewayTimeout(_) => "GATEWAY_TIMEOUT",
95        }
96    }
97
98    /// 获取错误消息
99    pub fn message(&self) -> &str {
100        match self {
101            HttpError::BadRequest(msg) => msg,
102            HttpError::Unauthorized(msg) => msg,
103            HttpError::Forbidden(msg) => msg,
104            HttpError::NotFound(msg) => msg,
105            HttpError::MethodNotAllowed(msg) => msg,
106            HttpError::RequestTimeout(msg) => msg,
107            HttpError::Conflict(msg) => msg,
108            HttpError::PayloadTooLarge(msg) => msg,
109            HttpError::UriTooLong(msg) => msg,
110            HttpError::UnsupportedMediaType(msg) => msg,
111            HttpError::UnprocessableEntity(msg) => msg,
112            HttpError::TooManyRequests(msg) => msg,
113            HttpError::InternalServerError(msg) => msg,
114            HttpError::ServiceUnavailable(msg) => msg,
115            HttpError::GatewayTimeout(msg) => msg,
116        }
117    }
118
119    /// 创建错误请求错误
120    pub fn bad_request(msg: impl Into<String>) -> Self {
121        HttpError::BadRequest(msg.into())
122    }
123
124    /// 创建未授权错误
125    pub fn unauthorized(msg: impl Into<String>) -> Self {
126        HttpError::Unauthorized(msg.into())
127    }
128
129    /// 创建禁止访问错误
130    pub fn forbidden(msg: impl Into<String>) -> Self {
131        HttpError::Forbidden(msg.into())
132    }
133
134    /// 创建资源未找到错误
135    pub fn not_found(msg: impl Into<String>) -> Self {
136        HttpError::NotFound(msg.into())
137    }
138
139    /// 创建方法不允许错误
140    pub fn method_not_allowed(msg: impl Into<String>) -> Self {
141        HttpError::MethodNotAllowed(msg.into())
142    }
143
144    /// 创建请求超时错误
145    pub fn request_timeout(msg: impl Into<String>) -> Self {
146        HttpError::RequestTimeout(msg.into())
147    }
148
149    /// 创建冲突错误
150    pub fn conflict(msg: impl Into<String>) -> Self {
151        HttpError::Conflict(msg.into())
152    }
153
154    /// 创建请求实体过大错误
155    pub fn payload_too_large(msg: impl Into<String>) -> Self {
156        HttpError::PayloadTooLarge(msg.into())
157    }
158
159    /// 创建内部服务器错误
160    pub fn internal(msg: impl Into<String>) -> Self {
161        HttpError::InternalServerError(msg.into())
162    }
163
164    /// 创建服务不可用错误
165    pub fn service_unavailable(msg: impl Into<String>) -> Self {
166        HttpError::ServiceUnavailable(msg.into())
167    }
168
169    /// 创建网关超时错误
170    pub fn gateway_timeout(msg: impl Into<String>) -> Self {
171        HttpError::GatewayTimeout(msg.into())
172    }
173}
174
175impl fmt::Display for HttpError {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        write!(f, "{}: {}", self.error_code(), self.message())
178    }
179}
180
181impl std::error::Error for HttpError {}
182
183/// HTTP 操作结果类型别名
184pub type HttpResult<T> = Result<T, HttpError>;
185
186/// 错误响应体结构
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct ErrorResponse {
189    /// 是否成功
190    pub success: bool,
191    /// 错误码
192    pub code: String,
193    /// 错误消息
194    pub message: String,
195    /// 详细信息(可选)
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub details: Option<serde_json::Value>,
198    /// 请求追踪 ID(可选)
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub trace_id: Option<String>,
201}
202
203impl ErrorResponse {
204    /// 从 HttpError 创建错误响应
205    pub fn from_error(error: &HttpError) -> Self {
206        Self {
207            success: false,
208            code: error.error_code().to_string(),
209            message: error.message().to_string(),
210            details: None,
211            trace_id: None,
212        }
213    }
214
215    /// 从 HttpError 创建错误响应(带详细信息)
216    pub fn from_error_with_details(error: &HttpError, details: serde_json::Value) -> Self {
217        Self {
218            success: false,
219            code: error.error_code().to_string(),
220            message: error.message().to_string(),
221            details: Some(details),
222            trace_id: None,
223        }
224    }
225
226    /// 设置追踪 ID
227    pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
228        self.trace_id = Some(trace_id.into());
229        self
230    }
231}
232
233impl IntoResponse for HttpError {
234    fn into_response(self) -> Response {
235        let status = self.status_code();
236        let error_response = ErrorResponse::from_error(&self);
237        let body = serde_json::to_string(&error_response).unwrap_or_else(|_| {
238            r#"{"success":false,"code":"INTERNAL_ERROR","message":"Failed to serialize error"}"#.to_string()
239        });
240
241        Response::builder().status(status).header(header::CONTENT_TYPE, "application/json").body(Body::from(body)).unwrap()
242    }
243}
244
245impl IntoResponse for ErrorResponse {
246    fn into_response(self) -> Response {
247        let status = StatusCode::BAD_REQUEST;
248        let body = serde_json::to_string(&self).unwrap_or_else(|_| {
249            r#"{"success":false,"code":"INTERNAL_ERROR","message":"Failed to serialize error"}"#.to_string()
250        });
251
252        Response::builder().status(status).header(header::CONTENT_TYPE, "application/json").body(Body::from(body)).unwrap()
253    }
254}
255
256/// 错误扩展 trait
257///
258/// 为 Result 类型提供便捷的错误转换方法。
259pub trait ErrorExt<T> {
260    /// 将错误转换为 HTTP 错误请求错误 (400)
261    fn bad_request(self) -> HttpResult<T>;
262
263    /// 将错误转换为 HTTP 未授权错误 (401)
264    fn unauthorized(self) -> HttpResult<T>;
265
266    /// 将错误转换为 HTTP 禁止访问错误 (403)
267    fn forbidden(self) -> HttpResult<T>;
268
269    /// 将错误转换为 HTTP 资源未找到错误 (404)
270    fn not_found(self) -> HttpResult<T>;
271
272    /// 将错误转换为 HTTP 内部服务器错误 (500)
273    fn internal_error(self) -> HttpResult<T>;
274
275    /// 使用自定义错误消息转换为 HTTP 错误
276    fn with_http_error(self, error: HttpError) -> HttpResult<T>;
277
278    /// 使用错误消息转换函数
279    fn map_http_error<F>(self, f: F) -> HttpResult<T>
280    where
281        F: FnOnce(String) -> HttpError;
282}
283
284impl<T, E: fmt::Display> ErrorExt<T> for Result<T, E> {
285    fn bad_request(self) -> HttpResult<T> {
286        self.map_err(|e| HttpError::BadRequest(e.to_string()))
287    }
288
289    fn unauthorized(self) -> HttpResult<T> {
290        self.map_err(|e| HttpError::Unauthorized(e.to_string()))
291    }
292
293    fn forbidden(self) -> HttpResult<T> {
294        self.map_err(|e| HttpError::Forbidden(e.to_string()))
295    }
296
297    fn not_found(self) -> HttpResult<T> {
298        self.map_err(|e| HttpError::NotFound(e.to_string()))
299    }
300
301    fn internal_error(self) -> HttpResult<T> {
302        self.map_err(|e| HttpError::InternalServerError(e.to_string()))
303    }
304
305    fn with_http_error(self, error: HttpError) -> HttpResult<T> {
306        self.map_err(|_| error)
307    }
308
309    fn map_http_error<F>(self, f: F) -> HttpResult<T>
310    where
311        F: FnOnce(String) -> HttpError,
312    {
313        self.map_err(|e| f(e.to_string()))
314    }
315}
316
317/// 从 wae_types::WaeError 转换为 HttpError
318impl From<wae_types::WaeError> for HttpError {
319    fn from(error: wae_types::WaeError) -> Self {
320        use wae_types::ErrorKind;
321        match error.kind {
322            ErrorKind::Validation => HttpError::BadRequest(error.to_string()),
323            ErrorKind::Network => HttpError::ServiceUnavailable(error.to_string()),
324            ErrorKind::Storage => HttpError::InternalServerError(error.to_string()),
325            ErrorKind::Internal => HttpError::InternalServerError(error.to_string()),
326            ErrorKind::NotFound => HttpError::NotFound(error.to_string()),
327            ErrorKind::Permission => HttpError::Forbidden(error.to_string()),
328            ErrorKind::Timeout => HttpError::RequestTimeout(error.to_string()),
329            ErrorKind::Config => HttpError::InternalServerError(error.to_string()),
330        }
331    }
332}
333
334/// 从 std::io::Error 转换为 HttpError
335impl From<std::io::Error> for HttpError {
336    fn from(error: std::io::Error) -> Self {
337        use std::io::ErrorKind;
338        match error.kind() {
339            ErrorKind::NotFound => HttpError::NotFound(error.to_string()),
340            ErrorKind::PermissionDenied => HttpError::Forbidden(error.to_string()),
341            ErrorKind::TimedOut => HttpError::RequestTimeout(error.to_string()),
342            ErrorKind::ConnectionRefused | ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted => {
343                HttpError::ServiceUnavailable(error.to_string())
344            }
345            _ => HttpError::InternalServerError(error.to_string()),
346        }
347    }
348}
349
350/// 从 serde_json::Error 转换为 HttpError
351impl From<serde_json::Error> for HttpError {
352    fn from(error: serde_json::Error) -> Self {
353        HttpError::BadRequest(format!("JSON 解析错误: {}", error))
354    }
355}
356
357/// 创建成功响应的便捷函数
358pub fn success_response<T: Serialize>(data: T) -> Response {
359    let body = serde_json::to_string(&serde_json::json!({
360        "success": true,
361        "data": data
362    }))
363    .unwrap_or_default();
364
365    Response::builder().status(StatusCode::OK).header(header::CONTENT_TYPE, "application/json").body(Body::from(body)).unwrap()
366}
367
368/// 创建分页响应的便捷函数
369pub fn paginated_response<T: Serialize>(items: Vec<T>, total: u64, page: u32, page_size: u32) -> Response {
370    let total_pages = (total as f64 / page_size as f64).ceil() as u32;
371
372    let body = serde_json::to_string(&serde_json::json!({
373        "success": true,
374        "data": {
375            "items": items,
376            "pagination": {
377                "total": total,
378                "page": page,
379                "page_size": page_size,
380                "total_pages": total_pages
381            }
382        }
383    }))
384    .unwrap_or_default();
385
386    Response::builder().status(StatusCode::OK).header(header::CONTENT_TYPE, "application/json").body(Body::from(body)).unwrap()
387}