mockforge_http/
contract_diff_middleware.rs1use axum::http::HeaderMap;
9use axum::{body::Body, extract::Request, middleware::Next, response::Response};
10use mockforge_core::{
11 ai_contract_diff::CapturedRequest, request_capture::get_global_capture_manager,
12};
13use serde_json::Value;
14use std::collections::HashMap;
15use tracing::debug;
16
17pub async fn capture_for_contract_diff(req: Request<Body>, next: Next) -> Response {
19 let method = req.method().to_string();
20 let uri = req.uri().clone();
21 let path = uri.path().to_string();
22 let query = uri.query();
23
24 let headers = extract_headers_for_capture(req.headers());
26
27 let user_agent = req
29 .headers()
30 .get("user-agent")
31 .and_then(|h| h.to_str().ok())
32 .map(|s| s.to_string());
33
34 let query_params = if let Some(query) = query {
36 parse_query_params(query)
37 } else {
38 HashMap::new()
39 };
40
41 let response = next.run(req).await;
47
48 let status_code = response.status().as_u16();
50
51 let captured = CapturedRequest::new(&method, &path, "proxy_middleware")
53 .with_headers(headers)
54 .with_query_params(query_params)
55 .with_response(status_code, None); if let Some(ua) = user_agent {
58 let mut metadata = HashMap::new();
61 metadata.insert("user_agent".to_string(), Value::String(ua));
62 }
64
65 if let Some(capture_manager) = get_global_capture_manager() {
67 if let Err(e) = capture_manager.capture(captured).await {
68 debug!("Failed to capture request for contract diff: {}", e);
69 }
70 }
71
72 response
73}
74
75fn extract_headers_for_capture(headers: &HeaderMap) -> HashMap<String, String> {
77 let mut captured_headers = HashMap::new();
78
79 let safe_headers = [
81 "accept",
82 "accept-encoding",
83 "accept-language",
84 "content-type",
85 "content-length",
86 "user-agent",
87 "referer",
88 "origin",
89 "x-requested-with",
90 ];
91
92 for header_name in safe_headers {
93 if let Some(value) = headers.get(header_name) {
94 if let Ok(value_str) = value.to_str() {
95 captured_headers.insert(header_name.to_string(), value_str.to_string());
96 }
97 }
98 }
99
100 captured_headers
101}
102
103fn parse_query_params(query: &str) -> HashMap<String, String> {
105 let mut params = HashMap::new();
106
107 for pair in query.split('&') {
108 if let Some((key, value)) = pair.split_once('=') {
109 let decoded_key = urlencoding::decode(key).unwrap_or_else(|_| key.into());
110 let decoded_value = urlencoding::decode(value).unwrap_or_else(|_| value.into());
111 params.insert(decoded_key.to_string(), decoded_value.to_string());
112 }
113 }
114
115 params
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use axum::http::HeaderValue;
122
123 #[test]
124 fn test_extract_headers_for_capture() {
125 let mut headers = HeaderMap::new();
126 headers.insert("content-type", HeaderValue::from_static("application/json"));
127 headers.insert("authorization", HeaderValue::from_static("Bearer token"));
128 headers.insert("accept", HeaderValue::from_static("application/json"));
129
130 let captured = extract_headers_for_capture(&headers);
131
132 assert_eq!(captured.get("content-type"), Some(&"application/json".to_string()));
133 assert_eq!(captured.get("accept"), Some(&"application/json".to_string()));
134 assert!(!captured.contains_key("authorization")); }
136
137 #[test]
138 fn test_parse_query_params() {
139 let query = "name=John&age=30&city=New%20York";
140 let params = parse_query_params(query);
141
142 assert_eq!(params.get("name"), Some(&"John".to_string()));
143 assert_eq!(params.get("age"), Some(&"30".to_string()));
144 assert_eq!(params.get("city"), Some(&"New York".to_string()));
145 }
146
147 #[test]
148 fn test_parse_query_params_empty() {
149 let params = parse_query_params("");
150 assert!(params.is_empty());
151 }
152}