1use http::{Response, StatusCode, header};
8use serde::{Deserialize, Serialize};
9use std::fmt;
10use wae_types::{ErrorCategory, WaeError};
11
12use crate::{Body, full_body};
13
14#[derive(Debug, Clone)]
18pub struct HttpError {
19 inner: WaeError,
21}
22
23impl HttpError {
24 pub fn new(error: WaeError) -> Self {
26 Self { inner: error }
27 }
28
29 pub fn inner(&self) -> &WaeError {
31 &self.inner
32 }
33
34 pub fn category(&self) -> ErrorCategory {
36 self.inner.category()
37 }
38
39 pub fn i18n_key(&self) -> &'static str {
41 self.inner.i18n_key()
42 }
43
44 pub fn i18n_data(&self) -> serde_json::Value {
46 self.inner.i18n_data()
47 }
48
49 pub fn invalid_params(param: impl Into<String>, reason: impl Into<String>) -> Self {
51 Self::new(WaeError::invalid_params(param, reason))
52 }
53
54 pub fn invalid_token(reason: impl Into<String>) -> Self {
56 Self::new(WaeError::invalid_token(reason))
57 }
58
59 pub fn token_expired() -> Self {
61 Self::new(WaeError::token_expired())
62 }
63
64 pub fn forbidden(resource: impl Into<String>) -> Self {
66 Self::new(WaeError::forbidden(resource))
67 }
68
69 pub fn permission_denied(action: impl Into<String>) -> Self {
71 Self::new(WaeError::permission_denied(action))
72 }
73
74 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 pub fn internal(reason: impl Into<String>) -> Self {
81 Self::new(WaeError::internal(reason))
82 }
83
84 pub fn invalid_format(field: impl Into<String>, expected: impl Into<String>) -> Self {
86 Self::new(WaeError::invalid_format(field, expected))
87 }
88
89 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
121pub type HttpResult<T> = Result<T, HttpError>;
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct ErrorResponse {
127 pub success: bool,
129 pub code: String,
131 pub message: String,
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub details: Option<serde_json::Value>,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub trace_id: Option<String>,
139}
140
141impl ErrorResponse {
142 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 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 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 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 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
188fn 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
207pub trait ErrorExt<T> {
211 fn bad_request(self) -> HttpResult<T>;
213
214 fn unauthorized(self) -> HttpResult<T>;
216
217 fn forbidden(self) -> HttpResult<T>;
219
220 fn not_found(self) -> HttpResult<T>;
222
223 fn internal_error(self) -> HttpResult<T>;
225
226 fn with_http_error(self, error: HttpError) -> HttpResult<T>;
228
229 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
268pub 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
279pub 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}