Skip to main content

qubit_http/response/
http_response_interceptor_context.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 context with controlled metadata mutation.
11
12use std::time::Duration;
13
14use http::{
15    HeaderMap,
16    Method,
17    StatusCode,
18};
19use url::Url;
20
21use super::HttpResponseMeta;
22
23/// Metadata view passed to response interceptors.
24///
25/// The HTTP status and originating method are immutable so interceptors cannot
26/// invalidate the `HttpClient::execute` success-status contract after the
27/// client has already accepted the response. Interceptors may still mutate
28/// response headers and the final response URL.
29#[derive(Debug, Clone)]
30pub struct HttpResponseInterceptorContext {
31    /// Response status code captured before interceptor execution.
32    status: StatusCode,
33    /// Mutable response headers visible to later interceptors and callers.
34    headers: HeaderMap,
35    /// Mutable final response URL visible to later interceptors and callers.
36    url: Url,
37    /// Originating request method captured before interceptor execution.
38    method: Method,
39}
40
41impl HttpResponseInterceptorContext {
42    /// Creates a response interceptor context from explicit metadata parts.
43    ///
44    /// # Parameters
45    /// - `status`: Response status code. It is immutable after construction.
46    /// - `headers`: Response headers that interceptors may mutate.
47    /// - `url`: Final response URL that interceptors may replace.
48    /// - `method`: Originating request method. It is immutable after construction.
49    ///
50    /// # Returns
51    /// New interceptor context.
52    pub fn new(status: StatusCode, headers: HeaderMap, url: Url, method: Method) -> Self {
53        Self {
54            status,
55            headers,
56            url,
57            method,
58        }
59    }
60
61    /// Copies response metadata into a mutable interceptor context.
62    ///
63    /// # Parameters
64    /// - `meta`: Source response metadata.
65    ///
66    /// # Returns
67    /// New context with cloned headers, URL, and method.
68    pub fn from_meta(meta: &HttpResponseMeta) -> Self {
69        Self::new(
70            meta.status(),
71            meta.headers().clone(),
72            meta.url().clone(),
73            meta.method().clone(),
74        )
75    }
76
77    /// Returns response status code.
78    ///
79    /// # Returns
80    /// Immutable status accepted by `HttpClient::execute`.
81    #[inline]
82    pub fn status(&self) -> StatusCode {
83        self.status
84    }
85
86    /// Returns response headers.
87    ///
88    /// # Returns
89    /// Immutable header map view.
90    #[inline]
91    pub fn headers(&self) -> &HeaderMap {
92        &self.headers
93    }
94
95    /// Returns mutable response headers.
96    ///
97    /// # Returns
98    /// Mutable header map applied back to [`HttpResponseMeta`] after all
99    /// response interceptors succeed.
100    #[inline]
101    pub fn headers_mut(&mut self) -> &mut HeaderMap {
102        &mut self.headers
103    }
104
105    /// Returns final response URL.
106    ///
107    /// # Returns
108    /// Immutable response URL view.
109    #[inline]
110    pub fn url(&self) -> &Url {
111        &self.url
112    }
113
114    /// Replaces final response URL.
115    ///
116    /// # Parameters
117    /// - `url`: New final response URL.
118    ///
119    /// # Returns
120    /// `self` for method chaining.
121    #[inline]
122    pub fn set_url(&mut self, url: Url) -> &mut Self {
123        self.url = url;
124        self
125    }
126
127    /// Returns originating request method.
128    ///
129    /// # Returns
130    /// Immutable request method.
131    #[inline]
132    pub fn method(&self) -> &Method {
133        &self.method
134    }
135
136    /// Returns parsed `Retry-After` when status and headers provide one.
137    ///
138    /// # Returns
139    /// `Some(Duration)` for retryable status codes with valid `Retry-After`;
140    /// otherwise `None`.
141    #[inline]
142    pub fn retry_after_hint(&self) -> Option<Duration> {
143        HttpResponseMeta::retry_after_hint_from_parts(self.status, &self.headers)
144    }
145
146    /// Applies mutable context fields back into response metadata.
147    ///
148    /// # Parameters
149    /// - `meta`: Response metadata to update.
150    ///
151    /// # Returns
152    /// Nothing. Status and method are intentionally not copied back.
153    pub(super) fn apply_to_meta(self, meta: &mut HttpResponseMeta) {
154        meta.set_headers(self.headers);
155        meta.set_url(self.url);
156    }
157}