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}