1#![allow(clippy::result_large_err)]
11mod client;
28pub mod constants;
29mod error;
30mod options;
31mod request;
32mod response;
33pub mod sse;
34
35#[cfg(coverage)]
36#[doc(hidden)]
37pub mod coverage_support {
38 use std::time::{Duration, Instant};
41
42 use http::{HeaderMap, HeaderValue, Method, StatusCode};
43 use qubit_retry::{AttemptFailure, RetryContext, RetryError, RetryErrorReason};
44 use url::Url;
45
46 use crate::error::backend_error_mapper;
47 use crate::error::ReqwestErrorPhase;
48 use crate::{HttpError, HttpErrorKind, HttpResponse};
49
50 pub fn classify_timeout_kinds() -> Vec<HttpErrorKind> {
55 vec![
56 backend_error_mapper::coverage_classify_timeout_kind(
57 true,
58 Some(ReqwestErrorPhase::Send),
59 ),
60 backend_error_mapper::coverage_classify_timeout_kind(
61 false,
62 Some(ReqwestErrorPhase::Send),
63 ),
64 backend_error_mapper::coverage_classify_timeout_kind(
65 false,
66 Some(ReqwestErrorPhase::Read),
67 ),
68 backend_error_mapper::coverage_classify_timeout_kind(false, None),
69 ]
70 }
71
72 pub fn classify_backend_error_kinds() -> Vec<HttpErrorKind> {
77 vec![
78 backend_error_mapper::coverage_classify_reqwest_error_kind(
79 false,
80 true,
81 false,
82 false,
83 false,
84 None,
85 HttpErrorKind::Transport,
86 ),
87 backend_error_mapper::coverage_classify_reqwest_error_kind(
88 false,
89 false,
90 true,
91 false,
92 false,
93 None,
94 HttpErrorKind::Transport,
95 ),
96 backend_error_mapper::coverage_classify_reqwest_error_kind(
97 false,
98 false,
99 false,
100 true,
101 false,
102 None,
103 HttpErrorKind::Transport,
104 ),
105 backend_error_mapper::coverage_classify_reqwest_error_kind(
106 false,
107 false,
108 false,
109 false,
110 false,
111 None,
112 HttpErrorKind::Transport,
113 ),
114 ]
115 }
116
117 pub fn exercise_http_retry_mapping_paths() -> Vec<String> {
122 let started_at = Instant::now();
123 let context = RetryContext::new(2, 2);
124 let attempts_exceeded = RetryError::<HttpError>::coverage_new(
125 RetryErrorReason::AttemptsExceeded,
126 None,
127 context,
128 );
129 let aborted =
130 RetryError::<HttpError>::coverage_new(RetryErrorReason::Aborted, None, context);
131 let unsupported = RetryError::<HttpError>::coverage_new(
132 RetryErrorReason::UnsupportedOperation,
133 None,
134 context,
135 );
136 let max_elapsed = RetryError::<HttpError>::coverage_new(
137 RetryErrorReason::MaxTotalElapsedExceeded,
138 None,
139 context,
140 );
141 let max_elapsed_unbounded = RetryError::<HttpError>::coverage_new(
142 RetryErrorReason::MaxTotalElapsedExceeded,
143 None,
144 context,
145 );
146
147 vec![
148 crate::HttpClient::coverage_map_retry_error(
149 attempts_exceeded,
150 started_at,
151 Some(Duration::from_millis(1)),
152 2,
153 )
154 .message,
155 crate::HttpClient::coverage_map_retry_error(
156 aborted,
157 started_at,
158 Some(Duration::from_millis(1)),
159 2,
160 )
161 .message,
162 crate::HttpClient::coverage_map_retry_error(
163 unsupported,
164 started_at,
165 Some(Duration::from_millis(1)),
166 2,
167 )
168 .message,
169 crate::HttpClient::coverage_map_retry_error(
170 max_elapsed,
171 started_at,
172 Some(Duration::from_millis(1)),
173 2,
174 )
175 .message,
176 crate::HttpClient::coverage_map_retry_error(max_elapsed_unbounded, started_at, None, 2)
177 .message,
178 ]
179 }
180
181 pub fn exercise_retry_failure_decision_path() -> String {
186 let retry_options = crate::HttpRetryOptions::default()
187 .to_executor_options()
188 .expect("default retry options should build");
189 let decision = crate::HttpClient::coverage_retry_failure_decision(
190 &AttemptFailure::Timeout,
191 &RetryContext::new(1, 2),
192 &crate::HttpRetryOptions::default(),
193 &retry_options,
194 );
195 format!("{decision:?}")
196 }
197
198 pub fn validate_non_utf8_sse_content_type() -> (HttpErrorKind, String) {
203 let mut headers = HeaderMap::new();
204 headers.insert(
205 http::header::CONTENT_TYPE,
206 HeaderValue::from_bytes(b"\xFF").expect("opaque header bytes should be accepted"),
207 );
208 let response = HttpResponse::new(
209 StatusCode::OK,
210 headers,
211 bytes::Bytes::new(),
212 Url::parse("https://example.com/sse").expect("coverage URL should parse"),
213 Method::GET,
214 );
215 let error = crate::sse::coverage_validate_sse_response_content_type(&response)
216 .expect_err("non-UTF8 content type should be rejected");
217 (error.kind, error.message)
218 }
219
220 pub async fn exercise_response_preview_paths() -> Vec<String> {
225 crate::response::coverage_exercise_response_preview_paths().await
226 }
227
228 pub async fn exercise_request_cache_paths() -> Vec<String> {
233 crate::request::coverage_exercise_request_cache_paths().await
234 }
235
236 pub async fn exercise_threshold_paths() -> Vec<String> {
241 let mut diagnostics = crate::client::coverage_exercise_factory_paths();
242 diagnostics.push(crate::client::coverage_exercise_request_log_url_fallback());
243 diagnostics.push(format!(
244 "{:?}",
245 crate::HttpClient::coverage_prepare_cancelled_error().await
246 ));
247 diagnostics.extend(crate::options::coverage_exercise_http_client_option_paths());
248 diagnostics.extend(crate::options::coverage_exercise_config_error_paths());
249 diagnostics.push(crate::options::coverage_exercise_retry_option_paths());
250 diagnostics
251 }
252}
253
254pub use client::http_logger::HttpLogger;
255pub use client::HttpClient;
256pub use client::HttpClientFactory;
257pub use constants::DEFAULT_SENSITIVE_HEADER_NAMES;
258pub use error::{HttpError, HttpErrorKind, HttpResult, RetryHint};
259pub use options::{
260 HttpClientOptions, HttpConfigError, HttpConfigErrorKind, HttpLoggingOptions,
261 HttpRetryMethodPolicy, HttpRetryOptions, HttpTimeoutOptions, ProxyOptions, ProxyType,
262 SensitiveHttpHeaders,
263};
264pub use qubit_retry::{RetryDelay, RetryJitter, RetryOptions};
265pub use request::{
266 AsyncHttpHeaderInjector, HttpHeaderInjector, HttpRequest, HttpRequestBody,
267 HttpRequestBodyByteStream, HttpRequestBuilder, HttpRequestInterceptor, HttpRequestInterceptors,
268 HttpRequestRetryOverride, HttpRequestStreamingBody,
269};
270pub use response::{
271 HttpByteStream, HttpResponse, HttpResponseInterceptor, HttpResponseInterceptors,
272 HttpResponseMeta,
273};
274pub use tokio_util::sync::CancellationToken;