Skip to main content

qubit_http/request/
http_request_interceptors.rs

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