simbld_http/responses/
mod.rs

1//! This module organizes and provides enums for various HTTP response status codes and categories.
2//! It includes the following categories:
3//! - Informational (1xx)
4//! - Success (2xx)
5//! - Redirection (3xx)
6//! - Client (4xx)
7//! - Server (5xx)
8//! - Local API codes (9xx)
9//! - Service responses (6xx)
10//! - Crawler-specific responses (7xx)
11#[macro_use]
12pub mod actix_responder;
13pub mod client;
14pub mod crawler;
15pub mod informational;
16pub mod local;
17pub mod redirection;
18pub mod server;
19pub mod service;
20pub mod success;
21
22// Public exports for response codes
23use crate::helpers::response_helpers;
24use crate::traits::get_description_trait::GetDescription;
25pub use actix_responder::CustomResponse;
26pub use client::ResponsesClientCodes;
27pub use crawler::ResponsesCrawlerCodes;
28pub use informational::ResponsesInformationalCodes;
29pub use local::ResponsesLocalApiCodes;
30pub use redirection::ResponsesRedirectionCodes;
31pub use server::ResponsesServerCodes;
32pub use service::ResponsesServiceCodes;
33pub use success::ResponsesSuccessCodes;
34
35// Public exports for response types
36use crate::helpers::http_code_helper::HttpCode;
37
38/// Enum representing the main categories of HTTP response codes.
39/// Combines multiple categories into a unified type for simplified handling.
40#[derive(Debug, Clone, Copy, PartialEq)]
41pub enum ResponsesTypes {
42    /// Enum representing all HTTP response families.
43    Informational(ResponsesInformationalCodes),
44    Success(ResponsesSuccessCodes),
45    Redirection(ResponsesRedirectionCodes),
46    ClientError(ResponsesClientCodes),
47    ServerError(ResponsesServerCodes),
48    ServiceError(ResponsesServiceCodes),
49    CrawlerError(ResponsesCrawlerCodes),
50    LocalApiError(ResponsesLocalApiCodes),
51}
52
53impl ResponsesTypes {
54    /// Converts the enum variant to its corresponding HTTP status code as `u16`.
55    pub fn get_code(&self) -> u16 {
56        match self {
57            ResponsesTypes::Informational(code) => code.get_code(),
58            ResponsesTypes::Success(code) => code.get_code(),
59            ResponsesTypes::Redirection(code) => code.get_code(),
60            ResponsesTypes::ClientError(code) => code.get_code(),
61            ResponsesTypes::ServerError(code) => code.get_code(),
62            ResponsesTypes::ServiceError(code) => code.get_code(),
63            ResponsesTypes::CrawlerError(code) => code.get_code(),
64            ResponsesTypes::LocalApiError(code) => code.get_code(),
65        }
66    }
67
68    /// Converts the enum variant into a JSON representation.
69    pub fn as_json(&self) -> serde_json::Value {
70        match self {
71            ResponsesTypes::Informational(code) => code.as_json(),
72            ResponsesTypes::Success(code) => code.as_json(),
73            ResponsesTypes::Redirection(code) => code.as_json(),
74            ResponsesTypes::ClientError(code) => code.as_json(),
75            ResponsesTypes::ServerError(code) => code.as_json(),
76            ResponsesTypes::ServiceError(code) => code.as_json(),
77            ResponsesTypes::CrawlerError(code) => code.as_json(),
78            ResponsesTypes::LocalApiError(code) => code.as_json(),
79        }
80    }
81
82    /// Converts the enum variant into a tuple representation.
83    pub fn as_tuple(&self) -> HttpCode {
84        match self {
85            ResponsesTypes::Informational(code) => code.to_http_code(),
86            ResponsesTypes::Success(code) => code.to_http_code(),
87            ResponsesTypes::Redirection(code) => code.to_http_code(),
88            ResponsesTypes::ClientError(code) => code.to_http_code(),
89            ResponsesTypes::ServerError(code) => code.to_http_code(),
90            ResponsesTypes::ServiceError(code) => code.to_http_code(),
91            ResponsesTypes::CrawlerError(code) => code.to_http_code(),
92            ResponsesTypes::LocalApiError(code) => code.to_http_code(),
93        }
94    }
95
96    /// Attempts to construct a `ResponsesTypes` variant from a given `u16` code.
97    pub fn from_u16(code: u16) -> Option<Self> {
98        if let Some(c) = ResponsesInformationalCodes::from_u16(code) {
99            return Some(ResponsesTypes::Informational(c));
100        }
101        if let Some(c) = ResponsesSuccessCodes::from_u16(code) {
102            return Some(ResponsesTypes::Success(c));
103        }
104        if let Some(c) = ResponsesRedirectionCodes::from_u16(code) {
105            return Some(ResponsesTypes::Redirection(c));
106        }
107        if let Some(c) = ResponsesClientCodes::from_u16(code) {
108            return Some(ResponsesTypes::ClientError(c));
109        }
110        if let Some(c) = ResponsesServerCodes::from_u16(code) {
111            return Some(ResponsesTypes::ServerError(c));
112        }
113        if let Some(c) = ResponsesServiceCodes::from_u16(code) {
114            return Some(ResponsesTypes::ServiceError(c));
115        }
116        if let Some(c) = ResponsesCrawlerCodes::from_u16(code) {
117            return Some(ResponsesTypes::CrawlerError(c));
118        }
119        if let Some(c) = ResponsesLocalApiCodes::from_u16(code) {
120            return Some(ResponsesTypes::LocalApiError(c));
121        }
122        None
123    }
124
125    /// Returns the description associated with a response code.
126    pub fn get_description(&self) -> &'static str {
127        match self {
128            ResponsesTypes::Informational(code_enum) => {
129                code_enum.get_description_field("Description").unwrap_or("No description")
130            }
131            ResponsesTypes::Success(code_enum) => {
132                code_enum.get_description_field("Description").unwrap_or("No description")
133            }
134            ResponsesTypes::Redirection(code_enum) => {
135                code_enum.get_description_field("Description").unwrap_or("No description")
136            }
137            ResponsesTypes::ClientError(code_enum) => {
138                code_enum.get_description_field("Description").unwrap_or("No description")
139            }
140            ResponsesTypes::ServerError(code_enum) => {
141                code_enum.get_description_field("Description").unwrap_or("No description")
142            }
143            ResponsesTypes::ServiceError(code_enum) => {
144                code_enum.get_description_field("Description").unwrap_or("No description")
145            }
146            ResponsesTypes::CrawlerError(code_enum) => {
147                code_enum.get_description_field("Description").unwrap_or("No description")
148            }
149            ResponsesTypes::LocalApiError(code_enum) => {
150                code_enum.get_description_field("Description").unwrap_or("No description")
151            }
152        }
153    }
154
155    /// Returns the code and description associated with a response code.
156    pub fn get_response_get_description(&self) -> (u16, &'static str) {
157        match self {
158            ResponsesTypes::Informational(code) => {
159                (code.get_code(), code.get_description_field("Description").unwrap_or(""))
160            }
161            ResponsesTypes::Success(code) => {
162                (code.get_code(), code.get_description_field("Description").unwrap_or(""))
163            }
164            ResponsesTypes::Redirection(code) => {
165                (code.get_code(), code.get_description_field("Description").unwrap_or(""))
166            }
167            ResponsesTypes::ClientError(code) => {
168                (code.get_code(), code.get_description_field("Description").unwrap_or(""))
169            }
170            ResponsesTypes::ServerError(code) => {
171                (code.get_code(), code.get_description_field("Description").unwrap_or(""))
172            }
173            ResponsesTypes::ServiceError(code) => {
174                (code.get_code(), code.get_description_field("Description").unwrap_or(""))
175            }
176            ResponsesTypes::CrawlerError(code) => {
177                (code.get_code(), code.get_description_field("Description").unwrap_or(""))
178            }
179            ResponsesTypes::LocalApiError(code) => {
180                (code.get_code(), code.get_description_field("Description").unwrap_or(""))
181            }
182        }
183    }
184
185    /// Returns a normalized JSON representation of the response.
186    pub fn as_normalized_json(&self) -> serde_json::Value {
187        if let Some(normalized) = response_helpers::get_response_by_type(self) {
188            normalized.as_json()
189        } else {
190            self.as_json()
191        }
192    }
193
194    /// returns a destructured tuple (code, name, description).
195    pub fn to_tuple(&self) -> (u16, &'static str, &'static str) {
196        let http_code = self.as_tuple();
197        (http_code.standard_code, http_code.standard_name, http_code.unified_description)
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use serde_json::json;
205
206    #[test]
207    fn test_get_code() {
208        assert_eq!(
209            ResponsesTypes::CrawlerError(ResponsesCrawlerCodes::ParsingErrorUnfinishedHeader)
210                .get_code(),
211            400
212        );
213    }
214
215    #[test]
216    fn test_as_identical_tuple() {
217        // Case where the internal and standard codes are identical
218        let tuple_result = ResponsesTypes::Success(ResponsesSuccessCodes::Ok).as_tuple();
219        assert_eq!(tuple_result.standard_code, 200);
220        assert_eq!(tuple_result.standard_name, "OK");
221        assert_eq!(tuple_result.unified_description, "Request processed successfully. Response will depend on the request method used, and the result will be either a representation of the requested resource or an empty response");
222        assert_eq!(tuple_result.internal_code, None);
223        assert_eq!(tuple_result.internal_name, None);
224    }
225
226    #[test]
227    fn test_as_different_tuple() {
228        // Case where the internal and standard codes are different
229        let tuple_result_diff =
230            ResponsesTypes::ServerError(ResponsesServerCodes::OriginIsUnreachable).as_tuple();
231        assert_eq!(tuple_result_diff.standard_code, 502);
232        assert_eq!(tuple_result_diff.standard_name, "Bad Gateway");
233        assert_eq!(tuple_result_diff.unified_description, "The origin server could not be contacted. This might be due to network issues or misconfiguration");
234        assert_eq!(tuple_result_diff.internal_code, Some(523));
235        assert_eq!(tuple_result_diff.internal_name, Some("Origin Is Unreachable"));
236    }
237
238    #[test]
239    fn test_as_json() {
240        let json_value =
241            ResponsesTypes::ServerError(ResponsesServerCodes::OriginIsUnreachable).as_json();
242        let expected_json = json!({
243            "type": "Server errors",
244            "details": {
245                "standard http code": {
246                    "code": 502,
247                    "name": "Bad Gateway"
248                },
249                "description": "The origin server could not be contacted. This might be due to network issues or misconfiguration",
250                "internal http code": {
251                    "code": 523,
252                    "name": "Origin Is Unreachable"
253                }
254            }
255        });
256        assert_eq!(json_value, expected_json);
257    }
258
259    #[test]
260    fn test_from_u16() {
261        assert_eq!(
262            ResponsesTypes::from_u16(700),
263            Some(ResponsesTypes::CrawlerError(ResponsesCrawlerCodes::ParsingErrorUnfinishedHeader))
264        );
265        assert_eq!(ResponsesTypes::from_u16(999), None);
266    }
267
268    #[test]
269    fn test_as_normalized() {
270        let client_error = ResponsesTypes::ClientError(ResponsesClientCodes::BadRequest);
271
272        // recovery via get_response_by_type
273        let normalized = response_helpers::get_response_by_type(&client_error);
274
275        // Verification that standardization is possible
276        assert_eq!(normalized, Some(ResponsesTypes::ClientError(ResponsesClientCodes::BadRequest)));
277
278        // Verification of an unknown code
279        let unknown_code = ResponsesTypes::from_u16(9999);
280        let normalized_unknown =
281            unknown_code.as_ref().and_then(|code| response_helpers::get_response_by_type(code));
282
283        assert_eq!(normalized_unknown, None);
284    }
285
286    #[test]
287    fn test_get_advance_response_get_description() {
288        let client_error = ResponsesTypes::ClientError(ResponsesClientCodes::SSLCertificateError);
289        let (code, description) =
290            response_helpers::get_advance_response_get_description(client_error);
291
292        assert_eq!(code, 400);
293        assert_eq!(description, "An invalid or untrusted SSL certificate was encountered");
294    }
295}