elif_http/response/
json.rs

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