Skip to main content

wae_https/error/
mod.rs

1//! HTTP 错误处理模块
2//!
3//! 提供统一的错误处理机制,包括:
4//! - 错误响应格式化
5//! - 错误扩展 trait
6
7use http::{Response, StatusCode, header};
8use serde::{Deserialize, Serialize};
9use std::fmt;
10use wae_types::{ErrorCategory, WaeError};
11
12use crate::{Body, full_body};
13
14/// HTTP 错误包装类型
15///
16/// 包装 WaeError 以提供 HTTP 响应转换。
17#[derive(Debug, Clone)]
18pub struct HttpError {
19    /// 内部 WaeError
20    inner: WaeError,
21}
22
23impl HttpError {
24    /// 从 WaeError 创建 HttpError
25    pub fn new(error: WaeError) -> Self {
26        Self { inner: error }
27    }
28
29    /// 获取内部 WaeError 引用
30    pub fn inner(&self) -> &WaeError {
31        &self.inner
32    }
33
34    /// 获取错误分类
35    pub fn category(&self) -> ErrorCategory {
36        self.inner.category()
37    }
38
39    /// 获取国际化键
40    pub fn i18n_key(&self) -> &'static str {
41        self.inner.i18n_key()
42    }
43
44    /// 获取国际化数据
45    pub fn i18n_data(&self) -> serde_json::Value {
46        self.inner.i18n_data()
47    }
48
49    /// 创建无效参数错误
50    pub fn invalid_params(param: impl Into<String>, reason: impl Into<String>) -> Self {
51        Self::new(WaeError::invalid_params(param, reason))
52    }
53
54    /// 创建无效令牌错误
55    pub fn invalid_token(reason: impl Into<String>) -> Self {
56        Self::new(WaeError::invalid_token(reason))
57    }
58
59    /// 创建令牌过期错误
60    pub fn token_expired() -> Self {
61        Self::new(WaeError::token_expired())
62    }
63
64    /// 创建禁止访问错误
65    pub fn forbidden(resource: impl Into<String>) -> Self {
66        Self::new(WaeError::forbidden(resource))
67    }
68
69    /// 创建权限拒绝错误
70    pub fn permission_denied(action: impl Into<String>) -> Self {
71        Self::new(WaeError::permission_denied(action))
72    }
73
74    /// 创建资源未找到错误
75    pub fn not_found(resource_type: impl Into<String>, identifier: impl Into<String>) -> Self {
76        Self::new(WaeError::not_found(resource_type, identifier))
77    }
78
79    /// 创建内部错误
80    pub fn internal(reason: impl Into<String>) -> Self {
81        Self::new(WaeError::internal(reason))
82    }
83
84    /// 创建无效格式错误
85    pub fn invalid_format(field: impl Into<String>, expected: impl Into<String>) -> Self {
86        Self::new(WaeError::invalid_format(field, expected))
87    }
88
89    /// 转换为 HTTP 响应
90    pub fn into_response(self) -> Response<Body> {
91        let status = category_to_status_code(self.category());
92        let error_response = ErrorResponse::from_error(&self);
93        let body = serde_json::to_string(&error_response).unwrap_or_else(|_| {
94            r#"{"success":false,"code":"INTERNAL_ERROR","message":"Failed to serialize error"}"#.to_string()
95        });
96
97        Response::builder().status(status).header(header::CONTENT_TYPE, "application/json").body(full_body(body)).unwrap()
98    }
99}
100
101impl fmt::Display for HttpError {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{}", self.inner)
104    }
105}
106
107impl std::error::Error for HttpError {}
108
109impl From<WaeError> for HttpError {
110    fn from(error: WaeError) -> Self {
111        Self::new(error)
112    }
113}
114
115impl From<HttpError> for WaeError {
116    fn from(error: HttpError) -> Self {
117        error.inner
118    }
119}
120
121/// HTTP 操作结果类型别名
122pub type HttpResult<T> = Result<T, HttpError>;
123
124/// 错误响应体结构
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct ErrorResponse {
127    /// 是否成功
128    pub success: bool,
129    /// 错误码
130    pub code: String,
131    /// 错误消息
132    pub message: String,
133    /// 详细信息(可选)
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub details: Option<serde_json::Value>,
136    /// 请求追踪 ID(可选)
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub trace_id: Option<String>,
139}
140
141impl ErrorResponse {
142    /// 从 HttpError 创建错误响应
143    pub fn from_error(error: &HttpError) -> Self {
144        Self {
145            success: false,
146            code: error.i18n_key().to_string(),
147            message: error.to_string(),
148            details: Some(error.i18n_data()),
149            trace_id: None,
150        }
151    }
152
153    /// 从 WaeError 创建错误响应
154    pub fn from_wae_error(error: &WaeError) -> Self {
155        Self {
156            success: false,
157            code: error.i18n_key().to_string(),
158            message: error.to_string(),
159            details: Some(error.i18n_data()),
160            trace_id: None,
161        }
162    }
163
164    /// 从 HttpError 创建错误响应(带额外详细信息)
165    pub fn from_error_with_details(error: &HttpError, details: serde_json::Value) -> Self {
166        let mut base = Self::from_error(error);
167        base.details = Some(details);
168        base
169    }
170
171    /// 设置追踪 ID
172    pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
173        self.trace_id = Some(trace_id.into());
174        self
175    }
176
177    /// 转换为 HTTP 响应
178    pub fn into_response(self) -> Response<Body> {
179        let status = StatusCode::BAD_REQUEST;
180        let body = serde_json::to_string(&self).unwrap_or_else(|_| {
181            r#"{"success":false,"code":"INTERNAL_ERROR","message":"Failed to serialize error"}"#.to_string()
182        });
183
184        Response::builder().status(status).header(header::CONTENT_TYPE, "application/json").body(full_body(body)).unwrap()
185    }
186}
187
188/// 将 ErrorCategory 转换为 StatusCode
189fn category_to_status_code(category: ErrorCategory) -> StatusCode {
190    match category {
191        ErrorCategory::Validation => StatusCode::BAD_REQUEST,
192        ErrorCategory::Auth => StatusCode::UNAUTHORIZED,
193        ErrorCategory::Permission => StatusCode::FORBIDDEN,
194        ErrorCategory::NotFound => StatusCode::NOT_FOUND,
195        ErrorCategory::Conflict => StatusCode::CONFLICT,
196        ErrorCategory::RateLimited => StatusCode::TOO_MANY_REQUESTS,
197        ErrorCategory::Network => StatusCode::BAD_GATEWAY,
198        ErrorCategory::Storage => StatusCode::INTERNAL_SERVER_ERROR,
199        ErrorCategory::Database => StatusCode::INTERNAL_SERVER_ERROR,
200        ErrorCategory::Cache => StatusCode::INTERNAL_SERVER_ERROR,
201        ErrorCategory::Config => StatusCode::INTERNAL_SERVER_ERROR,
202        ErrorCategory::Timeout => StatusCode::REQUEST_TIMEOUT,
203        ErrorCategory::Internal => StatusCode::INTERNAL_SERVER_ERROR,
204    }
205}
206
207/// 错误扩展 trait
208///
209/// 为 Result 类型提供便捷的错误转换方法。
210pub trait ErrorExt<T> {
211    /// 将错误转换为验证错误 (400)
212    fn bad_request(self) -> HttpResult<T>;
213
214    /// 将错误转换为认证错误 (401)
215    fn unauthorized(self) -> HttpResult<T>;
216
217    /// 将错误转换为权限错误 (403)
218    fn forbidden(self) -> HttpResult<T>;
219
220    /// 将错误转换为资源未找到错误 (404)
221    fn not_found(self) -> HttpResult<T>;
222
223    /// 将错误转换为内部服务器错误 (500)
224    fn internal_error(self) -> HttpResult<T>;
225
226    /// 使用自定义 HttpError 转换
227    fn with_http_error(self, error: HttpError) -> HttpResult<T>;
228
229    /// 使用错误消息转换函数
230    fn map_http_error<F>(self, f: F) -> HttpResult<T>
231    where
232        F: FnOnce(String) -> HttpError;
233}
234
235impl<T, E: fmt::Display> ErrorExt<T> for Result<T, E> {
236    fn bad_request(self) -> HttpResult<T> {
237        self.map_err(|e| HttpError::invalid_params("unknown", e.to_string()))
238    }
239
240    fn unauthorized(self) -> HttpResult<T> {
241        self.map_err(|e| HttpError::invalid_token(e.to_string()))
242    }
243
244    fn forbidden(self) -> HttpResult<T> {
245        self.map_err(|e| HttpError::forbidden(e.to_string()))
246    }
247
248    fn not_found(self) -> HttpResult<T> {
249        self.map_err(|e| HttpError::not_found("resource", e.to_string()))
250    }
251
252    fn internal_error(self) -> HttpResult<T> {
253        self.map_err(|e| HttpError::internal(e.to_string()))
254    }
255
256    fn with_http_error(self, error: HttpError) -> HttpResult<T> {
257        self.map_err(|_| error)
258    }
259
260    fn map_http_error<F>(self, f: F) -> HttpResult<T>
261    where
262        F: FnOnce(String) -> HttpError,
263    {
264        self.map_err(|e| f(e.to_string()))
265    }
266}
267
268/// 创建成功响应的便捷函数
269pub fn success_response<T: Serialize>(data: T) -> Response<Body> {
270    let body = serde_json::to_string(&serde_json::json!({
271        "success": true,
272        "data": data
273    }))
274    .unwrap_or_default();
275
276    Response::builder().status(StatusCode::OK).header(header::CONTENT_TYPE, "application/json").body(full_body(body)).unwrap()
277}
278
279/// 创建分页响应的便捷函数
280pub fn paginated_response<T: Serialize>(items: Vec<T>, total: u64, page: u32, page_size: u32) -> Response<Body> {
281    let total_pages = (total as f64 / page_size as f64).ceil() as u32;
282
283    let body = serde_json::to_string(&serde_json::json!({
284        "success": true,
285        "data": {
286            "items": items,
287            "pagination": {
288                "total": total,
289                "page": page,
290                "page_size": page_size,
291                "total_pages": total_pages
292            }
293        }
294    }))
295    .unwrap_or_default();
296
297    Response::builder().status(StatusCode::OK).header(header::CONTENT_TYPE, "application/json").body(full_body(body)).unwrap()
298}