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 =
29    ArcMutatingFunction<HttpResponseInterceptorContext, HttpResult<()>>;
30
31/// Ordered response interceptor list with unified application behavior.
32#[derive(Debug, Clone, Default)]
33pub struct HttpResponseInterceptors {
34    interceptors: Vec<HttpResponseInterceptor>,
35}
36
37impl HttpResponseInterceptors {
38    /// Creates an empty response interceptor list.
39    pub fn new() -> Self {
40        Self::default()
41    }
42
43    /// Appends one response interceptor.
44    pub fn push(&mut self, interceptor: HttpResponseInterceptor) {
45        self.interceptors.push(interceptor);
46    }
47
48    /// Removes all response interceptors.
49    pub fn clear(&mut self) {
50        self.interceptors.clear();
51    }
52
53    /// Applies response interceptors in insertion order.
54    ///
55    /// # Parameters
56    /// - `response_meta`: Response metadata to expose and update.
57    ///
58    /// # Returns
59    /// `Ok(())` when all interceptors accept the response.
60    ///
61    /// # Errors
62    /// Returns the first interceptor error and enriches it with
63    /// status/method/URL context when missing.
64    pub fn apply(&self, response_meta: &mut HttpResponseMeta) -> HttpResult<()> {
65        let mut context = HttpResponseInterceptorContext::from_meta(response_meta);
66        for interceptor in &self.interceptors {
67            interceptor.apply(&mut context).map_err(|error| {
68                let mut mapped = error;
69                if mapped.status.is_none() {
70                    mapped = mapped.with_status(context.status());
71                }
72                if mapped.method.is_none() {
73                    mapped = mapped.with_method(context.method());
74                }
75                if mapped.url.is_none() {
76                    mapped = mapped.with_url(context.url());
77                }
78                mapped
79            })?;
80        }
81        context.apply_to_meta(response_meta);
82        Ok(())
83    }
84}