1use crate::request::Request;
50use crate::response::Response;
51
52pub trait RequestInterceptor: Send + Sync + 'static {
74 fn intercept(&self, request: Request) -> Request;
78
79 fn clone_box(&self) -> Box<dyn RequestInterceptor>;
81}
82
83impl Clone for Box<dyn RequestInterceptor> {
84 fn clone(&self) -> Self {
85 self.clone_box()
86 }
87}
88
89pub trait ResponseInterceptor: Send + Sync + 'static {
114 fn intercept(&self, response: Response) -> Response;
118
119 fn clone_box(&self) -> Box<dyn ResponseInterceptor>;
121}
122
123impl Clone for Box<dyn ResponseInterceptor> {
124 fn clone(&self) -> Self {
125 self.clone_box()
126 }
127}
128
129#[derive(Clone, Default)]
135pub struct InterceptorChain {
136 request_interceptors: Vec<Box<dyn RequestInterceptor>>,
137 response_interceptors: Vec<Box<dyn ResponseInterceptor>>,
138}
139
140impl InterceptorChain {
141 pub fn new() -> Self {
143 Self {
144 request_interceptors: Vec::new(),
145 response_interceptors: Vec::new(),
146 }
147 }
148
149 pub fn add_request_interceptor<I: RequestInterceptor>(&mut self, interceptor: I) {
153 self.request_interceptors.push(Box::new(interceptor));
154 }
155
156 pub fn add_response_interceptor<I: ResponseInterceptor>(&mut self, interceptor: I) {
160 self.response_interceptors.push(Box::new(interceptor));
161 }
162
163 pub fn request_interceptor_count(&self) -> usize {
165 self.request_interceptors.len()
166 }
167
168 pub fn response_interceptor_count(&self) -> usize {
170 self.response_interceptors.len()
171 }
172
173 pub fn is_empty(&self) -> bool {
175 self.request_interceptors.is_empty() && self.response_interceptors.is_empty()
176 }
177
178 pub fn intercept_request(&self, mut request: Request) -> Request {
183 for interceptor in &self.request_interceptors {
184 request = interceptor.intercept(request);
185 }
186 request
187 }
188
189 pub fn intercept_response(&self, mut response: Response) -> Response {
194 for interceptor in self.response_interceptors.iter().rev() {
196 response = interceptor.intercept(response);
197 }
198 response
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::path_params::PathParams;
206 use bytes::Bytes;
207 use http::{Extensions, Method, StatusCode};
208 use http_body_util::Full;
209 use proptest::prelude::*;
210 use std::sync::Arc;
211
212 fn create_test_request(method: Method, path: &str) -> Request {
214 let uri: http::Uri = path.parse().unwrap();
215 let builder = http::Request::builder().method(method).uri(uri);
216
217 let req = builder.body(()).unwrap();
218 let (parts, _) = req.into_parts();
219
220 Request::new(
221 parts,
222 crate::request::BodyVariant::Buffered(Bytes::new()),
223 Arc::new(Extensions::new()),
224 PathParams::new(),
225 )
226 }
227
228 fn create_test_response(status: StatusCode) -> Response {
230 http::Response::builder()
231 .status(status)
232 .body(Full::new(Bytes::from("test")))
233 .unwrap()
234 }
235
236 #[derive(Clone)]
238 struct TrackingRequestInterceptor {
239 id: usize,
240 order: Arc<std::sync::Mutex<Vec<usize>>>,
241 }
242
243 impl TrackingRequestInterceptor {
244 fn new(id: usize, order: Arc<std::sync::Mutex<Vec<usize>>>) -> Self {
245 Self { id, order }
246 }
247 }
248
249 impl RequestInterceptor for TrackingRequestInterceptor {
250 fn intercept(&self, request: Request) -> Request {
251 self.order.lock().unwrap().push(self.id);
252 request
253 }
254
255 fn clone_box(&self) -> Box<dyn RequestInterceptor> {
256 Box::new(self.clone())
257 }
258 }
259
260 #[derive(Clone)]
262 struct TrackingResponseInterceptor {
263 id: usize,
264 order: Arc<std::sync::Mutex<Vec<usize>>>,
265 }
266
267 impl TrackingResponseInterceptor {
268 fn new(id: usize, order: Arc<std::sync::Mutex<Vec<usize>>>) -> Self {
269 Self { id, order }
270 }
271 }
272
273 impl ResponseInterceptor for TrackingResponseInterceptor {
274 fn intercept(&self, response: Response) -> Response {
275 self.order.lock().unwrap().push(self.id);
276 response
277 }
278
279 fn clone_box(&self) -> Box<dyn ResponseInterceptor> {
280 Box::new(self.clone())
281 }
282 }
283
284 proptest! {
292 #![proptest_config(ProptestConfig::with_cases(100))]
293
294 #[test]
295 fn prop_interceptor_execution_order(num_interceptors in 1usize..10usize) {
296 let request_order = Arc::new(std::sync::Mutex::new(Vec::new()));
297 let response_order = Arc::new(std::sync::Mutex::new(Vec::new()));
298
299 let mut chain = InterceptorChain::new();
300
301 for i in 0..num_interceptors {
303 chain.add_request_interceptor(
304 TrackingRequestInterceptor::new(i, request_order.clone())
305 );
306 chain.add_response_interceptor(
307 TrackingResponseInterceptor::new(i, response_order.clone())
308 );
309 }
310
311 let request = create_test_request(Method::GET, "/test");
313 let _ = chain.intercept_request(request);
314
315 let response = create_test_response(StatusCode::OK);
317 let _ = chain.intercept_response(response);
318
319 let req_order = request_order.lock().unwrap();
321 prop_assert_eq!(req_order.len(), num_interceptors);
322 for (idx, &id) in req_order.iter().enumerate() {
323 prop_assert_eq!(id, idx, "Request interceptor order mismatch at index {}", idx);
324 }
325
326 let res_order = response_order.lock().unwrap();
328 prop_assert_eq!(res_order.len(), num_interceptors);
329 for (idx, &id) in res_order.iter().enumerate() {
330 let expected = num_interceptors - 1 - idx;
331 prop_assert_eq!(id, expected, "Response interceptor order mismatch at index {}", idx);
332 }
333 }
334 }
335
336 #[derive(Clone)]
338 struct HeaderModifyingResponseInterceptor {
339 header_name: &'static str,
340 header_value: String,
341 }
342
343 impl HeaderModifyingResponseInterceptor {
344 fn new(header_name: &'static str, header_value: impl Into<String>) -> Self {
345 Self {
346 header_name,
347 header_value: header_value.into(),
348 }
349 }
350 }
351
352 impl ResponseInterceptor for HeaderModifyingResponseInterceptor {
353 fn intercept(&self, mut response: Response) -> Response {
354 if let Ok(value) = self.header_value.parse() {
355 response.headers_mut().insert(self.header_name, value);
356 }
357 response
358 }
359
360 fn clone_box(&self) -> Box<dyn ResponseInterceptor> {
361 Box::new(self.clone())
362 }
363 }
364
365 proptest! {
372 #![proptest_config(ProptestConfig::with_cases(100))]
373
374 #[test]
375 fn prop_interceptor_modification_propagation(
376 num_interceptors in 1usize..5usize,
377 header_values in prop::collection::vec("[a-zA-Z0-9]{1,10}", 1..5usize),
378 ) {
379 let mut chain = InterceptorChain::new();
380
381 for (i, value) in header_values.iter().enumerate().take(num_interceptors) {
383 let header_name = Box::leak(format!("x-test-{}", i).into_boxed_str());
384 chain.add_response_interceptor(
385 HeaderModifyingResponseInterceptor::new(header_name, value.clone())
386 );
387 }
388
389 let response = create_test_response(StatusCode::OK);
391 let modified_response = chain.intercept_response(response);
392
393 for (i, value) in header_values.iter().enumerate().take(num_interceptors) {
395 let header_name = format!("x-test-{}", i);
396 let header_value = modified_response.headers().get(&header_name);
397 prop_assert!(header_value.is_some(), "Header {} should be present", header_name);
398 prop_assert_eq!(
399 header_value.unwrap().to_str().unwrap(),
400 value,
401 "Header {} should have value {}", header_name, value
402 );
403 }
404 }
405 }
406
407 #[test]
408 fn test_empty_chain() {
409 let chain = InterceptorChain::new();
410 assert!(chain.is_empty());
411 assert_eq!(chain.request_interceptor_count(), 0);
412 assert_eq!(chain.response_interceptor_count(), 0);
413
414 let request = create_test_request(Method::GET, "/test");
416 let _ = chain.intercept_request(request);
417
418 let response = create_test_response(StatusCode::OK);
419 let result = chain.intercept_response(response);
420 assert_eq!(result.status(), StatusCode::OK);
421 }
422
423 #[test]
424 fn test_single_request_interceptor() {
425 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
426 let mut chain = InterceptorChain::new();
427 chain.add_request_interceptor(TrackingRequestInterceptor::new(42, order.clone()));
428
429 assert!(!chain.is_empty());
430 assert_eq!(chain.request_interceptor_count(), 1);
431
432 let request = create_test_request(Method::GET, "/test");
433 let _ = chain.intercept_request(request);
434
435 let recorded = order.lock().unwrap();
436 assert_eq!(recorded.len(), 1);
437 assert_eq!(recorded[0], 42);
438 }
439
440 #[test]
441 fn test_single_response_interceptor() {
442 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
443 let mut chain = InterceptorChain::new();
444 chain.add_response_interceptor(TrackingResponseInterceptor::new(42, order.clone()));
445
446 assert!(!chain.is_empty());
447 assert_eq!(chain.response_interceptor_count(), 1);
448
449 let response = create_test_response(StatusCode::OK);
450 let _ = chain.intercept_response(response);
451
452 let recorded = order.lock().unwrap();
453 assert_eq!(recorded.len(), 1);
454 assert_eq!(recorded[0], 42);
455 }
456
457 #[test]
458 fn test_response_header_modification() {
459 let mut chain = InterceptorChain::new();
460 chain.add_response_interceptor(HeaderModifyingResponseInterceptor::new(
461 "x-custom", "value1",
462 ));
463 chain.add_response_interceptor(HeaderModifyingResponseInterceptor::new(
464 "x-another",
465 "value2",
466 ));
467
468 let response = create_test_response(StatusCode::OK);
469 let modified = chain.intercept_response(response);
470
471 assert_eq!(
473 modified
474 .headers()
475 .get("x-custom")
476 .unwrap()
477 .to_str()
478 .unwrap(),
479 "value1"
480 );
481 assert_eq!(
482 modified
483 .headers()
484 .get("x-another")
485 .unwrap()
486 .to_str()
487 .unwrap(),
488 "value2"
489 );
490 }
491
492 #[test]
493 fn test_chain_clone() {
494 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
495 let mut chain = InterceptorChain::new();
496 chain.add_request_interceptor(TrackingRequestInterceptor::new(1, order.clone()));
497 chain.add_response_interceptor(TrackingResponseInterceptor::new(2, order.clone()));
498
499 let cloned = chain.clone();
501
502 assert_eq!(cloned.request_interceptor_count(), 1);
503 assert_eq!(cloned.response_interceptor_count(), 1);
504 }
505}