simbld_http/responses/
actix_responder.rs

1//! # Custom HTTP Response Implementation
2//!
3//! This module provides a customizable HTTP response structure for Actix Web applications.
4//! The `CustomResponse` type implements Actix's `Responder` trait, allowing it to be
5//! directly returned from route handlers with full control over status codes, headers,
6//! and response content.
7//!
8//! ## Usage
9//!
10//! Create a custom response with your desired status code, name, data, and description:
11//!
12//! ```no_run
13//! use simbld_http::responses::CustomResponse;
14//!
15//! // Create a custom success response
16//! let success_response = CustomResponse::new(
17//!     200,
18//!     "Success",
19//!     "{\"message\": \"Operation completed\"}",
20//!     "Request was successful"
21//! );
22//!
23//! // Create a custom error response
24//! let error_response = CustomResponse::new(
25//!     404,
26//!     "NotFound",
27//!     "{\"error\": \"Resource not available\"}",
28//!     "The requested resource could not be found"
29//! );
30//! ```
31//!
32//! Use it in Actix Web handlers:
33//!
34//! ```no_run
35//! # use actix_web::{web, Responder};
36//! # use simbld_http::responses::CustomResponse;
37//!
38//! async fn success_handler() -> impl Responder {
39//!     CustomResponse::new(
40//!         200,
41//!         "Success",
42//!         "{\"message\": \"Operation completed\"}",
43//!         "Request was successful"
44//!     )
45//! }
46//!
47//! async fn not_found_handler() -> impl Responder {
48//!     CustomResponse::new(
49//!         404,
50//!         "NotFound",
51//!         "{\"error\": \"Resource not found\"}",
52//!         "The requested resource could not be found"
53//!     )
54//! }
55//! ```
56
57use crate::helpers::http_code_helper::HttpCode;
58use actix_web::http::StatusCode;
59use actix_web::{web, HttpRequest, HttpResponse, Responder};
60use serde::Serialize;
61
62/// A customizable HTTP response for Actix Web applications.
63///
64/// `CustomResponse` provides a flexible way to create HTTP responses with
65/// custom status codes, data payloads, and metadata. It implements Actix's
66/// `Responder` trait, making it directly returnable from route handlers.
67#[derive(Debug, Serialize, Clone)]
68pub struct CustomResponse {
69    /// The HTTP status code and associated metadata
70    pub http_code: HttpCode,
71
72    /// Name identifier for the response (useful for logging and debugging)
73    pub name: String,
74
75    /// The response payload (typically JSON or XML formatted as a string)
76    pub data: String,
77
78    /// Human-readable description of the response
79    pub description: String,
80}
81
82impl CustomResponse {
83    /// Creates a new CustomResponse with the provided information.
84    ///
85    /// # Arguments
86    ///
87    /// * `code` - HTTP status code (e.g., 200, 404, 500)
88    /// * `name` - Name identifier for the response
89    /// * `data` - Response payload (typically JSON or XML)
90    /// * `description` - Human-readable description
91    ///
92    /// # Returns
93    ///
94    /// A new CustomResponse instance
95    pub fn new(
96        code: u16,
97        name: impl Into<String>,
98        data: impl Into<String>,
99        description: impl Into<String>,
100    ) -> Self {
101        // Convert the input parameters into strings
102        let name_str = name.into();
103        let data_str = data.into();
104        let desc_str = description.into();
105
106        // Leak the strings to create static references
107        let leaked_name: &'static str = Box::leak(name_str.clone().into_boxed_str());
108        let leaked_desc: &'static str = Box::leak(desc_str.clone().into_boxed_str());
109
110        // Create a new HttpCode instance
111        let resolved_http_code = HttpCode::new(code, leaked_name, leaked_desc, code, leaked_name);
112
113        Self {
114            http_code: resolved_http_code,
115            name: name_str,
116            data: data_str,
117            description: desc_str,
118        }
119    }
120}
121
122/// Implements Actix's Responder trait for CustomResponse.
123///
124/// This allows CustomResponse instances to be returned directly from
125/// route handlers, with automatic conversion to HttpResponse.
126impl Responder for CustomResponse {
127    type Body = actix_web::body::BoxBody;
128
129    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
130        // Utiliser standard_code au lieu de code
131        let mut response =
132            HttpResponse::build(StatusCode::from_u16(self.http_code.standard_code).unwrap());
133
134        // Ajouter les headers pertinents
135        response.content_type("application/json");
136
137        // Construire la réponse avec les données
138        response.body(self.data)
139    }
140}
141
142/// A handler that returns a pre-configured CustomResponse.
143///
144/// This handler can be used with a shared CustomResponse provided via
145/// the app's application data.
146///
147/// # Arguments
148///
149/// * `custom_response` - A shared CustomResponse injected via web::Data
150/// * `req` - The HTTP request
151///
152/// # Returns
153///
154/// An HttpResponse based on the provided CustomResponse
155pub async fn custom_response_handler(
156    custom_response: web::Data<CustomResponse>,
157    req: HttpRequest,
158) -> HttpResponse {
159    let response = custom_response.get_ref().clone(); // Clone the struct
160    response.respond_to(&req)
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use actix_web::HttpServer;
167    use actix_web::{http::StatusCode, test, web, App};
168
169    /// Example handler that uses the new constructor with four arguments.
170    async fn example_response() -> impl Responder {
171        CustomResponse::new(
172            200,
173            "Success",
174            "{\"message\": \"Request was successful\", \"data\": \"Test data\"}",
175            "Test response description",
176        )
177    }
178
179    #[actix_web::main]
180    async fn main() -> std::io::Result<()> {
181        HttpServer::new(|| App::new().default_service(web::route().to(example_response)))
182            .bind("127.0.0.1:8090")?
183            .run()
184            .await
185    }
186
187    #[actix_web::test]
188    async fn test_custom_response_responder() {
189        // Step 1: Create a custom response
190        let custom_response = CustomResponse {
191            http_code: HttpCode {
192                standard_code: 200,
193                standard_name: "OK",
194                unified_description: "Success",
195                internal_code: Some(200),
196                internal_name: Some("OK"),
197            },
198            name: "".to_string(),
199            data: "Test data".to_string(),
200            description: "".to_string(),
201        };
202
203        // Step 2: Initialize an Actix-Web application with a handler
204        let app = test::init_service(
205            App::new()
206                .app_data(web::Data::new(custom_response))
207                .default_service(web::route().to(custom_response_handler)),
208        )
209        .await;
210
211        // Step 3: Simulate an HTTP request
212        let req = test::TestRequest::default().to_request();
213        let resp = test::call_service(&app, req).await;
214
215        // Step 4: Assert the response
216        assert_eq!(resp.status(), StatusCode::OK);
217    }
218
219    #[actix_web::test]
220    async fn test_example_response() {
221        // Create an app with our function like handler
222        let app = test::init_service(App::new().route("/", web::get().to(example_response))).await;
223
224        // Simulate a request to our endpoint
225        let req = test::TestRequest::get().uri("/").to_request();
226        let resp = test::call_service(&app, req).await;
227
228        // Check the status code
229        assert_eq!(resp.status(), StatusCode::OK);
230
231        // Check the content of the answer
232        let body = test::read_body(resp).await;
233        let body_str = String::from_utf8(body.to_vec()).unwrap();
234
235        // Make sure the body contains the expected data
236        assert!(body_str.contains("Test data"));
237        assert!(body_str.contains("Request was successful"));
238    }
239}