elif_http/
json.rs

1//! JSON handling utilities for requests and responses
2//! 
3//! Provides enhanced JSON parsing, validation, and error handling.
4
5use axum::{
6    extract::{FromRequest, Request},
7    response::{IntoResponse, Response},
8    http::{StatusCode, HeaderMap},
9    Json,
10};
11use serde::{Deserialize, Serialize, de::DeserializeOwned};
12use std::ops::{Deref, DerefMut};
13use crate::error::{HttpError, HttpResult};
14use crate::response::{ElifResponse, IntoElifResponse};
15
16/// Enhanced JSON extractor with better error handling
17#[derive(Debug)]
18pub struct ElifJson<T>(pub T);
19
20impl<T> ElifJson<T> {
21    /// Create new ElifJson wrapper
22    pub fn new(data: T) -> Self {
23        Self(data)
24    }
25
26    /// Extract inner data
27    pub fn into_inner(self) -> T {
28        self.0
29    }
30}
31
32impl<T> Deref for ElifJson<T> {
33    type Target = T;
34
35    fn deref(&self) -> &Self::Target {
36        &self.0
37    }
38}
39
40impl<T> DerefMut for ElifJson<T> {
41    fn deref_mut(&mut self) -> &mut Self::Target {
42        &mut self.0
43    }
44}
45
46impl<T> From<T> for ElifJson<T> {
47    fn from(data: T) -> Self {
48        Self(data)
49    }
50}
51
52/// JSON request extraction with enhanced error handling
53#[axum::async_trait]
54impl<T, S> FromRequest<S> for ElifJson<T>
55where
56    T: DeserializeOwned,
57    S: Send + Sync,
58{
59    type Rejection = JsonError;
60
61    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
62        match Json::<T>::from_request(req, state).await {
63            Ok(Json(data)) => Ok(ElifJson(data)),
64            Err(rejection) => Err(JsonError::from_axum_json_rejection(rejection)),
65        }
66    }
67}
68
69/// ElifJson to ElifResponse implementation
70impl<T> IntoElifResponse for ElifJson<T>
71where
72    T: Serialize,
73{
74    fn into_elif_response(self) -> ElifResponse {
75        match ElifResponse::ok().json(&self.0) {
76            Ok(response) => response,
77            Err(_) => ElifResponse::internal_server_error().text("JSON serialization failed"),
78        }
79    }
80}
81
82/// JSON response implementation
83impl<T> IntoResponse for ElifJson<T>
84where
85    T: Serialize,
86{
87    fn into_response(self) -> Response {
88        match serde_json::to_vec(&self.0) {
89            Ok(bytes) => {
90                let mut response = Response::new(bytes.into());
91                response.headers_mut().insert(
92                    axum::http::header::CONTENT_TYPE,
93                    axum::http::HeaderValue::from_static("application/json"),
94                );
95                response
96            }
97            Err(err) => {
98                tracing::error!("JSON serialization failed: {}", err);
99                (
100                    StatusCode::INTERNAL_SERVER_ERROR,
101                    "Internal server error: JSON serialization failed"
102                ).into_response()
103            }
104        }
105    }
106}
107
108/// Enhanced JSON error handling
109#[derive(Debug)]
110pub struct JsonError {
111    pub status: StatusCode,
112    pub message: String,
113    pub details: Option<String>,
114}
115
116impl JsonError {
117    /// Create new JSON error
118    pub fn new(status: StatusCode, 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: StatusCode, message: String, details: String) -> Self {
128        Self {
129            status,
130            message,
131            details: Some(details),
132        }
133    }
134
135    /// Create from Axum JSON rejection
136    pub 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                    StatusCode::BAD_REQUEST,
143                    "Invalid JSON data".to_string(),
144                    err.to_string(),
145                )
146            }
147            JsonSyntaxError(err) => {
148                Self::with_details(
149                    StatusCode::BAD_REQUEST,
150                    "JSON syntax error".to_string(),
151                    err.to_string(),
152                )
153            }
154            MissingJsonContentType(_) => {
155                Self::new(
156                    StatusCode::BAD_REQUEST,
157                    "Missing 'Content-Type: application/json' header".to_string(),
158                )
159            }
160            BytesRejection(err) => {
161                Self::with_details(
162                    StatusCode::BAD_REQUEST,
163                    "Failed to read request body".to_string(),
164                    err.to_string(),
165                )
166            }
167            _ => {
168                Self::new(
169                    StatusCode::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 ElifResponse::with_status(self.status)
197            .json_value(error_body)
198            .build()
199        {
200            Ok(response) => response,
201            Err(_) => {
202                // Fallback error response
203                (self.status, self.message).into_response()
204            }
205        }
206    }
207}
208
209/// JSON response helpers
210pub struct JsonResponse;
211
212impl JsonResponse {
213    /// Create successful JSON response
214    pub fn ok<T: Serialize>(data: &T) -> HttpResult<Response> {
215        ElifResponse::json_ok(data)
216    }
217
218    /// Create JSON response with custom status
219    pub fn with_status<T: Serialize>(status: StatusCode, data: &T) -> HttpResult<Response> {
220        ElifResponse::with_status(status).json(data)?.build()
221    }
222
223    /// Create paginated JSON response
224    pub fn paginated<T: Serialize>(
225        data: &[T],
226        page: u32,
227        per_page: u32,
228        total: u64,
229    ) -> HttpResult<Response> {
230        let total_pages = (total as f64 / per_page as f64).ceil() as u32;
231        
232        let response_data = serde_json::json!({
233            "data": data,
234            "pagination": {
235                "page": page,
236                "per_page": per_page,
237                "total": total,
238                "total_pages": total_pages,
239                "has_next": page < total_pages,
240                "has_prev": page > 1
241            }
242        });
243
244        ElifResponse::ok().json_value(response_data).build()
245    }
246
247    /// Create error response with JSON body
248    pub fn error(status: StatusCode, message: &str) -> HttpResult<Response> {
249        ElifResponse::json_error(status, message)
250    }
251
252    /// Create validation error response
253    pub fn validation_error<T: Serialize>(errors: &T) -> HttpResult<Response> {
254        ElifResponse::validation_error(errors)
255    }
256
257    /// Create API success response with message
258    pub fn success_message(message: &str) -> HttpResult<Response> {
259        let response_data = serde_json::json!({
260            "success": true,
261            "message": message
262        });
263
264        ElifResponse::ok().json_value(response_data).build()
265    }
266
267    /// Create created resource response
268    pub fn created<T: Serialize>(data: &T) -> HttpResult<Response> {
269        ElifResponse::created().json(data)?.build()
270    }
271
272    /// Create no content response (for DELETE operations)
273    pub fn no_content() -> HttpResult<Response> {
274        ElifResponse::no_content().build()
275    }
276}
277
278/// Validation error types for JSON responses
279#[derive(Debug, Serialize, Deserialize)]
280pub struct ValidationErrors {
281    pub errors: std::collections::HashMap<String, Vec<String>>,
282}
283
284impl ValidationErrors {
285    /// Create new validation errors container
286    pub fn new() -> Self {
287        Self {
288            errors: std::collections::HashMap::new(),
289        }
290    }
291
292    /// Add error for a field
293    pub fn add_error(&mut self, field: String, error: String) {
294        self.errors.entry(field).or_insert_with(Vec::new).push(error);
295    }
296
297    /// Add multiple errors for a field
298    pub fn add_errors(&mut self, field: String, errors: Vec<String>) {
299        self.errors.entry(field).or_insert_with(Vec::new).extend(errors);
300    }
301
302    /// Check if there are any errors
303    pub fn has_errors(&self) -> bool {
304        !self.errors.is_empty()
305    }
306
307    /// Get error count
308    pub fn error_count(&self) -> usize {
309        self.errors.values().map(|v| v.len()).sum()
310    }
311
312    /// Convert to JSON response
313    pub fn to_response(self) -> HttpResult<Response> {
314        JsonResponse::validation_error(&self)
315    }
316}
317
318impl Default for ValidationErrors {
319    fn default() -> Self {
320        Self::new()
321    }
322}
323
324/// API response wrapper for consistent JSON responses
325#[derive(Debug, Serialize)]
326pub struct ApiResponse<T> {
327    pub success: bool,
328    pub data: Option<T>,
329    pub message: Option<String>,
330    pub errors: Option<serde_json::Value>,
331}
332
333impl<T: Serialize> ApiResponse<T> {
334    /// Create successful API response
335    pub fn success(data: T) -> Self {
336        Self {
337            success: true,
338            data: Some(data),
339            message: None,
340            errors: None,
341        }
342    }
343
344    /// Create successful API response with message
345    pub fn success_with_message(data: T, message: String) -> Self {
346        Self {
347            success: true,
348            data: Some(data),
349            message: Some(message),
350            errors: None,
351        }
352    }
353
354    /// Create error API response
355    pub fn error(message: String) -> ApiResponse<()> {
356        ApiResponse {
357            success: false,
358            data: None,
359            message: Some(message),
360            errors: None,
361        }
362    }
363
364    /// Create error API response with validation errors
365    pub fn validation_error(message: String, errors: serde_json::Value) -> ApiResponse<()> {
366        ApiResponse {
367            success: false,
368            data: None,
369            message: Some(message),
370            errors: Some(errors),
371        }
372    }
373
374    /// Convert to HTTP response
375    pub fn to_response(self) -> HttpResult<Response> {
376        let status = if self.success {
377            StatusCode::OK
378        } else {
379            StatusCode::BAD_REQUEST
380        };
381
382        ElifResponse::with_status(status).json(&self)?.build()
383    }
384}
385
386impl<T: Serialize> IntoResponse for ApiResponse<T> {
387    fn into_response(self) -> Response {
388        match self.to_response() {
389            Ok(response) => response,
390            Err(e) => {
391                tracing::error!("Failed to create API response: {}", e);
392                (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response()
393            }
394        }
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use serde_json::json;
402
403    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
404    struct TestData {
405        name: String,
406        age: u32,
407    }
408
409    #[test]
410    fn test_elif_json_wrapper() {
411        let data = TestData {
412            name: "John".to_string(),
413            age: 30,
414        };
415        
416        let json_data = ElifJson::new(data.clone());
417        assert_eq!(json_data.name, data.name);
418        assert_eq!(json_data.age, data.age);
419        
420        let extracted = json_data.into_inner();
421        assert_eq!(extracted, data);
422    }
423
424    #[test]
425    fn test_validation_errors() {
426        let mut errors = ValidationErrors::new();
427        errors.add_error("name".to_string(), "Name is required".to_string());
428        errors.add_error("age".to_string(), "Age must be positive".to_string());
429        
430        assert!(errors.has_errors());
431        assert_eq!(errors.error_count(), 2);
432    }
433
434    #[test]
435    fn test_api_response() {
436        let data = TestData {
437            name: "Jane".to_string(),
438            age: 25,
439        };
440        
441        let success_response = ApiResponse::success(data);
442        assert!(success_response.success);
443        assert!(success_response.data.is_some());
444        
445        let error_response = ApiResponse::<()>::error("Something went wrong".to_string());
446        assert!(!error_response.success);
447        assert!(error_response.data.is_none());
448        assert_eq!(error_response.message, Some("Something went wrong".to_string()));
449    }
450
451    #[test]
452    fn test_json_response_helpers() {
453        let data = vec![
454            TestData { name: "User1".to_string(), age: 20 },
455            TestData { name: "User2".to_string(), age: 25 },
456        ];
457        
458        // Test paginated response
459        let response = JsonResponse::paginated(&data, 1, 10, 25);
460        assert!(response.is_ok());
461    }
462}