ferro_rs/middleware/
metrics.rs1use crate::http::Request;
7use crate::http::Response;
8use crate::metrics;
9use crate::middleware::{Middleware, Next};
10use async_trait::async_trait;
11use std::time::Instant;
12
13#[derive(Debug, Clone, Copy, Default)]
31pub struct MetricsMiddleware;
32
33impl MetricsMiddleware {
34 pub fn new() -> Self {
36 Self
37 }
38}
39
40#[async_trait]
41impl Middleware for MetricsMiddleware {
42 async fn handle(&self, request: Request, next: Next) -> Response {
43 if !metrics::is_enabled() {
45 return next(request).await;
46 }
47
48 let path = request.path();
50 if path.starts_with("/_ferro/") {
51 return next(request).await;
52 }
53
54 let start = Instant::now();
55 let method = request.method().to_string();
56
57 let route_pattern = request
60 .route_pattern()
61 .unwrap_or_else(|| "UNMATCHED".to_string());
62
63 let response = next(request).await;
65
66 let duration = start.elapsed();
67
68 let is_error = match &response {
70 Ok(resp) => resp.status_code() >= 400,
71 Err(resp) => resp.status_code() >= 400,
72 };
73
74 metrics::record_request(&route_pattern, &method, duration, is_error);
76
77 response
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_metrics_middleware_new() {
87 let middleware = MetricsMiddleware::new();
88 assert!(format!("{middleware:?}").contains("MetricsMiddleware"));
89 }
90
91 #[test]
92 fn test_metrics_middleware_default() {
93 let middleware = MetricsMiddleware;
94 assert!(format!("{middleware:?}").contains("MetricsMiddleware"));
95 }
96
97 #[test]
98 fn test_metrics_middleware_clone() {
99 let middleware = MetricsMiddleware::new();
100 let cloned = middleware;
101 assert!(format!("{cloned:?}").contains("MetricsMiddleware"));
103 }
104
105 #[test]
106 fn test_metrics_middleware_copy() {
107 let middleware = MetricsMiddleware::new();
108 let copied: MetricsMiddleware = middleware; let _original = middleware; assert!(format!("{copied:?}").contains("MetricsMiddleware"));
111 }
112
113 }