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;
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/// JSON response implementation
70impl<T> IntoResponse for ElifJson<T>
71where
72    T: Serialize,
73{
74    fn into_response(self) -> Response {
75        match serde_json::to_vec(&self.0) {
76            Ok(bytes) => {
77                let mut response = Response::new(bytes.into());
78                response.headers_mut().insert(
79                    axum::http::header::CONTENT_TYPE,
80                    axum::http::HeaderValue::from_static("application/json"),
81                );
82                response
83            }
84            Err(err) => {
85                tracing::error!("JSON serialization failed: {}", err);
86                (
87                    StatusCode::INTERNAL_SERVER_ERROR,
88                    "Internal server error: JSON serialization failed"
89                ).into_response()
90            }
91        }
92    }
93}
94
95/// Enhanced JSON error handling
96#[derive(Debug)]
97pub struct JsonError {
98    pub status: StatusCode,
99    pub message: String,
100    pub details: Option<String>,
101}
102
103impl JsonError {
104    /// Create new JSON error
105    pub fn new(status: StatusCode, message: String) -> Self {
106        Self {
107            status,
108            message,
109            details: None,
110        }
111    }
112
113    /// Create JSON error with details
114    pub fn with_details(status: StatusCode, message: String, details: String) -> Self {
115        Self {
116            status,
117            message,
118            details: Some(details),
119        }
120    }
121
122    /// Create from Axum JSON rejection
123    pub fn from_axum_json_rejection(rejection: axum::extract::rejection::JsonRejection) -> Self {
124        use axum::extract::rejection::JsonRejection::*;
125        
126        match rejection {
127            JsonDataError(err) => {
128                Self::with_details(
129                    StatusCode::BAD_REQUEST,
130                    "Invalid JSON data".to_string(),
131                    err.to_string(),
132                )
133            }
134            JsonSyntaxError(err) => {
135                Self::with_details(
136                    StatusCode::BAD_REQUEST,
137                    "JSON syntax error".to_string(),
138                    err.to_string(),
139                )
140            }
141            MissingJsonContentType(_) => {
142                Self::new(
143                    StatusCode::BAD_REQUEST,
144                    "Missing 'Content-Type: application/json' header".to_string(),
145                )
146            }
147            BytesRejection(err) => {
148                Self::with_details(
149                    StatusCode::BAD_REQUEST,
150                    "Failed to read request body".to_string(),
151                    err.to_string(),
152                )
153            }
154            _ => {
155                Self::new(
156                    StatusCode::BAD_REQUEST,
157                    "Invalid JSON request".to_string(),
158                )
159            }
160        }
161    }
162}
163
164impl IntoResponse for JsonError {
165    fn into_response(self) -> Response {
166        let error_body = if let Some(details) = self.details {
167            serde_json::json!({
168                "error": {
169                    "code": self.status.as_u16(),
170                    "message": self.message,
171                    "details": details
172                }
173            })
174        } else {
175            serde_json::json!({
176                "error": {
177                    "code": self.status.as_u16(),
178                    "message": self.message
179                }
180            })
181        };
182
183        match ElifResponse::with_status(self.status)
184            .json_value(error_body)
185            .build()
186        {
187            Ok(response) => response,
188            Err(_) => {
189                // Fallback error response
190                (self.status, self.message).into_response()
191            }
192        }
193    }
194}
195
196/// JSON response helpers
197pub struct JsonResponse;
198
199impl JsonResponse {
200    /// Create successful JSON response
201    pub fn ok<T: Serialize>(data: &T) -> HttpResult<Response> {
202        ElifResponse::json_ok(data)
203    }
204
205    /// Create JSON response with custom status
206    pub fn with_status<T: Serialize>(status: StatusCode, data: &T) -> HttpResult<Response> {
207        ElifResponse::with_status(status).json(data)?.build()
208    }
209
210    /// Create paginated JSON response
211    pub fn paginated<T: Serialize>(
212        data: &[T],
213        page: u32,
214        per_page: u32,
215        total: u64,
216    ) -> HttpResult<Response> {
217        let total_pages = (total as f64 / per_page as f64).ceil() as u32;
218        
219        let response_data = serde_json::json!({
220            "data": data,
221            "pagination": {
222                "page": page,
223                "per_page": per_page,
224                "total": total,
225                "total_pages": total_pages,
226                "has_next": page < total_pages,
227                "has_prev": page > 1
228            }
229        });
230
231        ElifResponse::ok().json_value(response_data).build()
232    }
233
234    /// Create error response with JSON body
235    pub fn error(status: StatusCode, message: &str) -> HttpResult<Response> {
236        ElifResponse::json_error(status, message)
237    }
238
239    /// Create validation error response
240    pub fn validation_error<T: Serialize>(errors: &T) -> HttpResult<Response> {
241        ElifResponse::validation_error(errors)
242    }
243
244    /// Create API success response with message
245    pub fn success_message(message: &str) -> HttpResult<Response> {
246        let response_data = serde_json::json!({
247            "success": true,
248            "message": message
249        });
250
251        ElifResponse::ok().json_value(response_data).build()
252    }
253
254    /// Create created resource response
255    pub fn created<T: Serialize>(data: &T) -> HttpResult<Response> {
256        ElifResponse::created().json(data)?.build()
257    }
258
259    /// Create no content response (for DELETE operations)
260    pub fn no_content() -> HttpResult<Response> {
261        ElifResponse::no_content().build()
262    }
263}
264
265/// Validation error types for JSON responses
266#[derive(Debug, Serialize, Deserialize)]
267pub struct ValidationErrors {
268    pub errors: std::collections::HashMap<String, Vec<String>>,
269}
270
271impl ValidationErrors {
272    /// Create new validation errors container
273    pub fn new() -> Self {
274        Self {
275            errors: std::collections::HashMap::new(),
276        }
277    }
278
279    /// Add error for a field
280    pub fn add_error(&mut self, field: String, error: String) {
281        self.errors.entry(field).or_insert_with(Vec::new).push(error);
282    }
283
284    /// Add multiple errors for a field
285    pub fn add_errors(&mut self, field: String, errors: Vec<String>) {
286        self.errors.entry(field).or_insert_with(Vec::new).extend(errors);
287    }
288
289    /// Check if there are any errors
290    pub fn has_errors(&self) -> bool {
291        !self.errors.is_empty()
292    }
293
294    /// Get error count
295    pub fn error_count(&self) -> usize {
296        self.errors.values().map(|v| v.len()).sum()
297    }
298
299    /// Convert to JSON response
300    pub fn to_response(self) -> HttpResult<Response> {
301        JsonResponse::validation_error(&self)
302    }
303}
304
305impl Default for ValidationErrors {
306    fn default() -> Self {
307        Self::new()
308    }
309}
310
311/// API response wrapper for consistent JSON responses
312#[derive(Debug, Serialize)]
313pub struct ApiResponse<T> {
314    pub success: bool,
315    pub data: Option<T>,
316    pub message: Option<String>,
317    pub errors: Option<serde_json::Value>,
318}
319
320impl<T: Serialize> ApiResponse<T> {
321    /// Create successful API response
322    pub fn success(data: T) -> Self {
323        Self {
324            success: true,
325            data: Some(data),
326            message: None,
327            errors: None,
328        }
329    }
330
331    /// Create successful API response with message
332    pub fn success_with_message(data: T, message: String) -> Self {
333        Self {
334            success: true,
335            data: Some(data),
336            message: Some(message),
337            errors: None,
338        }
339    }
340
341    /// Create error API response
342    pub fn error(message: String) -> ApiResponse<()> {
343        ApiResponse {
344            success: false,
345            data: None,
346            message: Some(message),
347            errors: None,
348        }
349    }
350
351    /// Create error API response with validation errors
352    pub fn validation_error(message: String, errors: serde_json::Value) -> ApiResponse<()> {
353        ApiResponse {
354            success: false,
355            data: None,
356            message: Some(message),
357            errors: Some(errors),
358        }
359    }
360
361    /// Convert to HTTP response
362    pub fn to_response(self) -> HttpResult<Response> {
363        let status = if self.success {
364            StatusCode::OK
365        } else {
366            StatusCode::BAD_REQUEST
367        };
368
369        ElifResponse::with_status(status).json(&self)?.build()
370    }
371}
372
373impl<T: Serialize> IntoResponse for ApiResponse<T> {
374    fn into_response(self) -> Response {
375        match self.to_response() {
376            Ok(response) => response,
377            Err(e) => {
378                tracing::error!("Failed to create API response: {}", e);
379                (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response()
380            }
381        }
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use serde_json::json;
389
390    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
391    struct TestData {
392        name: String,
393        age: u32,
394    }
395
396    #[test]
397    fn test_elif_json_wrapper() {
398        let data = TestData {
399            name: "John".to_string(),
400            age: 30,
401        };
402        
403        let json_data = ElifJson::new(data.clone());
404        assert_eq!(json_data.name, data.name);
405        assert_eq!(json_data.age, data.age);
406        
407        let extracted = json_data.into_inner();
408        assert_eq!(extracted, data);
409    }
410
411    #[test]
412    fn test_validation_errors() {
413        let mut errors = ValidationErrors::new();
414        errors.add_error("name".to_string(), "Name is required".to_string());
415        errors.add_error("age".to_string(), "Age must be positive".to_string());
416        
417        assert!(errors.has_errors());
418        assert_eq!(errors.error_count(), 2);
419    }
420
421    #[test]
422    fn test_api_response() {
423        let data = TestData {
424            name: "Jane".to_string(),
425            age: 25,
426        };
427        
428        let success_response = ApiResponse::success(data);
429        assert!(success_response.success);
430        assert!(success_response.data.is_some());
431        
432        let error_response = ApiResponse::<()>::error("Something went wrong".to_string());
433        assert!(!error_response.success);
434        assert!(error_response.data.is_none());
435        assert_eq!(error_response.message, Some("Something went wrong".to_string()));
436    }
437
438    #[test]
439    fn test_json_response_helpers() {
440        let data = vec![
441            TestData { name: "User1".to_string(), age: 20 },
442            TestData { name: "User2".to_string(), age: 25 },
443        ];
444        
445        // Test paginated response
446        let response = JsonResponse::paginated(&data, 1, 10, 25);
447        assert!(response.is_ok());
448    }
449}