1use super::config::ManagementConfig;
6use super::stats::StatsCollector;
7use super::json_response;
8use http_body_util::Full;
9use hyper::body::Bytes;
10use hyper::{Request, Response};
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
15pub struct ManagementResponse {
16 pub status: String,
18}
19
20#[derive(Debug, Clone)]
22pub struct ManagementHandler {
23 config: ManagementConfig,
24 stats: StatsCollector,
25}
26
27impl ManagementHandler {
28 pub fn new(config: ManagementConfig, stats: StatsCollector) -> Self {
30 Self { config, stats }
31 }
32
33 pub fn with_config(config: ManagementConfig) -> Self {
35 Self {
36 config,
37 stats: StatsCollector::new(),
38 }
39 }
40
41 pub fn stats(&self) -> &StatsCollector {
43 &self.stats
44 }
45
46 pub fn stats_collector(&self) -> StatsCollector {
48 self.stats.clone()
49 }
50
51 pub fn is_management_path(&self, path: &str) -> bool {
53 if !self.config.enabled {
54 return false;
55 }
56 path == self.config.health_path
57 || path == self.config.ready_path
58 || path == self.config.stats_path
59 }
60
61 pub fn handle_request(
63 &self,
64 req: &Request<hyper::body::Incoming>,
65 ) -> Option<Response<Full<Bytes>>> {
66 if !self.config.enabled {
67 return None;
68 }
69
70 let path = req.uri().path();
71
72 if path == self.config.health_path {
73 Some(self.handle_health())
74 } else if path == self.config.ready_path {
75 Some(self.handle_ready())
76 } else if path == self.config.stats_path {
77 Some(self.handle_stats())
78 } else {
79 None
80 }
81 }
82
83 pub fn handle_health(&self) -> Response<Full<Bytes>> {
86 let response = ManagementResponse {
87 status: "healthy".to_string(),
88 };
89 json_response(200, &serde_json::to_string(&response).unwrap())
90 }
91
92 pub fn handle_ready(&self) -> Response<Full<Bytes>> {
95 if self.stats.is_ready() {
96 let response = ManagementResponse {
97 status: "ready".to_string(),
98 };
99 json_response(200, &serde_json::to_string(&response).unwrap())
100 } else {
101 let response = ManagementResponse {
102 status: "not_ready".to_string(),
103 };
104 json_response(503, &serde_json::to_string(&response).unwrap())
105 }
106 }
107
108 pub fn handle_stats(&self) -> Response<Full<Bytes>> {
111 let stats = self.stats.get_stats();
112 json_response(200, &serde_json::to_string(&stats).unwrap())
113 }
114
115 pub fn health_path(&self) -> &str {
117 &self.config.health_path
118 }
119
120 pub fn ready_path(&self) -> &str {
122 &self.config.ready_path
123 }
124
125 pub fn stats_path(&self) -> &str {
127 &self.config.stats_path
128 }
129
130 pub fn is_enabled(&self) -> bool {
132 self.config.enabled
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use http_body_util::BodyExt;
140
141 #[test]
146 fn test_management_handler_creation() {
147 let config = ManagementConfig {
148 enabled: true,
149 health_path: "/health".to_string(),
150 ready_path: "/ready".to_string(),
151 stats_path: "/stats".to_string(),
152 };
153 let handler = ManagementHandler::with_config(config.clone());
154 assert!(handler.is_enabled());
155 assert_eq!(handler.health_path(), "/health");
156 assert_eq!(handler.ready_path(), "/ready");
157 assert_eq!(handler.stats_path(), "/stats");
158 }
159
160 #[test]
161 fn test_handle_health() {
162 let config = ManagementConfig {
163 enabled: true,
164 health_path: "/health".to_string(),
165 ready_path: "/ready".to_string(),
166 stats_path: "/stats".to_string(),
167 };
168 let handler = ManagementHandler::with_config(config);
169
170 let response = handler.handle_health();
171 assert_eq!(response.status(), 200);
172
173 let body = response.into_body();
174 let bytes = futures::executor::block_on(body.collect()).unwrap().to_bytes();
175 let body_str = String::from_utf8(bytes.to_vec()).unwrap();
176 assert!(body_str.contains("healthy"));
177 }
178
179 #[test]
180 fn test_handle_ready_when_ready() {
181 let config = ManagementConfig {
182 enabled: true,
183 health_path: "/health".to_string(),
184 ready_path: "/ready".to_string(),
185 stats_path: "/stats".to_string(),
186 };
187 let handler = ManagementHandler::with_config(config);
188 handler.stats().set_ready(true);
189
190 let response = handler.handle_ready();
191 assert_eq!(response.status(), 200);
192
193 let body = response.into_body();
194 let bytes = futures::executor::block_on(body.collect()).unwrap().to_bytes();
195 let body_str = String::from_utf8(bytes.to_vec()).unwrap();
196 assert!(body_str.contains("ready"));
197 }
198
199 #[test]
200 fn test_handle_ready_when_not_ready() {
201 let config = ManagementConfig {
202 enabled: true,
203 health_path: "/health".to_string(),
204 ready_path: "/ready".to_string(),
205 stats_path: "/stats".to_string(),
206 };
207 let handler = ManagementHandler::with_config(config);
208 handler.stats().set_ready(false);
209
210 let response = handler.handle_ready();
211 assert_eq!(response.status(), 503);
212
213 let body = response.into_body();
214 let bytes = futures::executor::block_on(body.collect()).unwrap().to_bytes();
215 let body_str = String::from_utf8(bytes.to_vec()).unwrap();
216 assert!(body_str.contains("not_ready"));
217 }
218
219 #[test]
220 fn test_handle_stats() {
221 let config = ManagementConfig {
222 enabled: true,
223 health_path: "/health".to_string(),
224 ready_path: "/ready".to_string(),
225 stats_path: "/stats".to_string(),
226 };
227 let handler = ManagementHandler::with_config(config);
228 handler.stats().increment_requests();
229 handler.stats().increment_connections();
230
231 let response = handler.handle_stats();
232 assert_eq!(response.status(), 200);
233
234 let body = response.into_body();
235 let bytes = futures::executor::block_on(body.collect()).unwrap().to_bytes();
236 let body_str = String::from_utf8(bytes.to_vec()).unwrap();
237 assert!(body_str.contains("total_requests"));
238 assert!(body_str.contains("active_connections"));
239 }
240
241 #[test]
242 fn test_is_management_path_enabled() {
243 let config = ManagementConfig {
244 enabled: true,
245 health_path: "/health".to_string(),
246 ready_path: "/ready".to_string(),
247 stats_path: "/stats".to_string(),
248 };
249 let handler = ManagementHandler::with_config(config);
250
251 assert!(handler.is_management_path("/health"));
252 assert!(handler.is_management_path("/ready"));
253 assert!(handler.is_management_path("/stats"));
254 assert!(!handler.is_management_path("/other"));
255 }
256
257 #[test]
258 fn test_is_management_path_disabled() {
259 let config = ManagementConfig {
260 enabled: false,
261 health_path: "/health".to_string(),
262 ready_path: "/ready".to_string(),
263 stats_path: "/stats".to_string(),
264 };
265 let handler = ManagementHandler::with_config(config);
266
267 assert!(!handler.is_management_path("/health"));
268 assert!(!handler.is_management_path("/ready"));
269 assert!(!handler.is_management_path("/stats"));
270 }
271
272 #[test]
278 fn test_custom_paths() {
279 let config = ManagementConfig {
280 enabled: true,
281 health_path: "/api/health".to_string(),
282 ready_path: "/api/ready".to_string(),
283 stats_path: "/api/stats".to_string(),
284 };
285 let handler = ManagementHandler::with_config(config);
286
287 assert!(handler.is_management_path("/api/health"));
288 assert!(handler.is_management_path("/api/ready"));
289 assert!(handler.is_management_path("/api/stats"));
290 assert!(!handler.is_management_path("/health"));
291 }
292
293 #[test]
294 fn test_handler_clone() {
295 let config = ManagementConfig {
296 enabled: true,
297 health_path: "/health".to_string(),
298 ready_path: "/ready".to_string(),
299 stats_path: "/stats".to_string(),
300 };
301 let handler = ManagementHandler::with_config(config);
302 handler.stats().increment_requests();
303
304 let cloned = handler.clone();
305 assert_eq!(cloned.stats().get_stats().total_requests, 1);
306 }
307
308 #[test]
309 fn test_stats_collector_from_handler() {
310 let config = ManagementConfig {
311 enabled: true,
312 health_path: "/health".to_string(),
313 ready_path: "/ready".to_string(),
314 stats_path: "/stats".to_string(),
315 };
316 let handler = ManagementHandler::with_config(config);
317 handler.stats().increment_requests();
318 handler.stats().add_bytes_sent(1000);
319
320 let collector = handler.stats_collector();
321 let stats = collector.get_stats();
322 assert_eq!(stats.total_requests, 1);
323 assert_eq!(stats.bytes_sent, 1000);
324 }
325
326 #[test]
327 fn test_management_response_serialization() {
328 let response = ManagementResponse {
329 status: "healthy".to_string(),
330 };
331 let json = serde_json::to_string(&response).unwrap();
332 assert_eq!(json, r#"{"status":"healthy"}"#);
333 }
334
335 #[test]
336 fn test_management_response_deserialization() {
337 let json = r#"{"status":"ready"}"#;
338 let response: ManagementResponse = serde_json::from_str(json).unwrap();
339 assert_eq!(response.status, "ready");
340 }
341
342 #[test]
343 fn test_management_response_equality() {
344 let response1 = ManagementResponse {
345 status: "healthy".to_string(),
346 };
347 let response2 = ManagementResponse {
348 status: "healthy".to_string(),
349 };
350 assert_eq!(response1, response2);
351 }
352
353 #[test]
354 fn test_new_with_config_and_stats() {
355 let config = ManagementConfig {
356 enabled: true,
357 health_path: "/health".to_string(),
358 ready_path: "/ready".to_string(),
359 stats_path: "/stats".to_string(),
360 };
361 let stats = StatsCollector::new();
362 stats.increment_requests();
363 stats.increment_requests();
364
365 let handler = ManagementHandler::new(config, stats);
366 assert_eq!(handler.stats().get_stats().total_requests, 2);
367 }
368
369 #[test]
370 fn test_stats_json_format() {
371 let config = ManagementConfig {
372 enabled: true,
373 health_path: "/health".to_string(),
374 ready_path: "/ready".to_string(),
375 stats_path: "/stats".to_string(),
376 };
377 let handler = ManagementHandler::with_config(config);
378 handler.stats().increment_requests();
379 handler.stats().record_cache_hit();
380 handler.stats().record_cache_miss();
381
382 let response = handler.handle_stats();
383 let body = response.into_body();
384 let bytes = futures::executor::block_on(body.collect()).unwrap().to_bytes();
385 let body_str = String::from_utf8(bytes.to_vec()).unwrap();
386
387 assert!(body_str.contains("\"active_connections\":0"));
389 assert!(body_str.contains("\"total_requests\":1"));
390 assert!(body_str.contains("\"cache_hit_rate\":0.5"));
391 }
392
393 #[test]
394 fn test_health_response_content_type() {
395 let config = ManagementConfig {
396 enabled: true,
397 health_path: "/health".to_string(),
398 ready_path: "/ready".to_string(),
399 stats_path: "/stats".to_string(),
400 };
401 let handler = ManagementHandler::with_config(config);
402
403 let response = handler.handle_health();
404 let content_type = response.headers().get("Content-Type").unwrap();
405 assert_eq!(content_type, "application/json");
406 }
407
408 #[test]
409 fn test_ready_response_content_type() {
410 let config = ManagementConfig {
411 enabled: true,
412 health_path: "/health".to_string(),
413 ready_path: "/ready".to_string(),
414 stats_path: "/stats".to_string(),
415 };
416 let handler = ManagementHandler::with_config(config);
417
418 let response = handler.handle_ready();
419 let content_type = response.headers().get("Content-Type").unwrap();
420 assert_eq!(content_type, "application/json");
421 }
422
423 #[test]
424 fn test_stats_response_content_type() {
425 let config = ManagementConfig {
426 enabled: true,
427 health_path: "/health".to_string(),
428 ready_path: "/ready".to_string(),
429 stats_path: "/stats".to_string(),
430 };
431 let handler = ManagementHandler::with_config(config);
432
433 let response = handler.handle_stats();
434 let content_type = response.headers().get("Content-Type").unwrap();
435 assert_eq!(content_type, "application/json");
436 }
437
438 #[test]
439 fn test_handler_debug() {
440 let config = ManagementConfig {
441 enabled: true,
442 health_path: "/health".to_string(),
443 ready_path: "/ready".to_string(),
444 stats_path: "/stats".to_string(),
445 };
446 let handler = ManagementHandler::with_config(config);
447 let debug_str = format!("{:?}", handler);
448 assert!(debug_str.contains("ManagementHandler"));
449 }
450
451 #[test]
452 fn test_multiple_requests_increment_counter() {
453 let config = ManagementConfig {
454 enabled: true,
455 health_path: "/health".to_string(),
456 ready_path: "/ready".to_string(),
457 stats_path: "/stats".to_string(),
458 };
459 let handler = ManagementHandler::with_config(config);
460
461 for _ in 0..10 {
463 handler.stats().increment_requests();
464 }
465
466 let response = handler.handle_stats();
467 let body = response.into_body();
468 let bytes = futures::executor::block_on(body.collect()).unwrap().to_bytes();
469 let body_str = String::from_utf8(bytes.to_vec()).unwrap();
470 assert!(body_str.contains("\"total_requests\":10"));
471 }
472
473 #[test]
474 fn test_health_not_ready_state() {
475 let config = ManagementConfig {
476 enabled: true,
477 health_path: "/health".to_string(),
478 ready_path: "/ready".to_string(),
479 stats_path: "/stats".to_string(),
480 };
481 let handler = ManagementHandler::with_config(config);
482
483 handler.stats().set_ready(false);
485 let response = handler.handle_health();
486 assert_eq!(response.status(), 200);
487 }
488}