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 {
35 Self
36 }
37}
38
39#[async_trait]
40impl Middleware for MetricsMiddleware {
41 async fn handle(&self, request: Request, next: Next) -> Response {
42 if !metrics::is_enabled() {
44 return next(request).await;
45 }
46
47 let path = request.path();
49 if path.starts_with("/_ferro/") {
50 return next(request).await;
51 }
52
53 let start = Instant::now();
54 let method = request.method().to_string();
55
56 let route_pattern = request
59 .route_pattern()
60 .unwrap_or_else(|| "UNMATCHED".to_string());
61
62 let response = next(request).await;
64
65 let duration = start.elapsed();
66
67 let is_error = match &response {
69 Ok(resp) => resp.status_code() >= 400,
70 Err(resp) => resp.status_code() >= 400,
71 };
72
73 metrics::record_request(&route_pattern, &method, duration, is_error);
75
76 response
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn test_metrics_middleware_new() {
86 let middleware = MetricsMiddleware::new();
87 assert!(format!("{middleware:?}").contains("MetricsMiddleware"));
88 }
89
90 #[test]
91 fn test_metrics_middleware_default() {
92 let middleware = MetricsMiddleware;
93 assert!(format!("{middleware:?}").contains("MetricsMiddleware"));
94 }
95
96 #[test]
97 fn test_metrics_middleware_clone() {
98 let middleware = MetricsMiddleware::new();
99 let cloned = middleware;
100 assert!(format!("{cloned:?}").contains("MetricsMiddleware"));
102 }
103
104 #[test]
105 fn test_metrics_middleware_copy() {
106 let middleware = MetricsMiddleware::new();
107 let copied: MetricsMiddleware = middleware; let _original = middleware; assert!(format!("{copied:?}").contains("MetricsMiddleware"));
110 }
111
112 }