1pub trait RequestMutator: Send + Sync + 'static {
30 fn mutate(&self, proxy_url: &str, req: reqwest::Request) -> reqwest::Request;
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42 use std::sync::Arc;
43
44 struct NoOpMutator;
45
46 impl RequestMutator for NoOpMutator {
47 fn mutate(&self, _proxy_url: &str, req: reqwest::Request) -> reqwest::Request {
48 req
49 }
50 }
51
52 struct HeaderInjectMutator {
53 key: &'static str,
54 value: &'static str,
55 }
56
57 impl RequestMutator for HeaderInjectMutator {
58 fn mutate(&self, _proxy_url: &str, mut req: reqwest::Request) -> reqwest::Request {
59 if let (Ok(name), Ok(val)) = (
60 reqwest::header::HeaderName::from_bytes(self.key.as_bytes()),
61 self.value.parse::<reqwest::header::HeaderValue>(),
62 ) {
63 req.headers_mut().insert(name, val);
64 }
65 req
66 }
67 }
68
69 fn build_request() -> reqwest::Request {
70 reqwest::Client::new()
71 .get("http://example.com/")
72 .build()
73 .unwrap()
74 }
75
76 #[test]
77 fn no_op_mutator_leaves_request_unchanged() {
78 let m = NoOpMutator;
79 let req = build_request();
80 let mutated = m.mutate("socks5h://1.2.3.4:1080", req);
81 assert_eq!(mutated.url().as_str(), "http://example.com/");
82 }
83
84 #[test]
85 fn header_inject_mutator_adds_header() {
86 let m = HeaderInjectMutator {
87 key: "x-proxy-node",
88 value: "node-42",
89 };
90 let req = build_request();
91 let mutated = m.mutate("socks5h://1.2.3.4:1080", req);
92 assert_eq!(
93 mutated
94 .headers()
95 .get("x-proxy-node")
96 .and_then(|v| v.to_str().ok()),
97 Some("node-42"),
98 );
99 }
100
101 #[test]
102 fn mutator_can_be_used_as_trait_object() {
103 let m: Arc<dyn RequestMutator> = Arc::new(NoOpMutator);
104 let req = build_request();
105 let _mutated = m.mutate("socks5h://1.2.3.4:1080", req);
106 }
107
108 #[test]
109 fn different_proxy_urls_reach_mutator() {
110 struct UrlCapture(std::sync::Mutex<String>);
111 impl RequestMutator for UrlCapture {
112 fn mutate(&self, proxy_url: &str, req: reqwest::Request) -> reqwest::Request {
113 *self.0.lock().unwrap() = proxy_url.to_string();
114 req
115 }
116 }
117
118 let m = UrlCapture(std::sync::Mutex::new(String::new()));
119 let req = build_request();
120 m.mutate("socks5h://9.9.9.9:1080", req);
121 assert_eq!(*m.0.lock().unwrap(), "socks5h://9.9.9.9:1080");
122 }
123}