mockforge_http/middleware/
ab_testing.rs1use crate::handlers::ab_testing::ABTestingState;
7use axum::{body::Body, extract::Request, http::StatusCode, middleware::Next, response::Response};
8use mockforge_core::ab_testing::{apply_variant_to_response, select_variant};
9use std::time::Instant;
10use tracing::{debug, trace};
11
12pub async fn ab_testing_middleware(req: Request, next: Next) -> Response<Body> {
20 let start_time = Instant::now();
21
22 let method = req.method().to_string();
24 let path = req.uri().path().to_string();
25 let uri = req.uri().to_string();
26
27 let headers = req.headers().clone();
29
30 let state_opt = req.extensions().get::<ABTestingState>().cloned();
32
33 if let Some(state) = state_opt {
34 if let Some(test_config) = state.variant_manager.get_test(&method, &path).await {
36 trace!("A/B test found for {} {}", method, path);
37
38 match select_variant(&test_config, &headers, &uri, &state.variant_manager).await {
40 Ok(Some(variant)) => {
41 debug!("Selected variant '{}' for {} {}", variant.variant_id, method, path);
42
43 let response = next.run(req).await;
45
46 let response = apply_variant_to_response(&variant, response);
48
49 let response_time_ms = start_time.elapsed().as_millis() as f64;
51 let status_code = response.status().as_u16();
52 state
53 .variant_manager
54 .record_request(
55 &method,
56 &path,
57 &variant.variant_id,
58 status_code,
59 response_time_ms,
60 )
61 .await;
62
63 if let Some(latency_ms) = variant.latency_ms {
65 tokio::time::sleep(tokio::time::Duration::from_millis(latency_ms)).await;
66 }
67
68 return response;
69 }
70 Ok(None) => {
71 trace!("No variant selected for {} {}", method, path);
72 }
73 Err(e) => {
74 debug!("Error selecting variant for {} {}: {}", method, path, e);
75 }
76 }
77 }
78 }
79
80 next.run(req).await
82}