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}