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