elif_http/response/
json.rs

1//! JSON handling utilities for requests and responses
2
3use axum::{
4    extract::{FromRequest, Request},
5    response::{IntoResponse, Response},
6    Json,
7};
8use serde::{Deserialize, Serialize, de::DeserializeOwned};
9use std::ops::{Deref, DerefMut};
10use crate::errors::HttpResult;
11use crate::response::{ElifResponse, IntoElifResponse, ElifStatusCode};
12
13/// Enhanced JSON extractor with better error handling
14#[derive(Debug)]
15pub struct ElifJson<T>(pub T);
16
17impl<T> ElifJson<T> {
18    /// Create new ElifJson wrapper
19    pub fn new(data: T) -> Self {
20        Self(data)
21    }
22
23    /// Extract inner data
24    pub fn into_inner(self) -> T {
25        self.0
26    }
27}
28
29impl<T> Deref for ElifJson<T> {
30    type Target = T;
31
32    fn deref(&self) -> &Self::Target {
33        &self.0
34    }
35}
36
37impl<T> DerefMut for ElifJson<T> {
38    fn deref_mut(&mut self) -> &mut Self::Target {
39        &mut self.0
40    }
41}
42
43impl<T> From<T> for ElifJson<T> {
44    fn from(data: T) -> Self {
45        Self(data)
46    }
47}
48
49/// JSON request extraction with enhanced error handling
50#[axum::async_trait]
51impl<T, S> FromRequest<S> for ElifJson<T>
52where
53    T: DeserializeOwned,
54    S: Send + Sync,
55{
56    type Rejection = JsonError;
57
58    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
59        match Json::<T>::from_request(req, state).await {
60            Ok(Json(data)) => Ok(ElifJson(data)),
61            Err(rejection) => Err(JsonError::from_axum_json_rejection(rejection)),
62        }
63    }
64}
65
66/// ElifJson to ElifResponse implementation
67impl<T> IntoElifResponse for ElifJson<T>
68where
69    T: Serialize,
70{
71    fn into_response(self) -> ElifResponse {
72        match ElifResponse::ok().json(&self.0) {
73            Ok(response) => response,
74            Err(_) => ElifResponse::internal_server_error().text("JSON serialization failed"),
75        }
76    }
77}
78
79/// JSON response implementation
80impl<T> IntoResponse for ElifJson<T>
81where
82    T: Serialize,
83{
84    fn into_response(self) -> Response {
85        match serde_json::to_vec(&self.0) {
86            Ok(bytes) => {
87                let mut response = Response::new(bytes.into());
88                response.headers_mut().insert(
89                    axum::http::header::CONTENT_TYPE,
90                    axum::http::HeaderValue::from_static("application/json"),
91                );
92                response
93            }
94            Err(err) => {
95                tracing::error!("JSON serialization failed: {}", err);
96                let mut response = Response::new("Internal server error: JSON serialization failed".into());
97                *response.status_mut() = axum::http::StatusCode::INTERNAL_SERVER_ERROR;
98                response.headers_mut().insert(
99                    axum::http::header::CONTENT_TYPE,
100                    axum::http::HeaderValue::from_static("text/plain"),
101                );
102                response
103            }
104        }
105    }
106}
107
108/// Enhanced JSON error handling
109#[derive(Debug)]
110pub struct JsonError {
111    pub status: ElifStatusCode,
112    pub message: String,
113    pub details: Option<String>,
114}
115
116impl JsonError {
117    /// Create new JSON error
118    pub fn new(status: ElifStatusCode, message: String) -> Self {
119        Self {
120            status,
121            message,
122            details: None,
123        }
124    }
125
126    /// Create JSON error with details
127    pub fn with_details(status: ElifStatusCode, message: String, details: String) -> Self {
128        Self {
129            status,
130            message,
131            details: Some(details),
132        }
133    }
134
135    /// Create from Axum JSON rejection (internal use only)
136    pub(crate) fn from_axum_json_rejection(rejection: axum::extract::rejection::JsonRejection) -> Self {
137        use axum::extract::rejection::JsonRejection::*;
138        
139        match rejection {
140            JsonDataError(err) => {
141                Self::with_details(
142                    ElifStatusCode::BAD_REQUEST,
143                    "Invalid JSON data".to_string(),
144                    err.to_string(),
145                )
146            }
147            JsonSyntaxError(err) => {
148                Self::with_details(
149                    ElifStatusCode::BAD_REQUEST,
150                    "JSON syntax error".to_string(),
151                    err.to_string(),
152                )
153            }
154            MissingJsonContentType(_) => {
155                Self::new(
156                    ElifStatusCode::BAD_REQUEST,
157                    "Missing 'Content-Type: application/json' header".to_string(),
158                )
159            }
160            BytesRejection(err) => {
161                Self::with_details(
162                    ElifStatusCode::BAD_REQUEST,
163                    "Failed to read request body".to_string(),
164                    err.to_string(),
165                )
166            }
167            _ => {
168                Self::new(
169                    ElifStatusCode::BAD_REQUEST,
170                    "Invalid JSON request".to_string(),
171                )
172            }
173        }
174    }
175}
176
177impl IntoResponse for JsonError {
178    fn into_response(self) -> Response {
179        let error_body = if let Some(details) = self.details {
180            serde_json::json!({
181                "error": {
182                    "code": self.status.as_u16(),
183                    "message": self.message,
184                    "details": details
185                }
186            })
187        } else {
188            serde_json::json!({
189                "error": {
190                    "code": self.status.as_u16(),
191                    "message": self.message
192                }
193            })
194        };
195
196        match serde_json::to_vec(&error_body) {
197            Ok(bytes) => {
198                let mut response = Response::new(bytes.into());
199                *response.status_mut() = self.status.to_axum();
200                response.headers_mut().insert(
201                    axum::http::header::CONTENT_TYPE,
202                    axum::http::HeaderValue::from_static("application/json"),
203                );
204                response
205            }
206            Err(_) => {
207                // Fallback error response
208                let mut response = Response::new(self.message.into());
209                *response.status_mut() = self.status.to_axum();
210                response.headers_mut().insert(
211                    axum::http::header::CONTENT_TYPE,
212                    axum::http::HeaderValue::from_static("text/plain"),
213                );
214                response
215            }
216        }
217    }
218}
219
220/// JSON response helpers
221pub struct JsonResponse;
222
223impl JsonResponse {
224    /// Create successful JSON response
225    pub fn ok<T: Serialize>(data: &T) -> HttpResult<Response> {
226        ElifResponse::json_ok(data)
227    }
228
229    /// Create JSON response with custom status
230    pub fn with_status<T: Serialize>(status: ElifStatusCode, data: &T) -> HttpResult<Response> {
231        ElifResponse::with_status(status).json(data)?.build()
232    }
233
234    /// Create paginated JSON response
235    pub fn paginated<T: Serialize>(
236        data: &[T],
237        page: u32,
238        per_page: u32,
239        total: u64,
240    ) -> HttpResult<Response> {
241        let total_pages = (total as f64 / per_page as f64).ceil() as u32;
242        
243        let response_data = serde_json::json!({
244            "data": data,
245            "pagination": {
246                "page": page,
247                "per_page": per_page,
248                "total": total,
249                "total_pages": total_pages,
250                "has_next": page < total_pages,
251                "has_prev": page > 1
252            }
253        });
254
255        ElifResponse::ok().json_value(response_data).build()
256    }
257
258    /// Create error response with JSON body
259    pub fn error(status: ElifStatusCode, message: &str) -> HttpResult<Response> {
260        ElifResponse::json_error(status, message)
261    }
262
263    /// Create validation error response
264    pub fn validation_error<T: Serialize>(errors: &T) -> HttpResult<Response> {
265        ElifResponse::validation_error(errors)
266    }
267
268    /// Create API success response with message
269    pub fn success_message(message: &str) -> HttpResult<Response> {
270        let response_data = serde_json::json!({
271            "success": true,
272            "message": message
273        });
274
275        ElifResponse::ok().json_value(response_data).build()
276    }
277
278    /// Create created resource response
279    pub fn created<T: Serialize>(data: &T) -> HttpResult<Response> {
280        ElifResponse::created().json(data)?.build()
281    }
282
283    /// Create no content response (for DELETE operations)
284    pub fn no_content() -> HttpResult<Response> {
285        ElifResponse::no_content().build()
286    }
287}
288
289/// Validation error types for JSON responses
290#[derive(Debug, Serialize, Deserialize)]
291pub struct ValidationErrors {
292    pub errors: std::collections::HashMap<String, Vec<String>>,
293}
294
295impl ValidationErrors {
296    /// Create new validation errors container
297    pub fn new() -> Self {
298        Self {
299            errors: std::collections::HashMap::new(),
300        }
301    }
302
303    /// Add error for a field
304    pub fn add_error(&mut self, field: String, error: String) {
305        self.errors.entry(field).or_insert_with(Vec::new).push(error);
306    }
307
308    /// Add multiple errors for a field
309    pub fn add_errors(&mut self, field: String, errors: Vec<String>) {
310        self.errors.entry(field).or_insert_with(Vec::new).extend(errors);
311    }
312
313    /// Check if there are any errors
314    pub fn has_errors(&self) -> bool {
315        !self.errors.is_empty()
316    }
317
318    /// Get error count
319    pub fn error_count(&self) -> usize {
320        self.errors.values().map(|v| v.len()).sum()
321    }
322
323    /// Convert to JSON response
324    pub fn to_response(self) -> HttpResult<Response> {
325        JsonResponse::validation_error(&self)
326    }
327}
328
329impl Default for ValidationErrors {
330    fn default() -> Self {
331        Self::new()
332    }
333}
334
335/// API response wrapper for consistent JSON responses
336#[derive(Debug, Serialize)]
337pub struct ApiResponse<T> {
338    pub success: bool,
339    pub data: Option<T>,
340    pub message: Option<String>,
341    pub errors: Option<serde_json::Value>,
342}
343
344impl<T: Serialize> ApiResponse<T> {
345    /// Create successful API response
346    pub fn success(data: T) -> Self {
347        Self {
348            success: true,
349            data: Some(data),
350            message: None,
351            errors: None,
352        }
353    }
354
355    /// Create successful API response with message
356    pub fn success_with_message(data: T, message: String) -> Self {
357        Self {
358            success: true,
359            data: Some(data),
360            message: Some(message),
361            errors: None,
362        }
363    }
364
365    /// Create error API response
366    pub fn error(message: String) -> ApiResponse<()> {
367        ApiResponse {
368            success: false,
369            data: None,
370            message: Some(message),
371            errors: None,
372        }
373    }
374
375    /// Create error API response with validation errors
376    pub fn validation_error(message: String, errors: serde_json::Value) -> ApiResponse<()> {
377        ApiResponse {
378            success: false,
379            data: None,
380            message: Some(message),
381            errors: Some(errors),
382        }
383    }
384
385    /// Convert to HTTP response
386    pub fn to_response(self) -> HttpResult<Response> {
387        let status = if self.success {
388            ElifStatusCode::OK
389        } else {
390            ElifStatusCode::BAD_REQUEST
391        };
392
393        ElifResponse::with_status(status).json(&self)?.build()
394    }
395}
396
397impl<T: Serialize> IntoResponse for ApiResponse<T> {
398    fn into_response(self) -> Response {
399        match self.to_response() {
400            Ok(response) => response,
401            Err(e) => {
402                tracing::error!("Failed to create API response: {}", e);
403                (ElifStatusCode::INTERNAL_SERVER_ERROR.to_axum(), "Internal server error").into_response()
404            }
405        }
406    }
407}