Skip to main content

qubit_http/response/
http_response_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//! Response interceptor abstraction for successful HTTP responses.
11
12use qubit_function::{
13    ArcMutatingFunction,
14    MutatingFunction,
15};
16
17use super::HttpResponseMeta;
18use crate::HttpResult;
19
20/// Response interceptor function used to inspect/mutate response metadata
21/// (`status`, `headers`, `url`) before the response is returned to callers.
22///
23/// Returning `Err` short-circuits execution for the current attempt.
24pub type HttpResponseInterceptor = ArcMutatingFunction<HttpResponseMeta, HttpResult<()>>;
25
26/// Ordered response interceptor list with unified application behavior.
27#[derive(Debug, Clone, Default)]
28pub struct HttpResponseInterceptors {
29    interceptors: Vec<HttpResponseInterceptor>,
30}
31
32impl HttpResponseInterceptors {
33    /// Creates an empty response interceptor list.
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Appends one response interceptor.
39    pub fn push(&mut self, interceptor: HttpResponseInterceptor) {
40        self.interceptors.push(interceptor);
41    }
42
43    /// Removes all response interceptors.
44    pub fn clear(&mut self) {
45        self.interceptors.clear();
46    }
47
48    /// Applies response interceptors in insertion order.
49    ///
50    /// # Parameters
51    /// - `response_meta`: Response metadata.
52    ///
53    /// # Returns
54    /// `Ok(())` when all interceptors accept the response.
55    ///
56    /// # Errors
57    /// Returns the first interceptor error and enriches it with
58    /// status/method/URL context when missing.
59    pub fn apply(&self, response_meta: &mut HttpResponseMeta) -> HttpResult<()> {
60        for interceptor in &self.interceptors {
61            interceptor.apply(response_meta).map_err(|error| {
62                let mut mapped = error;
63                if mapped.status.is_none() {
64                    mapped = mapped.with_status(response_meta.status);
65                }
66                if mapped.method.is_none() {
67                    mapped = mapped.with_method(&response_meta.method);
68                }
69                if mapped.url.is_none() {
70                    mapped = mapped.with_url(&response_meta.url);
71                }
72                mapped
73            })?;
74        }
75        Ok(())
76    }
77}