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 HeaderModifyingRequestInterceptor {
339 header_name: &'static str,
340 header_value: String,
341 }
342
343 impl HeaderModifyingRequestInterceptor {
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 RequestInterceptor for HeaderModifyingRequestInterceptor {
353 fn intercept(&self, mut request: Request) -> Request {
354 request
357 .extensions_mut()
358 .insert(format!("{}:{}", self.header_name, self.header_value));
359 request
360 }
361
362 fn clone_box(&self) -> Box<dyn RequestInterceptor> {
363 Box::new(self.clone())
364 }
365 }
366
367 #[derive(Clone)]
369 struct HeaderModifyingResponseInterceptor {
370 header_name: &'static str,
371 header_value: String,
372 }
373
374 impl HeaderModifyingResponseInterceptor {
375 fn new(header_name: &'static str, header_value: impl Into<String>) -> Self {
376 Self {
377 header_name,
378 header_value: header_value.into(),
379 }
380 }
381 }
382
383 impl ResponseInterceptor for HeaderModifyingResponseInterceptor {
384 fn intercept(&self, mut response: Response) -> Response {
385 if let Ok(value) = self.header_value.parse() {
386 response.headers_mut().insert(self.header_name, value);
387 }
388 response
389 }
390
391 fn clone_box(&self) -> Box<dyn ResponseInterceptor> {
392 Box::new(self.clone())
393 }
394 }
395
396 proptest! {
403 #![proptest_config(ProptestConfig::with_cases(100))]
404
405 #[test]
406 fn prop_interceptor_modification_propagation(
407 num_interceptors in 1usize..5usize,
408 header_values in prop::collection::vec("[a-zA-Z0-9]{1,10}", 1..5usize),
409 ) {
410 let mut chain = InterceptorChain::new();
411
412 for (i, value) in header_values.iter().enumerate().take(num_interceptors) {
414 let header_name = Box::leak(format!("x-test-{}", i).into_boxed_str());
415 chain.add_response_interceptor(
416 HeaderModifyingResponseInterceptor::new(header_name, value.clone())
417 );
418 }
419
420 let response = create_test_response(StatusCode::OK);
422 let modified_response = chain.intercept_response(response);
423
424 for (i, value) in header_values.iter().enumerate().take(num_interceptors) {
426 let header_name = format!("x-test-{}", i);
427 let header_value = modified_response.headers().get(&header_name);
428 prop_assert!(header_value.is_some(), "Header {} should be present", header_name);
429 prop_assert_eq!(
430 header_value.unwrap().to_str().unwrap(),
431 value,
432 "Header {} should have value {}", header_name, value
433 );
434 }
435 }
436 }
437
438 #[test]
439 fn test_empty_chain() {
440 let chain = InterceptorChain::new();
441 assert!(chain.is_empty());
442 assert_eq!(chain.request_interceptor_count(), 0);
443 assert_eq!(chain.response_interceptor_count(), 0);
444
445 let request = create_test_request(Method::GET, "/test");
447 let _ = chain.intercept_request(request);
448
449 let response = create_test_response(StatusCode::OK);
450 let result = chain.intercept_response(response);
451 assert_eq!(result.status(), StatusCode::OK);
452 }
453
454 #[test]
455 fn test_single_request_interceptor() {
456 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
457 let mut chain = InterceptorChain::new();
458 chain.add_request_interceptor(TrackingRequestInterceptor::new(42, order.clone()));
459
460 assert!(!chain.is_empty());
461 assert_eq!(chain.request_interceptor_count(), 1);
462
463 let request = create_test_request(Method::GET, "/test");
464 let _ = chain.intercept_request(request);
465
466 let recorded = order.lock().unwrap();
467 assert_eq!(recorded.len(), 1);
468 assert_eq!(recorded[0], 42);
469 }
470
471 #[test]
472 fn test_single_response_interceptor() {
473 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
474 let mut chain = InterceptorChain::new();
475 chain.add_response_interceptor(TrackingResponseInterceptor::new(42, order.clone()));
476
477 assert!(!chain.is_empty());
478 assert_eq!(chain.response_interceptor_count(), 1);
479
480 let response = create_test_response(StatusCode::OK);
481 let _ = chain.intercept_response(response);
482
483 let recorded = order.lock().unwrap();
484 assert_eq!(recorded.len(), 1);
485 assert_eq!(recorded[0], 42);
486 }
487
488 #[test]
489 fn test_response_header_modification() {
490 let mut chain = InterceptorChain::new();
491 chain.add_response_interceptor(HeaderModifyingResponseInterceptor::new(
492 "x-custom", "value1",
493 ));
494 chain.add_response_interceptor(HeaderModifyingResponseInterceptor::new(
495 "x-another",
496 "value2",
497 ));
498
499 let response = create_test_response(StatusCode::OK);
500 let modified = chain.intercept_response(response);
501
502 assert_eq!(
504 modified
505 .headers()
506 .get("x-custom")
507 .unwrap()
508 .to_str()
509 .unwrap(),
510 "value1"
511 );
512 assert_eq!(
513 modified
514 .headers()
515 .get("x-another")
516 .unwrap()
517 .to_str()
518 .unwrap(),
519 "value2"
520 );
521 }
522
523 #[test]
524 fn test_chain_clone() {
525 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
526 let mut chain = InterceptorChain::new();
527 chain.add_request_interceptor(TrackingRequestInterceptor::new(1, order.clone()));
528 chain.add_response_interceptor(TrackingResponseInterceptor::new(2, order.clone()));
529
530 let cloned = chain.clone();
532
533 assert_eq!(cloned.request_interceptor_count(), 1);
534 assert_eq!(cloned.response_interceptor_count(), 1);
535 }
536}