http_tunnel_common/protocol/
response.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// Represents the response from the local service, sent back through the tunnel
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct HttpResponse {
7    /// Must match the request_id from the corresponding HttpRequest
8    pub request_id: String,
9
10    /// HTTP status code (200, 404, 500, etc.)
11    pub status_code: u16,
12
13    /// Response headers as a map of header name to list of values
14    pub headers: HashMap<String, Vec<String>>,
15
16    /// Response body encoded in Base64
17    #[serde(default)]
18    pub body: String,
19
20    /// Processing time in milliseconds (local service response time)
21    #[serde(default)]
22    pub processing_time_ms: u64,
23}
24
25impl HttpResponse {
26    /// Create a new HTTP response
27    pub fn new(request_id: String, status_code: u16) -> Self {
28        Self {
29            request_id,
30            status_code,
31            headers: HashMap::new(),
32            body: String::new(),
33            processing_time_ms: 0,
34        }
35    }
36
37    /// Check if the response has a body
38    pub fn has_body(&self) -> bool {
39        !self.body.is_empty()
40    }
41
42    /// Check if the response is successful (2xx status code)
43    pub fn is_success(&self) -> bool {
44        (200..300).contains(&self.status_code)
45    }
46
47    /// Check if the response is a client error (4xx status code)
48    pub fn is_client_error(&self) -> bool {
49        (400..500).contains(&self.status_code)
50    }
51
52    /// Check if the response is a server error (5xx status code)
53    pub fn is_server_error(&self) -> bool {
54        (500..600).contains(&self.status_code)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_http_response_creation() {
64        let res = HttpResponse::new("req_123".to_string(), 200);
65
66        assert_eq!(res.request_id, "req_123");
67        assert_eq!(res.status_code, 200);
68        assert!(res.headers.is_empty());
69        assert!(!res.has_body());
70        assert_eq!(res.processing_time_ms, 0);
71    }
72
73    #[test]
74    fn test_http_response_status_checks() {
75        let success = HttpResponse::new("req_1".to_string(), 200);
76        assert!(success.is_success());
77        assert!(!success.is_client_error());
78        assert!(!success.is_server_error());
79
80        let client_error = HttpResponse::new("req_2".to_string(), 404);
81        assert!(!client_error.is_success());
82        assert!(client_error.is_client_error());
83        assert!(!client_error.is_server_error());
84
85        let server_error = HttpResponse::new("req_3".to_string(), 500);
86        assert!(!server_error.is_success());
87        assert!(!server_error.is_client_error());
88        assert!(server_error.is_server_error());
89    }
90
91    #[test]
92    fn test_http_response_with_headers() {
93        let mut headers = HashMap::new();
94        headers.insert(
95            "content-type".to_string(),
96            vec!["application/json".to_string()],
97        );
98        headers.insert("x-custom-header".to_string(), vec!["value".to_string()]);
99
100        let res = HttpResponse {
101            request_id: "req_123".to_string(),
102            status_code: 200,
103            headers,
104            body: "eyJ0ZXN0IjoidmFsdWUifQ==".to_string(),
105            processing_time_ms: 123,
106        };
107
108        assert_eq!(res.headers.len(), 2);
109        assert!(res.has_body());
110        assert_eq!(res.processing_time_ms, 123);
111    }
112
113    #[test]
114    fn test_http_response_serialization() {
115        let mut headers = HashMap::new();
116        headers.insert("content-type".to_string(), vec!["text/plain".to_string()]);
117
118        let res = HttpResponse {
119            request_id: "req_abc123".to_string(),
120            status_code: 201,
121            headers,
122            body: "dGVzdCBkYXRh".to_string(), // "test data"
123            processing_time_ms: 456,
124        };
125
126        let json = serde_json::to_string(&res).unwrap();
127        assert!(json.contains(r#""request_id":"req_abc123"#));
128        assert!(json.contains(r#""status_code":201"#));
129        assert!(json.contains(r#""processing_time_ms":456"#));
130
131        let parsed: HttpResponse = serde_json::from_str(&json).unwrap();
132        assert_eq!(parsed.request_id, res.request_id);
133        assert_eq!(parsed.status_code, res.status_code);
134        assert_eq!(parsed.body, res.body);
135        assert_eq!(parsed.processing_time_ms, res.processing_time_ms);
136    }
137
138    #[test]
139    fn test_http_response_multiple_header_values() {
140        let mut headers = HashMap::new();
141        headers.insert(
142            "set-cookie".to_string(),
143            vec!["session=abc".to_string(), "token=xyz".to_string()],
144        );
145
146        let res = HttpResponse {
147            request_id: "req_123".to_string(),
148            status_code: 200,
149            headers,
150            body: String::new(),
151            processing_time_ms: 0,
152        };
153
154        assert_eq!(res.headers.get("set-cookie").unwrap().len(), 2);
155
156        let json = serde_json::to_string(&res).unwrap();
157        let parsed: HttpResponse = serde_json::from_str(&json).unwrap();
158        assert_eq!(parsed.headers.get("set-cookie").unwrap().len(), 2);
159    }
160
161    #[test]
162    fn test_http_response_defaults() {
163        let json = r#"{
164            "request_id": "req_123",
165            "status_code": 200,
166            "headers": {}
167        }"#;
168
169        let parsed: HttpResponse = serde_json::from_str(json).unwrap();
170        assert_eq!(parsed.body, "");
171        assert_eq!(parsed.processing_time_ms, 0);
172        assert!(!parsed.has_body());
173    }
174
175    #[test]
176    fn test_status_code_ranges() {
177        let codes = vec![
178            (100, false, false, false),
179            (200, true, false, false),
180            (299, true, false, false),
181            (300, false, false, false),
182            (400, false, true, false),
183            (404, false, true, false),
184            (499, false, true, false),
185            (500, false, false, true),
186            (503, false, false, true),
187            (599, false, false, true),
188        ];
189
190        for (code, is_success, is_client_err, is_server_err) in codes {
191            let res = HttpResponse::new("req".to_string(), code);
192            assert_eq!(
193                res.is_success(),
194                is_success,
195                "Failed for status code {}",
196                code
197            );
198            assert_eq!(
199                res.is_client_error(),
200                is_client_err,
201                "Failed for status code {}",
202                code
203            );
204            assert_eq!(
205                res.is_server_error(),
206                is_server_err,
207                "Failed for status code {}",
208                code
209            );
210        }
211    }
212}