avx_http/
interceptors.rs

1//! Request and response interceptors
2//!
3//! Interceptors allow you to modify requests before they are sent
4//! and inspect or modify responses after they are received.
5
6use crate::error::Result;
7use bytes::Bytes;
8use http::{HeaderMap, Method};
9use std::sync::Arc;
10
11/// Type alias for request interceptor function
12pub type RequestInterceptor = Arc<dyn Fn(&mut RequestData) + Send + Sync>;
13
14/// Type alias for response interceptor function
15pub type ResponseInterceptor = Arc<dyn Fn(&ResponseData) + Send + Sync>;
16
17/// Data available to request interceptors
18#[derive(Debug)]
19pub struct RequestData {
20    /// HTTP method
21    pub method: Method,
22    /// Request URL
23    pub url: String,
24    /// Request headers
25    pub headers: HeaderMap,
26    /// Request body
27    pub body: Option<Bytes>,
28}
29
30impl RequestData {
31    /// Create new request data
32    pub fn new(method: Method, url: String, headers: HeaderMap, body: Option<Bytes>) -> Self {
33        Self {
34            method,
35            url,
36            headers,
37            body,
38        }
39    }
40
41    /// Add or update a header
42    pub fn set_header(&mut self, name: &str, value: &str) -> Result<()> {
43        use http::header::{HeaderName, HeaderValue};
44        let name = HeaderName::from_bytes(name.as_bytes())
45            .map_err(|_| crate::error::Error::InvalidHeader {
46                name: name.to_string(),
47                value: value.to_string(),
48            })?;
49        let value = HeaderValue::from_str(value)
50            .map_err(|_| crate::error::Error::InvalidHeader {
51                name: name.to_string(),
52                value: value.to_string(),
53            })?;
54        self.headers.insert(name, value);
55        Ok(())
56    }
57
58    /// Remove a header
59    pub fn remove_header(&mut self, name: &str) {
60        if let Ok(name) = http::HeaderName::from_bytes(name.as_bytes()) {
61            self.headers.remove(&name);
62        }
63    }
64
65    /// Get header value
66    pub fn get_header(&self, name: &str) -> Option<&str> {
67        let name = http::HeaderName::from_bytes(name.as_bytes()).ok()?;
68        self.headers.get(&name)?.to_str().ok()
69    }
70}
71
72/// Data available to response interceptors
73#[derive(Debug)]
74pub struct ResponseData {
75    /// HTTP status code
76    pub status: u16,
77    /// Response headers
78    pub headers: HeaderMap,
79    /// Response body size in bytes
80    pub body_size: usize,
81    /// Request duration in milliseconds
82    pub duration_ms: u64,
83}
84
85impl ResponseData {
86    /// Create new response data
87    pub fn new(status: u16, headers: HeaderMap, body_size: usize, duration_ms: u64) -> Self {
88        Self {
89            status,
90            headers,
91            body_size,
92            duration_ms,
93        }
94    }
95
96    /// Get header value
97    pub fn get_header(&self, name: &str) -> Option<&str> {
98        let name = http::HeaderName::from_bytes(name.as_bytes()).ok()?;
99        self.headers.get(&name)?.to_str().ok()
100    }
101
102    /// Check if response is successful (2xx)
103    pub fn is_success(&self) -> bool {
104        self.status >= 200 && self.status < 300
105    }
106
107    /// Check if response is redirect (3xx)
108    pub fn is_redirect(&self) -> bool {
109        self.status >= 300 && self.status < 400
110    }
111
112    /// Check if response is client error (4xx)
113    pub fn is_client_error(&self) -> bool {
114        self.status >= 400 && self.status < 500
115    }
116
117    /// Check if response is server error (5xx)
118    pub fn is_server_error(&self) -> bool {
119        self.status >= 500 && self.status < 600
120    }
121}
122
123/// Collection of interceptors
124#[derive(Clone, Default)]
125pub struct Interceptors {
126    /// Request interceptors
127    pub(crate) request: Vec<RequestInterceptor>,
128    /// Response interceptors
129    pub(crate) response: Vec<ResponseInterceptor>,
130}
131
132impl Interceptors {
133    /// Create new empty interceptor collection
134    pub fn new() -> Self {
135        Self {
136            request: Vec::new(),
137            response: Vec::new(),
138        }
139    }
140
141    /// Add a request interceptor
142    pub fn add_request<F>(&mut self, interceptor: F)
143    where
144        F: Fn(&mut RequestData) + Send + Sync + 'static,
145    {
146        self.request.push(Arc::new(interceptor));
147    }
148
149    /// Add a response interceptor
150    pub fn add_response<F>(&mut self, interceptor: F)
151    where
152        F: Fn(&ResponseData) + Send + Sync + 'static,
153    {
154        self.response.push(Arc::new(interceptor));
155    }
156
157    /// Apply all request interceptors
158    pub(crate) fn apply_request(&self, data: &mut RequestData) {
159        for interceptor in &self.request {
160            interceptor(data);
161        }
162    }
163
164    /// Apply all response interceptors
165    pub(crate) fn apply_response(&self, data: &ResponseData) {
166        for interceptor in &self.response {
167            interceptor(data);
168        }
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_request_data_set_header() {
178        let mut data = RequestData::new(
179            Method::GET,
180            "https://example.com".to_string(),
181            HeaderMap::new(),
182            None,
183        );
184
185        data.set_header("User-Agent", "avx-http/0.2.0").unwrap();
186        assert_eq!(data.get_header("User-Agent"), Some("avx-http/0.2.0"));
187    }
188
189    #[test]
190    fn test_request_data_remove_header() {
191        let mut headers = HeaderMap::new();
192        headers.insert(
193            http::header::USER_AGENT,
194            http::HeaderValue::from_static("test"),
195        );
196
197        let mut data = RequestData::new(
198            Method::GET,
199            "https://example.com".to_string(),
200            headers,
201            None,
202        );
203
204        assert!(data.get_header("User-Agent").is_some());
205        data.remove_header("User-Agent");
206        assert!(data.get_header("User-Agent").is_none());
207    }
208
209    #[test]
210    fn test_response_data_status_checks() {
211        let data = ResponseData::new(200, HeaderMap::new(), 0, 0);
212        assert!(data.is_success());
213        assert!(!data.is_client_error());
214
215        let data = ResponseData::new(404, HeaderMap::new(), 0, 0);
216        assert!(data.is_client_error());
217        assert!(!data.is_success());
218
219        let data = ResponseData::new(500, HeaderMap::new(), 0, 0);
220        assert!(data.is_server_error());
221        assert!(!data.is_success());
222    }
223
224    #[test]
225    fn test_interceptors_add() {
226        let mut interceptors = Interceptors::new();
227
228        interceptors.add_request(|data| {
229            let _ = data.set_header("X-Test", "value");
230        });
231
232        interceptors.add_response(|data| {
233            println!("Response status: {}", data.status);
234        });
235
236        assert_eq!(interceptors.request.len(), 1);
237        assert_eq!(interceptors.response.len(), 1);
238    }
239
240    #[test]
241    fn test_interceptors_apply_request() {
242        let mut interceptors = Interceptors::new();
243
244        interceptors.add_request(|data| {
245            let _ = data.set_header("X-Custom", "test");
246        });
247
248        let mut data = RequestData::new(
249            Method::GET,
250            "https://example.com".to_string(),
251            HeaderMap::new(),
252            None,
253        );
254
255        interceptors.apply_request(&mut data);
256        assert_eq!(data.get_header("X-Custom"), Some("test"));
257    }
258}