use crate::box_error::BoxError;
use crate::client::connector_metadata::ConnectorMetadata;
use crate::client::orchestrator::{HttpRequest, HttpResponse};
use crate::client::result::ConnectorError;
use crate::client::runtime_components::sealed::ValidateConfig;
use crate::client::runtime_components::{RuntimeComponents, RuntimeComponentsBuilder};
use crate::impl_shared_conversions;
use aws_smithy_types::config_bag::ConfigBag;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
new_type_future! {
#[doc = "Future for [`HttpConnector::call`]."]
pub struct HttpConnectorFuture<'static, HttpResponse, ConnectorError>;
}
pub trait HttpConnector: Send + Sync + fmt::Debug {
fn call(&self, request: HttpRequest) -> HttpConnectorFuture;
}
#[derive(Clone, Debug)]
pub struct SharedHttpConnector(Arc<dyn HttpConnector>);
impl SharedHttpConnector {
pub fn new(connection: impl HttpConnector + 'static) -> Self {
Self(Arc::new(connection))
}
}
impl HttpConnector for SharedHttpConnector {
fn call(&self, request: HttpRequest) -> HttpConnectorFuture {
(*self.0).call(request)
}
}
impl_shared_conversions!(convert SharedHttpConnector from HttpConnector using SharedHttpConnector::new);
pub fn http_client_fn<F>(connector: F) -> SharedHttpClient
where
F: Fn(&HttpConnectorSettings, &RuntimeComponents) -> SharedHttpConnector
+ Send
+ Sync
+ 'static,
{
struct ConnectorFn<T>(T);
impl<T> fmt::Debug for ConnectorFn<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ConnectorFn")
}
}
impl<T> HttpClient for ConnectorFn<T>
where
T: (Fn(&HttpConnectorSettings, &RuntimeComponents) -> SharedHttpConnector) + Send + Sync,
{
fn http_connector(
&self,
settings: &HttpConnectorSettings,
components: &RuntimeComponents,
) -> SharedHttpConnector {
(self.0)(settings, components)
}
}
SharedHttpClient::new(ConnectorFn(connector))
}
pub trait HttpClient: Send + Sync + fmt::Debug {
fn http_connector(
&self,
settings: &HttpConnectorSettings,
components: &RuntimeComponents,
) -> SharedHttpConnector;
#[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
fn validate_base_client_config(
&self,
runtime_components: &RuntimeComponentsBuilder,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
let _ = (runtime_components, cfg);
Ok(())
}
#[doc = include_str!("../../rustdoc/validate_final_config.md")]
fn validate_final_config(
&self,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
let _ = (runtime_components, cfg);
Ok(())
}
fn connector_metadata(&self) -> Option<ConnectorMetadata> {
None
}
}
#[derive(Clone, Debug)]
pub struct SharedHttpClient {
selector: Arc<dyn HttpClient>,
}
impl SharedHttpClient {
pub fn new(selector: impl HttpClient + 'static) -> Self {
Self {
selector: Arc::new(selector),
}
}
}
impl HttpClient for SharedHttpClient {
fn http_connector(
&self,
settings: &HttpConnectorSettings,
components: &RuntimeComponents,
) -> SharedHttpConnector {
self.selector.http_connector(settings, components)
}
fn validate_base_client_config(
&self,
runtime_components: &RuntimeComponentsBuilder,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
self.selector
.validate_base_client_config(runtime_components, cfg)
}
fn validate_final_config(
&self,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
self.selector.validate_final_config(runtime_components, cfg)
}
fn connector_metadata(&self) -> Option<ConnectorMetadata> {
self.selector.connector_metadata()
}
}
impl ValidateConfig for SharedHttpClient {
fn validate_base_client_config(
&self,
runtime_components: &RuntimeComponentsBuilder,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
HttpClient::validate_base_client_config(self, runtime_components, cfg)
}
fn validate_final_config(
&self,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
HttpClient::validate_final_config(self, runtime_components, cfg)
}
}
impl_shared_conversions!(convert SharedHttpClient from HttpClient using SharedHttpClient::new);
#[non_exhaustive]
#[derive(Default, Debug)]
pub struct HttpConnectorSettingsBuilder {
connect_timeout: Option<Duration>,
read_timeout: Option<Duration>,
}
impl HttpConnectorSettingsBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
self.connect_timeout = Some(connect_timeout);
self
}
pub fn set_connect_timeout(&mut self, connect_timeout: Option<Duration>) -> &mut Self {
self.connect_timeout = connect_timeout;
self
}
pub fn read_timeout(mut self, read_timeout: Duration) -> Self {
self.read_timeout = Some(read_timeout);
self
}
pub fn set_read_timeout(&mut self, read_timeout: Option<Duration>) -> &mut Self {
self.read_timeout = read_timeout;
self
}
pub fn build(self) -> HttpConnectorSettings {
HttpConnectorSettings {
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
}
}
}
#[non_exhaustive]
#[derive(Clone, Default, Debug)]
pub struct HttpConnectorSettings {
connect_timeout: Option<Duration>,
read_timeout: Option<Duration>,
}
impl HttpConnectorSettings {
pub fn builder() -> HttpConnectorSettingsBuilder {
Default::default()
}
pub fn connect_timeout(&self) -> Option<Duration> {
self.connect_timeout
}
pub fn read_timeout(&self) -> Option<Duration> {
self.read_timeout
}
}
#[cfg(all(test, feature = "test-util"))]
mod tests {
use super::*;
use crate::client::runtime_components::RuntimeComponentsBuilder;
use aws_smithy_types::config_bag::ConfigBag;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Default)]
struct CountingSelector {
base: AtomicUsize,
final_: AtomicUsize,
}
impl HttpClient for CountingSelector {
fn http_connector(
&self,
_settings: &HttpConnectorSettings,
_components: &RuntimeComponents,
) -> SharedHttpConnector {
unreachable!("http_connector is not exercised by these tests")
}
fn validate_base_client_config(
&self,
_runtime_components: &RuntimeComponentsBuilder,
_cfg: &ConfigBag,
) -> Result<(), BoxError> {
self.base.fetch_add(1, Ordering::SeqCst);
Ok(())
}
fn validate_final_config(
&self,
_runtime_components: &RuntimeComponents,
_cfg: &ConfigBag,
) -> Result<(), BoxError> {
self.final_.fetch_add(1, Ordering::SeqCst);
Ok(())
}
}
#[derive(Debug)]
struct DecoratingClient {
inner: SharedHttpClient,
}
impl HttpClient for DecoratingClient {
fn http_connector(
&self,
settings: &HttpConnectorSettings,
components: &RuntimeComponents,
) -> SharedHttpConnector {
self.inner.http_connector(settings, components)
}
fn validate_base_client_config(
&self,
runtime_components: &RuntimeComponentsBuilder,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
HttpClient::validate_base_client_config(&self.inner, runtime_components, cfg)
}
fn validate_final_config(
&self,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
HttpClient::validate_final_config(&self.inner, runtime_components, cfg)
}
}
#[test]
fn shared_http_client_forwards_validation_to_selector() {
let selector = Arc::new(CountingSelector::default());
let shared = SharedHttpClient {
selector: selector.clone(),
};
let cfg = ConfigBag::base();
let builder = RuntimeComponentsBuilder::for_tests();
let components = RuntimeComponentsBuilder::for_tests().build().unwrap();
HttpClient::validate_base_client_config(&shared, &builder, &cfg).unwrap();
HttpClient::validate_final_config(&shared, &components, &cfg).unwrap();
ValidateConfig::validate_base_client_config(&shared, &builder, &cfg).unwrap();
ValidateConfig::validate_final_config(&shared, &components, &cfg).unwrap();
assert_eq!(2, selector.base.load(Ordering::SeqCst));
assert_eq!(2, selector.final_.load(Ordering::SeqCst));
}
#[test]
fn external_decorator_reaches_selector_through_shared_client() {
let selector = Arc::new(CountingSelector::default());
let decorator = DecoratingClient {
inner: SharedHttpClient {
selector: selector.clone(),
},
};
let cfg = ConfigBag::base();
let builder = RuntimeComponentsBuilder::for_tests();
let components = RuntimeComponentsBuilder::for_tests().build().unwrap();
decorator
.validate_base_client_config(&builder, &cfg)
.unwrap();
decorator.validate_final_config(&components, &cfg).unwrap();
assert_eq!(1, selector.base.load(Ordering::SeqCst));
assert_eq!(1, selector.final_.load(Ordering::SeqCst));
}
}