Skip to main content

qubit_http/request/
http_request_interceptors.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! Request interceptor abstraction for outgoing HTTP requests.
10
11use qubit_function::{ArcMutatingFunction, MutatingFunction};
12use url::Url;
13
14use super::http_request::HttpRequest;
15use crate::HttpResult;
16
17/// Request interceptor function used to mutate an outbound [`HttpRequest`]
18/// before URL resolution, header merge, and network I/O.
19///
20/// Returning `Err` short-circuits execution for the current attempt.
21pub type HttpRequestInterceptor = ArcMutatingFunction<HttpRequest, HttpResult<()>>;
22
23/// Ordered request interceptor list with unified application behavior.
24#[derive(Debug, Clone, Default)]
25pub struct HttpRequestInterceptors {
26    interceptors: Vec<HttpRequestInterceptor>,
27}
28
29impl HttpRequestInterceptors {
30    /// Creates an empty request interceptor list.
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// Appends one request interceptor.
36    pub fn push(&mut self, interceptor: HttpRequestInterceptor) {
37        self.interceptors.push(interceptor);
38    }
39
40    /// Removes all request interceptors.
41    pub fn clear(&mut self) {
42        self.interceptors.clear();
43    }
44
45    /// Applies request interceptors in insertion order.
46    ///
47    /// # Parameters
48    /// - `request`: Request snapshot to mutate before URL resolution and send.
49    ///
50    /// # Returns
51    /// `Ok(())` when all interceptors succeed.
52    ///
53    /// # Errors
54    /// Returns the first interceptor error and enriches it with method/URL
55    /// context when missing.
56    pub fn apply(&self, request: &mut HttpRequest) -> HttpResult<()> {
57        for interceptor in &self.interceptors {
58            interceptor.apply(request).map_err(|error| {
59                let mut mapped = error;
60                if mapped.method.is_none() {
61                    mapped = mapped.with_method(request.method());
62                }
63                if mapped.url.is_none() {
64                    // Prefer the fully resolved request URL so builder query
65                    // params are visible in interceptor errors; fallback to the
66                    // raw URL to preserve behavior when resolution fails.
67                    if let Ok(resolved_url) = request.resolved_url_with_query() {
68                        mapped = mapped.with_url(&resolved_url);
69                    } else if let Ok(parsed_url) = Url::parse(request.path()) {
70                        mapped = mapped.with_url(&parsed_url);
71                    }
72                }
73                mapped
74            })?;
75        }
76        Ok(())
77    }
78}