mockforge_http/middleware/
deceptive_canary.rs1use axum::extract::Request;
6use axum::http::{HeaderMap, Uri};
7use axum::middleware::Next;
8use axum::response::Response;
9use mockforge_core::deceptive_canary::DeceptiveCanaryRouter;
10use std::collections::HashMap;
11use std::sync::Arc;
12use tracing::debug;
13
14#[derive(Clone)]
16pub struct DeceptiveCanaryState {
17 pub router: Arc<DeceptiveCanaryRouter>,
19}
20
21impl DeceptiveCanaryState {
22 pub fn new(router: DeceptiveCanaryRouter) -> Self {
24 Self {
25 router: Arc::new(router),
26 }
27 }
28}
29
30pub async fn deceptive_canary_middleware(req: Request, next: Next) -> Response {
35 let state = req.extensions().get::<DeceptiveCanaryState>().cloned().unwrap_or_else(|| {
37 DeceptiveCanaryState::new(DeceptiveCanaryRouter::default())
39 });
40 let user_agent = req
42 .headers()
43 .get("user-agent")
44 .and_then(|h| h.to_str().ok())
45 .map(|s| s.to_string());
46
47 let ip_address = req
49 .extensions()
50 .get::<std::net::SocketAddr>()
51 .map(|addr| addr.ip().to_string())
52 .or_else(|| {
53 req.headers()
54 .get("x-forwarded-for")
55 .or_else(|| req.headers().get("x-real-ip"))
56 .and_then(|h| h.to_str().ok())
57 .map(|s| s.split(',').next().unwrap_or(s).trim().to_string())
58 });
59
60 let mut headers_map = HashMap::new();
62 for (key, value) in req.headers() {
63 let key_str = key.as_str().to_string();
64 if let Ok(value_str) = value.to_str() {
65 headers_map.insert(key_str, value_str.to_string());
66 }
67 }
68
69 let mut query_params = HashMap::new();
71 if let Some(query) = req.uri().query() {
72 for pair in query.split('&') {
73 if let Some((key, value)) = pair.split_once('=') {
74 query_params.insert(key.to_string(), value.to_string());
75 }
76 }
77 }
78
79 let user_id = req
81 .headers()
82 .get("x-user-id")
83 .or_else(|| req.headers().get("authorization"))
84 .and_then(|h| h.to_str().ok())
85 .map(|s| s.to_string());
86
87 let should_route = state.router.should_route_to_canary(
89 user_agent.as_deref(),
90 ip_address.as_deref(),
91 &headers_map,
92 &query_params,
93 user_id.as_deref(),
94 );
95
96 if should_route {
97 debug!("Routing request to deceptive canary: {} {}", req.method(), req.uri().path());
98
99 let canary_url = &state.router.config().deceptive_deploy_url;
101
102 if !canary_url.is_empty() {
103 let mut response = next.run(req).await;
107 response.headers_mut().insert("X-Deceptive-Canary", "true".parse().unwrap());
108 return response;
109 }
110 }
111
112 next.run(req).await
114}