simbld_http/helpers/
response_helpers.rs

1//! # HTTP Response Utilities
2//!
3//! This module provides comprehensive utilities for working with HTTP responses.
4//! It includes functions for creating, manipulating, transforming, and filtering
5//! HTTP response codes and messages in various formats (JSON, XML).
6//!
7//! The module supports:
8//! - Looking up response information by code or type
9//! - Transforming responses to JSON and XML formats
10//! - Filtering responses by code ranges
11//! - Creating enriched responses with metadata
12//! - CORS validation for responses
13//!
14//! These utilities are designed to provide a consistent API for handling
15//! HTTP responses throughout the application.
16
17use crate::responses::ResponsesTypes;
18use crate::responses::{
19    ResponsesClientCodes, ResponsesCrawlerCodes, ResponsesInformationalCodes,
20    ResponsesLocalApiCodes, ResponsesRedirectionCodes, ResponsesServerCodes, ResponsesServiceCodes,
21    ResponsesSuccessCodes,
22};
23/// The code provides functions for handling HTTP response codes, including retrieving descriptions, converting to JSON/XML, filtering by range, and adding metadata.
24use crate::traits::get_description_trait::GetDescription;
25use crate::utils::populate_metadata::populate_metadata;
26use serde_json::{json, Value};
27use std::collections::HashMap;
28use std::time::Duration;
29use std::time::SystemTime;
30use strum::IntoEnumIterator;
31
32/// Returns the standard code and description for a given response type.
33pub fn get_response_get_description(response: ResponsesTypes) -> (u16, &'static str) {
34    let code = response.get_code();
35    let description = response.get_description_field("Description").unwrap_or("No description");
36    (code, description)
37}
38
39/// Returns the code and extended description for a given response type.
40pub fn get_advance_response_get_description(response: ResponsesTypes) -> (u16, &'static str) {
41    if let Some(normalized_response) = get_response_by_type(&response) {
42        (normalized_response.get_code(), normalized_response.get_description())
43    } else {
44        (response.get_code(), response.get_description())
45    };
46
47    let timestamp = SystemTime::now();
48
49    // Simulate a middleware call here to record logs
50    log::info!(
51        "Fetching description for code: {}, timestamp: {:?}",
52        response.get_code(),
53        timestamp
54    );
55
56    // Simulate a CORS check or any other advanced security logic
57    if let Ok(origin) = std::env::var("ALLOWED_ORIGIN") {
58        log::debug!("Applying CORS check for origin: {}", origin);
59    } else {
60        log::warn!("No ALLOWED_ORIGIN set; defaulting to open.");
61    }
62
63    // Provide a fallback description if not present
64    let code = response.get_code();
65    let description = response.get_description_field("Description").unwrap_or("No description");
66
67    (code, description)
68}
69
70/// Retrieves a response type based on its HTTP status code.
71pub fn get_response_by_code(code: u16) -> Option<ResponsesTypes> {
72    ResponsesTypes::from_u16(code)
73}
74
75/// Matches an HTTP response type and returns the corresponding `ResponsesTypes` enum.
76/// Arguments:
77/// * `rtype`: a reference to an instance of` responsibility '.
78///
79/// refers:
80/// * `Option <A responsibility>` corresponding to the code `U16 'of the` rtype`, or `non -` if no match.
81pub fn get_response_by_type(rtype: &ResponsesTypes) -> Option<ResponsesTypes> {
82    let (code, _) = get_response_get_description(*rtype);
83    get_response_by_code(code)
84}
85
86/// Fetches the description for a specific HTTP code.
87pub fn get_description_by_code(code: u16) -> Option<&'static str> {
88    get_response_by_code(code).and_then(|r| r.get_description().into())
89}
90
91/// Matches the input code with predefined HTTP response codes and returns the corresponding description as a static string if a match is found.
92pub fn get_advance_description_by_code(code: u16) -> Option<&'static str> {
93    log::info!("Fetching description for code: {}", code);
94
95    let fetched_description = get_response_by_code(code).map(|response_type| {
96        let description = GetDescription::get_description_field(&response_type, "Description")
97            .unwrap_or("No description");
98        log::debug!("Code {} corresponds to description: {}", code, description);
99        description
100    });
101
102    if fetched_description.is_none() {
103        log::warn!("No response type found for code: {}", code);
104    }
105
106    fetched_description
107}
108
109/// Converts a `ResponsesTypes` into a short JSON string. Includes a fallback if the response is not found.
110pub fn transform_to_json_short(response: ResponsesTypes) -> String {
111    let (code, description) = get_response_get_description(response);
112    json!({
113        "code": code,
114        "description": description
115    })
116    .to_string()
117}
118
119/// Converts a `ResponsesTypes` into a detailed JSON string with fallback for missing descriptions.
120pub fn transform_to_json(response: ResponsesTypes) -> String {
121    let code = response.get_code();
122    let description = response.get_description();
123    json!({
124        "code": code,
125        "description": description
126    })
127    .to_string()
128}
129/// Converts a `ResponsesTypes` into JSON only for standard HTTP codes (100–599). Returns `None` for invalid codes.
130pub fn transform_to_json_filtered(response: ResponsesTypes) -> Option<String> {
131    let (code, description) = get_response_get_description(response);
132    if !(100..=599).contains(&code) {
133        None
134    } else {
135        Some(
136            json!({
137                "code": code,
138                "description": description,
139                "is_standard_code": true
140            })
141            .to_string(),
142        )
143    }
144}
145
146/// Converts a `ResponsesTypes` into JSON, enriched with metadata like timestamps and status families.
147pub fn transform_to_json_with_metadata(response: ResponsesTypes) -> String {
148    let (code, description) = get_response_get_description(response);
149    let timestamp = chrono::Utc::now();
150    let metadata = json!({
151        "requested_at": timestamp.to_rfc3339(),
152        "status_family": match code {
153            100..=199 => "Informational",
154            200..=299 => "Success",
155            300..=399 => "Redirection",
156            400..=499 => "Client Error",
157            500..=599 => "Server Error",
158            600..=699 => "Service Error",
159            700..=799 => "Crawler Error",
160            900..=999 => "Local API Error",
161            _ => "Unknown",
162        },
163        "is_error": code >= 400,
164    });
165
166    json!({
167        "code": code,
168        "description": description,
169        "metadata": metadata
170    })
171    .to_string()
172}
173
174/// Converts a `ResponsesTypes` into a simple XML string. Includes a fallback for missing responses.
175pub fn transform_to_xml_short(response: ResponsesTypes) -> String {
176    let (code, description) = get_response_get_description(response);
177    format!(
178        r#"<response><code>{}</code><description>{}</description></response>"#,
179        code, description
180    )
181}
182
183/// Converts a `ResponsesTypes` into a detailed XML string. Handles missing descriptions gracefully.
184pub fn transform_to_xml(response: ResponsesTypes) -> String {
185    let code = response.get_code();
186    let description = response.get_description();
187    format!("<response><code>{}</code><description>{}</description></response>", code, description)
188}
189
190/// Converts a `ResponsesTypes` into an XML string for valid HTTP codes (100–599). Returns `None` for invalid codes.
191pub fn transform_to_xml_filtered(response: ResponsesTypes) -> Option<String> {
192    let (code, description) = get_response_get_description(response);
193    if !(100..=599).contains(&code) {
194        None
195    } else {
196        Some(format!(
197            r#"<response><code>{}</code><description>{}</description><is_standard_code>true</is_standard_code></response>"#,
198            code, description
199        ))
200    }
201}
202
203/// Converts a `ResponsesTypes` into an XML string enriched with metadata such as timestamps and status families.
204pub fn transform_to_xml_with_metadata(response: ResponsesTypes) -> String {
205    let (code, description) = get_response_get_description(response);
206    let timestamp = chrono::Utc::now();
207    let status_family = match code {
208        100..=199 => "Informational",
209        200..=299 => "Success",
210        300..=399 => "Redirection",
211        400..=499 => "Client Error",
212        500..=599 => "Server Error",
213        600..=699 => "Service Error",
214        700..=799 => "Crawler Error",
215        900..=999 => "Local API Error",
216        _ => "Unknown",
217    };
218    let is_error = code >= 400;
219
220    format!(
221        r#"<response>
222  <code>{}</code>
223  <description>{}</description>
224  <metadata>
225    <requested_at>{}</requested_at>
226    <status_family>{}</status_family>
227    <is_error>{}</is_error>
228  </metadata>
229</response>"#,
230        code,
231        description,
232        timestamp.to_rfc3339(),
233        status_family,
234        is_error
235    )
236}
237
238/// Filters predefined response codes based on a specified range. The function takes two input parameters, `start` and `end`, representing the lower and upper bounds of the range, respectively. It returns a vector containing tuples of response codes and descriptions that fall within the specified range.
239pub fn filter_codes_by_range(start: u16, end: u16) -> Vec<(u16, &'static str)> {
240    let mut filtered_codes = Vec::new();
241
242    add_filtered_codes(
243        start,
244        end,
245        ResponsesInformationalCodes::iter().map(ResponsesTypes::Informational),
246        &mut filtered_codes,
247    );
248    add_filtered_codes(
249        start,
250        end,
251        ResponsesSuccessCodes::iter().map(ResponsesTypes::Success),
252        &mut filtered_codes,
253    );
254    add_filtered_codes(
255        start,
256        end,
257        ResponsesRedirectionCodes::iter().map(ResponsesTypes::Redirection),
258        &mut filtered_codes,
259    );
260    add_filtered_codes(
261        start,
262        end,
263        ResponsesClientCodes::iter().map(ResponsesTypes::ClientError),
264        &mut filtered_codes,
265    );
266    add_filtered_codes(
267        start,
268        end,
269        ResponsesServerCodes::iter().map(ResponsesTypes::ServerError),
270        &mut filtered_codes,
271    );
272    add_filtered_codes(
273        start,
274        end,
275        ResponsesServiceCodes::iter().map(ResponsesTypes::ServiceError),
276        &mut filtered_codes,
277    );
278    add_filtered_codes(
279        start,
280        end,
281        ResponsesCrawlerCodes::iter().map(ResponsesTypes::CrawlerError),
282        &mut filtered_codes,
283    );
284    add_filtered_codes(
285        start,
286        end,
287        ResponsesLocalApiCodes::iter().map(ResponsesTypes::LocalApiError),
288        &mut filtered_codes,
289    );
290
291    filtered_codes
292}
293
294/// Helper function to add filtered codes to a result collection.
295///
296/// # Arguments
297/// * `start` - Starting code (inclusive)
298/// * `end` - Ending code (inclusive)
299/// * `codes_iter` - Iterator of response types to filter
300/// * `filtered_codes` - Output collection for filtered codes
301fn add_filtered_codes<I>(
302    start: u16,
303    end: u16,
304    codes_iter: I,
305    filtered_codes: &mut Vec<(u16, &'static str)>,
306) where
307    I: Iterator<Item = ResponsesTypes>,
308{
309    for response in codes_iter {
310        let (code, description) = get_response_get_description(response);
311        if code >= start && code <= end && !filtered_codes.iter().any(|(c, _)| *c == code) {
312            filtered_codes.push((code, description));
313        }
314    }
315}
316
317/// Type alias
318pub type ResponseCodeWithMetadata = (u16, &'static str, HashMap<String, String>);
319pub type ResponseCodeIterator = Box<dyn Iterator<Item = ResponseCodeWithMetadata>>;
320pub type RequestMetadata = Option<HashMap<&'static str, &'static str>>;
321
322/// Returns response codes within the specified range, with additional metadata.
323pub fn filter_codes_by_range_with_metadata(
324    start: u16,
325    end: u16,
326    request_metadata: RequestMetadata,
327) -> Vec<ResponseCodeWithMetadata> {
328    let iterators: Vec<ResponseCodeIterator> = vec![
329        Box::new(ResponsesInformationalCodes::iter().map({
330            let request_metadata = request_metadata.clone();
331            move |code| {
332                let rtype = ResponsesTypes::Informational(code);
333                create_tuple_with_metadata(rtype, &request_metadata)
334            }
335        })),
336        Box::new(ResponsesSuccessCodes::iter().map({
337            let request_metadata = request_metadata.clone();
338            move |code| {
339                let rtype = ResponsesTypes::Success(code);
340                create_tuple_with_metadata(rtype, &request_metadata)
341            }
342        })),
343        Box::new(ResponsesRedirectionCodes::iter().map({
344            let request_metadata = request_metadata.clone();
345            move |code| {
346                let rtype = ResponsesTypes::Redirection(code);
347                create_tuple_with_metadata(rtype, &request_metadata)
348            }
349        })),
350        Box::new(ResponsesClientCodes::iter().map({
351            let request_metadata = request_metadata.clone();
352            move |code| {
353                let rtype = ResponsesTypes::ClientError(code);
354                create_tuple_with_metadata(rtype, &request_metadata)
355            }
356        })),
357        Box::new(ResponsesServerCodes::iter().map({
358            let request_metadata = request_metadata.clone();
359            move |code| {
360                let rtype = ResponsesTypes::ServerError(code);
361                create_tuple_with_metadata(rtype, &request_metadata)
362            }
363        })),
364        Box::new(ResponsesServiceCodes::iter().map({
365            let request_metadata = request_metadata.clone();
366            move |code| {
367                let rtype = ResponsesTypes::ServiceError(code);
368                create_tuple_with_metadata(rtype, &request_metadata)
369            }
370        })),
371        Box::new(ResponsesCrawlerCodes::iter().map({
372            let request_metadata = request_metadata.clone();
373            move |code| {
374                let rtype = ResponsesTypes::CrawlerError(code);
375                create_tuple_with_metadata(rtype, &request_metadata)
376            }
377        })),
378        Box::new(ResponsesLocalApiCodes::iter().map({
379            let request_metadata = request_metadata.clone();
380            move |code| {
381                let rtype = ResponsesTypes::LocalApiError(code);
382                create_tuple_with_metadata(rtype, &request_metadata)
383            }
384        })),
385    ];
386
387    let mut results = Vec::new();
388    for iterator in iterators {
389        for (code_u16, desc, meta) in iterator {
390            if code_u16 >= start && code_u16 <= end {
391                results.push((code_u16, desc, meta));
392            }
393        }
394    }
395
396    results
397}
398
399/// Constructs a basic response tuple (status_code, description, metadata) from the given response type.
400fn create_tuple_with_metadata(
401    rtype: ResponsesTypes,
402    request_metadata: &RequestMetadata,
403) -> ResponseCodeWithMetadata {
404    let metadata = request_metadata.clone().map_or_else(HashMap::new, |meta| {
405        meta.into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
406    });
407
408    (rtype.get_code(), rtype.get_description(), metadata)
409}
410
411/// Transforms a response type into a complete response tuple with contextual metadata from the request.
412pub fn list_codes_and_descriptions_short(family: &str) -> Vec<(u16, &'static str)> {
413    #[allow(clippy::type_complexity)]
414    let iterator: Box<dyn Iterator<Item = (u16, &'static str)>> = match family {
415        "Informational" => Box::new(
416            ResponsesInformationalCodes::iter()
417                .map(|c: ResponsesInformationalCodes| (c.get_code(), c.get_description())),
418        ),
419        "Success" => Box::new(
420            ResponsesSuccessCodes::iter()
421                .map(|c: ResponsesSuccessCodes| (c.get_code(), c.get_description())),
422        ),
423        "Redirection" => Box::new(
424            ResponsesRedirectionCodes::iter()
425                .map(|c: ResponsesRedirectionCodes| (c.get_code(), c.get_description())),
426        ),
427        "ClientError" => Box::new(
428            ResponsesClientCodes::iter()
429                .map(|c: ResponsesClientCodes| (c.get_code(), c.get_description())),
430        ),
431        "ServerError" => Box::new(
432            ResponsesServerCodes::iter()
433                .map(|c: ResponsesServerCodes| (c.get_code(), c.get_description())),
434        ),
435        "Service" => Box::new(
436            ResponsesServiceCodes::iter()
437                .map(|c: ResponsesServiceCodes| (c.get_code(), c.get_description())),
438        ),
439        "Crawler" => Box::new(
440            ResponsesCrawlerCodes::iter()
441                .map(|c: ResponsesCrawlerCodes| (c.get_code(), c.get_description())),
442        ),
443        "LocalApi" => Box::new(
444            ResponsesLocalApiCodes::iter()
445                .map(|c: ResponsesLocalApiCodes| (c.get_code(), c.get_description())),
446        ),
447        _ => Box::new(std::iter::empty()),
448    };
449
450    iterator.collect()
451}
452/// Lists response codes and descriptions for a given family with extended metadata.
453pub fn list_codes_and_descriptions_with_metadata(
454    family: &str,
455    request_metadata: Option<HashMap<&str, &str>>,
456) -> Vec<ResponseCodeWithMetadata> {
457    let iterator: Box<dyn Iterator<Item = (u16, &'static str)>> = match family {
458        "Informational" => Box::new(ResponsesInformationalCodes::iter().map(|c| {
459            (c.get_code(), c.get_description_field("Description").unwrap_or("No description"))
460        })),
461        "Success" => Box::new(ResponsesSuccessCodes::iter().map(|c| {
462            (c.get_code(), c.get_description_field("Description").unwrap_or("No description"))
463        })),
464        "Redirection" => Box::new(ResponsesRedirectionCodes::iter().map(|c| {
465            (c.get_code(), c.get_description_field("Description").unwrap_or("No description"))
466        })),
467        "ClientError" => Box::new(ResponsesClientCodes::iter().map(|c| {
468            (c.get_code(), c.get_description_field("Description").unwrap_or("No description"))
469        })),
470        "ServerError" => Box::new(ResponsesServerCodes::iter().map(|c| {
471            (c.get_code(), c.get_description_field("Description").unwrap_or("No description"))
472        })),
473        "ServiceError" => Box::new(ResponsesServiceCodes::iter().map(|c| {
474            (c.get_code(), c.get_description_field("Description").unwrap_or("No description"))
475        })),
476        "CrawlerError" => Box::new(ResponsesCrawlerCodes::iter().map(|c| {
477            (c.get_code(), c.get_description_field("Description").unwrap_or("No description"))
478        })),
479        "LocalApiError" => Box::new(ResponsesLocalApiCodes::iter().map(|c| {
480            (c.get_code(), c.get_description_field("Description").unwrap_or("No description"))
481        })),
482        _ => return vec![],
483    };
484
485    iterator
486        .map(|(code, description)| {
487            let metadata = populate_metadata(code, description, request_metadata.clone());
488            (code, description, metadata)
489        })
490        .collect()
491}
492
493/// Generates a complete response string in JSON format based on input parameters. The function takes three input parameters: `code`, `description`, and `data`.
494pub fn create_response(code: u16, description: &str, data: &str) -> String {
495    json!({
496        "code": code,
497        "description": description,
498        "data": serde_json::from_str::<Value>(data).unwrap_or(Value::Null)
499    })
500    .to_string()
501}
502
503/// Generates a complete response string in XML format based on input parameters. The function takes three input parameters: `code`, `description`, and `data`.
504pub fn create_response_xml(code: u16, description: &str, data: &str) -> String {
505    format!(
506        r#"<response><code>{}</code><description>{}</description><data>{}</data></response>"#,
507        code, description, data
508    )
509}
510
511/// Converts a `u16` code to an `Option` containing the corresponding `ResponsesTypes` enumeration variant. The function reuses the existing `get_response_by_code` function to perform the conversion.
512pub fn convert_to_enum(code: u16) -> Option<ResponsesTypes> {
513    get_response_by_code(code)
514}
515
516/// Generates a complete response string in JSON format based on input parameters. The function takes two input parameters: `response_type` and `data`.
517pub fn create_response_with_types(
518    response_type: Option<ResponsesTypes>,
519    data: Option<&str>,
520) -> String {
521    let mut map = serde_json::Map::new();
522    if let Some(rt) = response_type {
523        map.insert("code".to_string(), json!(rt.get_code()));
524        let description = rt.get_description();
525        map.insert("description".to_string(), json!(description));
526    }
527    if let Some(d) = data {
528        let data_value: Value = serde_json::from_str(d).unwrap();
529        map.extend(data_value.as_object().unwrap().clone());
530    }
531    Value::Object(map).to_string()
532}
533
534/// Fetches a full response with CORS headers and metadata.
535pub fn get_enriched_response_with_metadata(
536    response: ResponsesTypes,
537    cors_origin: Option<&str>,
538    duration: Duration,
539) -> String {
540    let (code, description) = get_response_get_description(response);
541    let timestamp = chrono::Utc::now();
542
543    // Add CORS headers
544    let cors_headers = match cors_origin {
545        Some(origin) => json!({ "Access-Control-Allow-Origin": origin }),
546        None => json!({ "Access-Control-Allow-Origin": "*" }),
547    };
548
549    // Metadata
550    let metadata = json!({
551        "requested_at": timestamp.to_rfc3339(),
552        "status_family": match code {
553            100..=199 => "Informational",
554            200..=299 => "Success",
555            300..=399 => "Redirection",
556            400..=499 => "Client Error",
557            500..=599 => "Server Error",
558            _ => "Unknown",
559        },
560        "is_error": code >= 400,
561        "processing_time_ms": duration.as_millis()
562    });
563
564    json!({
565        "code": code,
566        "description": description,
567        "headers": cors_headers,
568        "metadata": metadata
569    })
570    .to_string()
571}
572
573/// Validates if the given origin is allowed based on a list of allowed origins.
574pub fn is_origin_allowed(origin: &str, allowed_origins: &[&str]) -> bool {
575    allowed_origins.contains(&origin)
576}
577
578#[cfg(test)]
579mod tests {
580    use super::*;
581    use crate::responses::{ResponsesSuccessCodes, ResponsesTypes};
582    use serde_json::json;
583
584    #[test]
585    fn test_get_response_by_code() {
586        let response = get_response_by_code(200);
587        assert!(response.is_some());
588        assert_eq!(response.unwrap().get_code(), 200);
589    }
590
591    #[test]
592    fn test_get_description_by_code() {
593        let description = get_description_by_code(200);
594        assert!(description.is_some());
595        assert_eq!(
596            description.unwrap(),
597            "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"
598        );
599    }
600
601    #[test]
602    fn test_transform_to_json() {
603        let response = ResponsesTypes::Success(ResponsesSuccessCodes::Ok);
604        let json_str = transform_to_json(response);
605        let expected_json = json!({
606        "code": 200,
607        "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"
608    })
609            .to_string();
610        assert_eq!(json_str, expected_json);
611    }
612
613    #[test]
614    fn test_transform_to_json_with_metadata() {
615        let response = ResponsesTypes::Success(ResponsesSuccessCodes::Ok);
616        let json_str = transform_to_json_with_metadata(response);
617        assert!(json_str.contains("\"requested_at\""));
618        assert!(json_str.contains("\"status_family\":\"Success\""));
619    }
620
621    #[test]
622    fn test_transform_to_json_filtered() {
623        let response = ResponsesTypes::Success(ResponsesSuccessCodes::Ok);
624        let json_str = transform_to_json_filtered(response).unwrap();
625        assert!(json_str.contains("\"is_standard_code\":true"));
626    }
627
628    #[test]
629    fn test_transform_to_xml() {
630        let response = ResponsesTypes::Success(ResponsesSuccessCodes::Ok);
631        let xml_str = transform_to_xml(response);
632        let expected_xml = r#"<response><code>200</code><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</description></response>"#;
633        assert_eq!(xml_str, expected_xml);
634    }
635
636    #[test]
637    fn test_transform_to_xml_with_metadata() {
638        let response = ResponsesTypes::Success(ResponsesSuccessCodes::Ok);
639        let xml_str = transform_to_xml_with_metadata(response);
640        assert!(xml_str.contains("<requested_at>"));
641        assert!(xml_str.contains("<status_family>Success</status_family>"));
642    }
643
644    #[test]
645    fn test_transform_to_xml_filtered() {
646        let response = ResponsesTypes::Success(ResponsesSuccessCodes::Ok);
647        let xml_str = transform_to_xml_filtered(response).unwrap();
648        assert!(xml_str.contains("<is_standard_code>true</is_standard_code>"));
649    }
650
651    #[test]
652    fn test_get_response_by_code_unknown() {
653        let response = get_response_by_code(9999);
654        assert!(response.is_none());
655    }
656
657    #[test]
658    fn test_filter_codes_by_range() {
659        let codes = filter_codes_by_range(100, 103);
660        let expected_codes = vec![
661            (
662                100,
663                "The server has received the initial part of the request, the headers, and asks the client to continue request, proceed to send the body of the request, a POST request",
664            ),
665            (
666                101,
667                "The server is complying with a request to switch protocols, used in WebSocket connections",
668            ),
669            (
670                102,
671                "Indicates the server is processing the request but has not yet finished, used to prevent timeout errors in asynchronous operations, webdav RFC 2518",
672            ),
673            (
674                103,
675                "Experimental: The server provides preliminary hints to the client, such as preloading resources while the final response is being prepared",
676            ),
677        ];
678        assert_eq!(codes, expected_codes);
679    }
680
681    #[test]
682    fn test_filter_codes_by_range_with_metadata() {
683        let request_metadata = Some(std::collections::HashMap::from([("source", "test_case")]));
684        let codes = filter_codes_by_range_with_metadata(200, 299, request_metadata);
685        assert!(!codes.is_empty());
686        assert!(codes[0].2.contains_key("source"));
687    }
688
689    #[test]
690    fn test_filter_codes_by_range_by_length() {
691        let codes = filter_codes_by_range(200, 201);
692        assert_eq!(codes.len(), 2);
693    }
694
695    #[test]
696    fn test_list_codes_and_descriptions_with_metadata() {
697        let request_metadata = Some(std::collections::HashMap::from([("source", "unit_test")]));
698        let descriptions = list_codes_and_descriptions_with_metadata("Success", request_metadata);
699        assert!(!descriptions.is_empty());
700        assert!(descriptions[0].2.contains_key("source"));
701    }
702
703    #[test]
704    fn test_list_codes_and_descriptions_short() {
705        let informational = list_codes_and_descriptions_short("Informational");
706        assert!(!informational.is_empty());
707        assert_eq!(informational[0].0, 100);
708
709        let success = list_codes_and_descriptions_short("Success");
710        assert!(!success.is_empty());
711        assert_eq!(success[0].0, 200);
712
713        let unknown = list_codes_and_descriptions_short("UnknownFamily");
714        assert!(unknown.is_empty());
715    }
716
717    #[test]
718    fn test_create_response_with_types() {
719        let response = Some(ResponsesTypes::Success(ResponsesSuccessCodes::Ok));
720        let json_response = create_response_with_types(response, Some(r#"{"key": "value"}"#));
721        println!("JSON Response: {}", json_response);
722        assert!(json_response.contains("\"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\""));
723        assert!(json_response.contains("\"key\":\"value\""));
724    }
725
726    #[test]
727    fn test_create_response() {
728        let code = 200;
729        let description = "OK";
730        let data = r#"{"message": "Success"}"#;
731        let response = create_response(code, description, data);
732        let expected_response = json!({
733            "code": 200,
734            "description": "OK",
735            "data": {
736                "message": "Success"
737            }
738        })
739        .to_string();
740        assert_eq!(response, expected_response);
741    }
742
743    #[test]
744    fn test_create_response_without_json() {
745        let code = 200;
746        let description = "OK";
747        let data = r#"{"message": "Success"}"#;
748        let response = create_response(code, description, data);
749        assert!(response.contains("\"code\":200"));
750        assert!(response.contains("\"description\":\"OK\""));
751        assert!(response.contains("\"message\":\"Success\""));
752    }
753
754    #[test]
755    fn test_create_response_xml() {
756        let code = 200;
757        let description = "OK";
758        let data = "<message>Success</message>";
759        let response = create_response_xml(code, description, data);
760        let expected_response = format!(
761            r#"<response><code>{}</code><description>{}</description><data>{}</data></response>"#,
762            code, description, data
763        );
764        assert_eq!(response, expected_response);
765    }
766
767    #[test]
768    fn test_convert_to_enum() {
769        let code = 200;
770        let response_type = convert_to_enum(code);
771        assert!(response_type.is_some());
772        assert_eq!(response_type.unwrap().get_code(), 200);
773    }
774
775    #[test]
776    fn test_get_enriched_response_with_metadata() {
777        let response = ResponsesTypes::Success(ResponsesSuccessCodes::Ok);
778        let enriched_response = get_enriched_response_with_metadata(
779            response,
780            Some("https://example.com"),
781            Duration::from_millis(150),
782        );
783        assert!(enriched_response.contains("\"code\":200"));
784        assert!(
785            enriched_response.contains("\"Access-Control-Allow-Origin\":\"https://example.com\"")
786        );
787    }
788
789    #[test]
790    fn test_is_origin_allowed() {
791        let allowed_origins = vec!["https://example.com", "https://localhost"];
792        assert!(is_origin_allowed("https://example.com", &allowed_origins));
793        assert!(!is_origin_allowed("https://unauthorized.com", &allowed_origins));
794    }
795
796    #[test]
797    fn test_get_response_by_type() {
798        let client_error = ResponsesTypes::ClientError(ResponsesClientCodes::BadRequest);
799        let result = get_response_by_type(&client_error);
800        let _unknown_response = ResponsesTypes::from_u16(9999);
801
802        assert!(result.is_some());
803        assert_eq!(result.unwrap().get_code(), 400);
804    }
805}