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