1#![allow(clippy::result_large_err)]
10mod client;
30pub mod constants;
31mod error;
32mod options;
33mod request;
34mod response;
35pub mod sse;
36
37#[cfg(coverage)]
38#[doc(hidden)]
39pub mod coverage_support {
40 use std::time::{Duration, Instant};
43
44 use http::{HeaderMap, HeaderValue, Method, StatusCode};
45 use qubit_retry::{AttemptFailure, RetryContext, RetryError, RetryErrorReason};
46 use url::Url;
47
48 use crate::error::{backend_error_mapper, ReqwestErrorPhase};
49 use crate::{HttpError, HttpErrorKind, HttpResponse};
50
51 pub fn map_reqwest_error_kind(
61 error: reqwest::Error,
62 phase: Option<&str>,
63 default_kind: HttpErrorKind,
64 ) -> HttpErrorKind {
65 let phase = match phase {
66 Some("send") => Some(ReqwestErrorPhase::Send),
67 Some("read") => Some(ReqwestErrorPhase::Read),
68 _ => None,
69 };
70 backend_error_mapper::map_reqwest_error(error, default_kind, phase, None, None).kind
71 }
72
73 pub fn classify_timeout_kinds() -> Vec<HttpErrorKind> {
78 vec![
79 backend_error_mapper::coverage_classify_timeout_kind(
80 true,
81 Some(ReqwestErrorPhase::Send),
82 ),
83 backend_error_mapper::coverage_classify_timeout_kind(
84 false,
85 Some(ReqwestErrorPhase::Send),
86 ),
87 backend_error_mapper::coverage_classify_timeout_kind(
88 false,
89 Some(ReqwestErrorPhase::Read),
90 ),
91 backend_error_mapper::coverage_classify_timeout_kind(false, None),
92 ]
93 }
94
95 pub fn classify_backend_error_kinds() -> Vec<HttpErrorKind> {
100 vec![
101 backend_error_mapper::coverage_classify_reqwest_error_kind(
102 false,
103 true,
104 false,
105 false,
106 false,
107 None,
108 HttpErrorKind::Transport,
109 ),
110 backend_error_mapper::coverage_classify_reqwest_error_kind(
111 false,
112 false,
113 true,
114 false,
115 false,
116 None,
117 HttpErrorKind::Transport,
118 ),
119 backend_error_mapper::coverage_classify_reqwest_error_kind(
120 false,
121 false,
122 false,
123 true,
124 false,
125 None,
126 HttpErrorKind::Transport,
127 ),
128 backend_error_mapper::coverage_classify_reqwest_error_kind(
129 false,
130 false,
131 false,
132 false,
133 false,
134 None,
135 HttpErrorKind::Transport,
136 ),
137 ]
138 }
139
140 pub fn exercise_http_retry_mapping_paths() -> Vec<String> {
145 let started_at = Instant::now();
146 let context = RetryContext::new(2, 2);
147 let attempts_exceeded = RetryError::<HttpError>::coverage_new(
148 RetryErrorReason::AttemptsExceeded,
149 None,
150 context,
151 );
152 let aborted =
153 RetryError::<HttpError>::coverage_new(RetryErrorReason::Aborted, None, context);
154 let unsupported = RetryError::<HttpError>::coverage_new(
155 RetryErrorReason::UnsupportedOperation,
156 None,
157 context,
158 );
159 let max_elapsed = RetryError::<HttpError>::coverage_new(
160 RetryErrorReason::MaxTotalElapsedExceeded,
161 None,
162 context,
163 );
164 let max_elapsed_unbounded = RetryError::<HttpError>::coverage_new(
165 RetryErrorReason::MaxTotalElapsedExceeded,
166 None,
167 context,
168 );
169
170 vec![
171 crate::HttpClient::coverage_map_retry_error(
172 attempts_exceeded,
173 started_at,
174 Some(Duration::from_millis(1)),
175 2,
176 )
177 .message,
178 crate::HttpClient::coverage_map_retry_error(
179 aborted,
180 started_at,
181 Some(Duration::from_millis(1)),
182 2,
183 )
184 .message,
185 crate::HttpClient::coverage_map_retry_error(
186 unsupported,
187 started_at,
188 Some(Duration::from_millis(1)),
189 2,
190 )
191 .message,
192 crate::HttpClient::coverage_map_retry_error(
193 max_elapsed,
194 started_at,
195 Some(Duration::from_millis(1)),
196 2,
197 )
198 .message,
199 crate::HttpClient::coverage_map_retry_error(max_elapsed_unbounded, started_at, None, 2)
200 .message,
201 ]
202 }
203
204 pub fn exercise_retry_failure_decision_path() -> String {
209 let retry_options = crate::HttpRetryOptions::default()
210 .to_executor_options()
211 .expect("default retry options should build");
212 let decision = crate::HttpClient::coverage_retry_failure_decision(
213 &AttemptFailure::Timeout,
214 &RetryContext::new(1, 2),
215 &crate::HttpRetryOptions::default(),
216 &retry_options,
217 );
218 format!("{decision:?}")
219 }
220
221 pub fn validate_non_utf8_sse_content_type() -> (HttpErrorKind, String) {
226 let mut headers = HeaderMap::new();
227 headers.insert(
228 http::header::CONTENT_TYPE,
229 HeaderValue::from_bytes(b"\xFF").expect("opaque header bytes should be accepted"),
230 );
231 let response = HttpResponse::new(
232 StatusCode::OK,
233 headers,
234 bytes::Bytes::new(),
235 Url::parse("https://example.com/sse").expect("coverage URL should parse"),
236 Method::GET,
237 );
238 let error = crate::sse::coverage_validate_sse_response_content_type(&response)
239 .expect_err("non-UTF8 content type should be rejected");
240 (error.kind, error.message)
241 }
242
243 pub async fn exercise_response_preview_paths() -> Vec<String> {
248 crate::response::coverage_exercise_response_preview_paths().await
249 }
250
251 pub async fn exercise_request_cache_paths() -> Vec<String> {
256 crate::request::coverage_exercise_request_cache_paths().await
257 }
258
259 pub async fn exercise_threshold_paths() -> Vec<String> {
264 let mut diagnostics = crate::client::coverage_exercise_factory_paths();
265 diagnostics.push(crate::client::coverage_exercise_request_log_url_fallback());
266 diagnostics.push(format!(
267 "{:?}",
268 crate::HttpClient::coverage_prepare_cancelled_error().await
269 ));
270 diagnostics.extend(crate::options::coverage_exercise_http_client_option_paths());
271 diagnostics.extend(crate::options::coverage_exercise_config_error_paths());
272 diagnostics.push(crate::options::coverage_exercise_retry_option_paths());
273 diagnostics
274 }
275}
276
277pub use client::http_logger::HttpLogger;
278pub use client::HttpClient;
279pub use client::HttpClientFactory;
280pub use constants::DEFAULT_SENSITIVE_HEADER_NAMES;
281pub use error::{HttpError, HttpErrorKind, HttpResult, RetryHint};
282pub use options::{
283 HttpClientOptions, HttpConfigError, HttpConfigErrorKind, HttpLoggingOptions,
284 HttpRetryMethodPolicy, HttpRetryOptions, HttpTimeoutOptions, ProxyOptions, ProxyType,
285 SensitiveHttpHeaders,
286};
287pub use qubit_retry::{RetryDelay, RetryJitter, RetryOptions};
288pub use request::{
289 AsyncHttpHeaderInjector, HttpHeaderInjector, HttpRequest, HttpRequestBody,
290 HttpRequestBodyByteStream, HttpRequestBuilder, HttpRequestInterceptor, HttpRequestInterceptors,
291 HttpRequestRetryOverride, HttpRequestStreamingBody,
292};
293pub use response::{
294 HttpByteStream, HttpResponse, HttpResponseInterceptor, HttpResponseInterceptors,
295 HttpResponseMeta,
296};
297pub use tokio_util::sync::CancellationToken;