#![allow(clippy::result_large_err)]
mod client;
pub mod constants;
mod error;
mod options;
mod request;
mod response;
pub mod sse;
#[cfg(coverage)]
#[doc(hidden)]
pub mod coverage_support {
use std::time::{Duration, Instant};
use http::{HeaderMap, HeaderValue, Method, StatusCode};
use qubit_retry::{AttemptFailure, RetryContext, RetryError, RetryErrorReason};
use url::Url;
use crate::error::backend_error_mapper;
use crate::error::ReqwestErrorPhase;
use crate::{HttpError, HttpErrorKind, HttpResponse};
pub fn classify_timeout_kinds() -> Vec<HttpErrorKind> {
vec![
backend_error_mapper::coverage_classify_timeout_kind(
true,
Some(ReqwestErrorPhase::Send),
),
backend_error_mapper::coverage_classify_timeout_kind(
false,
Some(ReqwestErrorPhase::Send),
),
backend_error_mapper::coverage_classify_timeout_kind(
false,
Some(ReqwestErrorPhase::Read),
),
backend_error_mapper::coverage_classify_timeout_kind(false, None),
]
}
pub fn classify_backend_error_kinds() -> Vec<HttpErrorKind> {
vec![
backend_error_mapper::coverage_classify_reqwest_error_kind(
false,
true,
false,
false,
false,
None,
HttpErrorKind::Transport,
),
backend_error_mapper::coverage_classify_reqwest_error_kind(
false,
false,
true,
false,
false,
None,
HttpErrorKind::Transport,
),
backend_error_mapper::coverage_classify_reqwest_error_kind(
false,
false,
false,
true,
false,
None,
HttpErrorKind::Transport,
),
backend_error_mapper::coverage_classify_reqwest_error_kind(
false,
false,
false,
false,
false,
None,
HttpErrorKind::Transport,
),
]
}
pub fn exercise_http_retry_mapping_paths() -> Vec<String> {
let started_at = Instant::now();
let context = RetryContext::new(2, 2);
let attempts_exceeded = RetryError::<HttpError>::coverage_new(
RetryErrorReason::AttemptsExceeded,
None,
context,
);
let aborted =
RetryError::<HttpError>::coverage_new(RetryErrorReason::Aborted, None, context);
let unsupported = RetryError::<HttpError>::coverage_new(
RetryErrorReason::UnsupportedOperation,
None,
context,
);
let max_elapsed = RetryError::<HttpError>::coverage_new(
RetryErrorReason::MaxTotalElapsedExceeded,
None,
context,
);
let max_elapsed_unbounded = RetryError::<HttpError>::coverage_new(
RetryErrorReason::MaxTotalElapsedExceeded,
None,
context,
);
vec![
crate::HttpClient::coverage_map_retry_error(
attempts_exceeded,
started_at,
Some(Duration::from_millis(1)),
2,
)
.message,
crate::HttpClient::coverage_map_retry_error(
aborted,
started_at,
Some(Duration::from_millis(1)),
2,
)
.message,
crate::HttpClient::coverage_map_retry_error(
unsupported,
started_at,
Some(Duration::from_millis(1)),
2,
)
.message,
crate::HttpClient::coverage_map_retry_error(
max_elapsed,
started_at,
Some(Duration::from_millis(1)),
2,
)
.message,
crate::HttpClient::coverage_map_retry_error(max_elapsed_unbounded, started_at, None, 2)
.message,
]
}
pub fn exercise_retry_failure_decision_path() -> String {
let retry_options = crate::HttpRetryOptions::default()
.to_executor_options()
.expect("default retry options should build");
let decision = crate::HttpClient::coverage_retry_failure_decision(
&AttemptFailure::Timeout,
&RetryContext::new(1, 2),
&crate::HttpRetryOptions::default(),
&retry_options,
);
format!("{decision:?}")
}
pub fn validate_non_utf8_sse_content_type() -> (HttpErrorKind, String) {
let mut headers = HeaderMap::new();
headers.insert(
http::header::CONTENT_TYPE,
HeaderValue::from_bytes(b"\xFF").expect("opaque header bytes should be accepted"),
);
let response = HttpResponse::new(
StatusCode::OK,
headers,
bytes::Bytes::new(),
Url::parse("https://example.com/sse").expect("coverage URL should parse"),
Method::GET,
);
let error = crate::sse::coverage_validate_sse_response_content_type(&response)
.expect_err("non-UTF8 content type should be rejected");
(error.kind, error.message)
}
pub async fn exercise_response_preview_paths() -> Vec<String> {
crate::response::coverage_exercise_response_preview_paths().await
}
pub async fn exercise_request_cache_paths() -> Vec<String> {
crate::request::coverage_exercise_request_cache_paths().await
}
pub async fn exercise_threshold_paths() -> Vec<String> {
let mut diagnostics = crate::client::coverage_exercise_factory_paths();
diagnostics.push(crate::client::coverage_exercise_request_log_url_fallback());
diagnostics.push(format!(
"{:?}",
crate::HttpClient::coverage_prepare_cancelled_error().await
));
diagnostics.extend(crate::options::coverage_exercise_http_client_option_paths());
diagnostics.extend(crate::options::coverage_exercise_config_error_paths());
diagnostics.push(crate::options::coverage_exercise_retry_option_paths());
diagnostics
}
}
pub use client::http_logger::HttpLogger;
pub use client::HttpClient;
pub use client::HttpClientFactory;
pub use constants::DEFAULT_SENSITIVE_HEADER_NAMES;
pub use error::{HttpError, HttpErrorKind, HttpResult, RetryHint};
pub use options::{
HttpClientOptions, HttpConfigError, HttpConfigErrorKind, HttpLoggingOptions,
HttpRetryMethodPolicy, HttpRetryOptions, HttpTimeoutOptions, ProxyOptions, ProxyType,
SensitiveHttpHeaders,
};
pub use qubit_retry::{RetryDelay, RetryJitter, RetryOptions};
pub use request::{
AsyncHttpHeaderInjector, HttpHeaderInjector, HttpRequest, HttpRequestBody,
HttpRequestBodyByteStream, HttpRequestBuilder, HttpRequestInterceptor, HttpRequestInterceptors,
HttpRequestRetryOverride, HttpRequestStreamingBody,
};
pub use response::{
HttpByteStream, HttpResponse, HttpResponseInterceptor, HttpResponseInterceptors,
HttpResponseMeta,
};
pub use tokio_util::sync::CancellationToken;