1use axum::{
10 body::Body,
11 http::{StatusCode, header},
12 response::{IntoResponse, Response},
13};
14use serde::{Deserialize, Serialize};
15use std::fmt;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum HttpError {
23 BadRequest(String),
25 Unauthorized(String),
27 Forbidden(String),
29 NotFound(String),
31 MethodNotAllowed(String),
33 RequestTimeout(String),
35 Conflict(String),
37 PayloadTooLarge(String),
39 UriTooLong(String),
41 UnsupportedMediaType(String),
43 UnprocessableEntity(String),
45 TooManyRequests(String),
47 InternalServerError(String),
49 ServiceUnavailable(String),
51 GatewayTimeout(String),
53}
54
55impl HttpError {
56 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 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 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 pub fn bad_request(msg: impl Into<String>) -> Self {
121 HttpError::BadRequest(msg.into())
122 }
123
124 pub fn unauthorized(msg: impl Into<String>) -> Self {
126 HttpError::Unauthorized(msg.into())
127 }
128
129 pub fn forbidden(msg: impl Into<String>) -> Self {
131 HttpError::Forbidden(msg.into())
132 }
133
134 pub fn not_found(msg: impl Into<String>) -> Self {
136 HttpError::NotFound(msg.into())
137 }
138
139 pub fn method_not_allowed(msg: impl Into<String>) -> Self {
141 HttpError::MethodNotAllowed(msg.into())
142 }
143
144 pub fn request_timeout(msg: impl Into<String>) -> Self {
146 HttpError::RequestTimeout(msg.into())
147 }
148
149 pub fn conflict(msg: impl Into<String>) -> Self {
151 HttpError::Conflict(msg.into())
152 }
153
154 pub fn payload_too_large(msg: impl Into<String>) -> Self {
156 HttpError::PayloadTooLarge(msg.into())
157 }
158
159 pub fn internal(msg: impl Into<String>) -> Self {
161 HttpError::InternalServerError(msg.into())
162 }
163
164 pub fn service_unavailable(msg: impl Into<String>) -> Self {
166 HttpError::ServiceUnavailable(msg.into())
167 }
168
169 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
183pub type HttpResult<T> = Result<T, HttpError>;
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct ErrorResponse {
189 pub success: bool,
191 pub code: String,
193 pub message: String,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub details: Option<serde_json::Value>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub trace_id: Option<String>,
201}
202
203impl ErrorResponse {
204 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 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 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
256pub trait ErrorExt<T> {
260 fn bad_request(self) -> HttpResult<T>;
262
263 fn unauthorized(self) -> HttpResult<T>;
265
266 fn forbidden(self) -> HttpResult<T>;
268
269 fn not_found(self) -> HttpResult<T>;
271
272 fn internal_error(self) -> HttpResult<T>;
274
275 fn with_http_error(self, error: HttpError) -> HttpResult<T>;
277
278 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
317impl 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
334impl 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
350impl From<serde_json::Error> for HttpError {
352 fn from(error: serde_json::Error) -> Self {
353 HttpError::BadRequest(format!("JSON 解析错误: {}", error))
354 }
355}
356
357pub 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
368pub 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}