use crate::client::auth::{
AuthScheme, AuthSchemeId, SharedAuthScheme, SharedAuthSchemeOptionResolver,
};
use crate::client::connectors::SharedHttpConnector;
use crate::client::endpoint::SharedEndpointResolver;
use crate::client::identity::{ConfiguredIdentityResolver, SharedIdentityResolver};
use crate::client::interceptors::SharedInterceptor;
use crate::client::retries::{RetryClassifiers, SharedRetryStrategy};
use aws_smithy_async::rt::sleep::SharedAsyncSleep;
use aws_smithy_async::time::SharedTimeSource;
use std::fmt;
pub(crate) static EMPTY_RUNTIME_COMPONENTS_BUILDER: RuntimeComponentsBuilder =
RuntimeComponentsBuilder::new("empty");
macro_rules! merge {
(Option $other:ident . $name:ident => $self:ident) => {
$self.$name = $other.$name.clone().or($self.$name.take());
};
(Vec $other:ident . $name:ident => $self:ident) => {
if !$other.$name.is_empty() {
$self.$name.extend($other.$name.iter().cloned());
}
};
}
macro_rules! builder_field_value {
(Option $self:ident . $name:ident) => {
$self.$name
};
(Option $self:ident . $name:ident required) => {
$self.$name.ok_or(BuildError(concat!(
"the `",
stringify!($name),
"` runtime component is required"
)))?
};
(Vec $self:ident . $name:ident) => {
$self.$name
};
(Vec $self:ident . $name:ident atLeastOneRequired) => {{
if $self.$name.is_empty() {
return Err(BuildError(concat!(
"at least one `",
stringify!($name),
"` runtime component is required"
)));
}
$self.$name
}};
}
macro_rules! runtime_component_field_type {
(Option $inner_type:ident) => {
Option<Tracked<$inner_type>>
};
(Option $inner_type:ident required) => {
Tracked<$inner_type>
};
(Vec $inner_type:ident) => {
Vec<Tracked<$inner_type>>
};
(Vec $inner_type:ident atLeastOneRequired) => {
Vec<Tracked<$inner_type>>
};
}
macro_rules! empty_builder_value {
(Option) => {
None
};
(Vec) => {
Vec::new()
};
}
macro_rules! declare_runtime_components {
(fields for $rc_name:ident and $builder_name:ident {
$($(#[$option:ident])? $field_name:ident : $outer_type:ident<$inner_type:ident> ,)+
}) => {
#[derive(Clone, Debug)]
pub struct $rc_name {
$($field_name: runtime_component_field_type!($outer_type $inner_type $($option)?),)+
}
#[derive(Clone, Debug)]
pub struct $builder_name {
builder_name: &'static str,
$($field_name: $outer_type<Tracked<$inner_type>>,)+
}
impl $builder_name {
pub const fn new(name: &'static str) -> Self {
Self {
builder_name: name,
$($field_name: empty_builder_value!($outer_type),)+
}
}
pub fn merge_from(mut self, other: &Self) -> Self {
$(merge!($outer_type other.$field_name => self);)+
self
}
pub fn build(self) -> Result<$rc_name, BuildError> {
Ok($rc_name {
$($field_name: builder_field_value!($outer_type self.$field_name $($option)?),)+
})
}
}
};
}
declare_runtime_components! {
fields for RuntimeComponents and RuntimeComponentsBuilder {
#[required]
auth_scheme_option_resolver: Option<SharedAuthSchemeOptionResolver>,
http_connector: Option<SharedHttpConnector>,
#[required]
endpoint_resolver: Option<SharedEndpointResolver>,
#[atLeastOneRequired]
auth_schemes: Vec<SharedAuthScheme>,
#[atLeastOneRequired]
identity_resolvers: Vec<ConfiguredIdentityResolver>,
interceptors: Vec<SharedInterceptor>,
retry_classifiers: Option<RetryClassifiers>,
#[required]
retry_strategy: Option<SharedRetryStrategy>,
time_source: Option<SharedTimeSource>,
sleep_impl: Option<SharedAsyncSleep>,
}
}
impl RuntimeComponents {
pub fn builder(name: &'static str) -> RuntimeComponentsBuilder {
RuntimeComponentsBuilder::new(name)
}
pub fn auth_scheme_option_resolver(&self) -> SharedAuthSchemeOptionResolver {
self.auth_scheme_option_resolver.value.clone()
}
pub fn http_connector(&self) -> Option<SharedHttpConnector> {
self.http_connector.as_ref().map(|s| s.value.clone())
}
pub fn endpoint_resolver(&self) -> SharedEndpointResolver {
self.endpoint_resolver.value.clone()
}
pub fn auth_scheme(&self, scheme_id: AuthSchemeId) -> Option<SharedAuthScheme> {
self.auth_schemes
.iter()
.find(|s| s.value.scheme_id() == scheme_id)
.map(|s| s.value.clone())
}
pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
self.interceptors.iter().map(|s| s.value.clone())
}
pub fn retry_classifiers(&self) -> Option<&RetryClassifiers> {
self.retry_classifiers.as_ref().map(|s| &s.value)
}
pub fn retry_strategy(&self) -> SharedRetryStrategy {
self.retry_strategy.value.clone()
}
pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
self.sleep_impl.as_ref().map(|s| s.value.clone())
}
pub fn time_source(&self) -> Option<SharedTimeSource> {
self.time_source.as_ref().map(|s| s.value.clone())
}
}
impl RuntimeComponentsBuilder {
pub fn auth_scheme_option_resolver(&self) -> Option<SharedAuthSchemeOptionResolver> {
self.auth_scheme_option_resolver
.as_ref()
.map(|s| s.value.clone())
}
pub fn set_auth_scheme_option_resolver(
&mut self,
auth_scheme_option_resolver: Option<SharedAuthSchemeOptionResolver>,
) -> &mut Self {
self.auth_scheme_option_resolver =
auth_scheme_option_resolver.map(|r| Tracked::new(self.builder_name, r));
self
}
pub fn with_auth_scheme_option_resolver(
mut self,
auth_scheme_option_resolver: Option<SharedAuthSchemeOptionResolver>,
) -> Self {
self.set_auth_scheme_option_resolver(auth_scheme_option_resolver);
self
}
pub fn http_connector(&self) -> Option<SharedHttpConnector> {
self.http_connector.as_ref().map(|s| s.value.clone())
}
pub fn set_http_connector(&mut self, connector: Option<SharedHttpConnector>) -> &mut Self {
self.http_connector = connector.map(|c| Tracked::new(self.builder_name, c));
self
}
pub fn with_http_connector(mut self, connector: Option<SharedHttpConnector>) -> Self {
self.set_http_connector(connector);
self
}
pub fn endpoint_resolver(&self) -> Option<SharedEndpointResolver> {
self.endpoint_resolver.as_ref().map(|s| s.value.clone())
}
pub fn set_endpoint_resolver(
&mut self,
endpoint_resolver: Option<SharedEndpointResolver>,
) -> &mut Self {
self.endpoint_resolver = endpoint_resolver.map(|s| Tracked::new(self.builder_name, s));
self
}
pub fn with_endpoint_resolver(
mut self,
endpoint_resolver: Option<SharedEndpointResolver>,
) -> Self {
self.set_endpoint_resolver(endpoint_resolver);
self
}
pub fn auth_schemes(&self) -> impl Iterator<Item = SharedAuthScheme> + '_ {
self.auth_schemes.iter().map(|s| s.value.clone())
}
pub fn push_auth_scheme(&mut self, auth_scheme: SharedAuthScheme) -> &mut Self {
self.auth_schemes
.push(Tracked::new(self.builder_name, auth_scheme));
self
}
pub fn with_auth_scheme(mut self, auth_scheme: SharedAuthScheme) -> Self {
self.push_auth_scheme(auth_scheme);
self
}
pub fn push_identity_resolver(
&mut self,
scheme_id: AuthSchemeId,
identity_resolver: SharedIdentityResolver,
) -> &mut Self {
self.identity_resolvers.push(Tracked::new(
self.builder_name,
ConfiguredIdentityResolver::new(scheme_id, identity_resolver),
));
self
}
pub fn with_identity_resolver(
mut self,
scheme_id: AuthSchemeId,
identity_resolver: SharedIdentityResolver,
) -> Self {
self.push_identity_resolver(scheme_id, identity_resolver);
self
}
pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
self.interceptors.iter().map(|s| s.value.clone())
}
pub fn extend_interceptors(
&mut self,
interceptors: impl Iterator<Item = SharedInterceptor>,
) -> &mut Self {
self.interceptors
.extend(interceptors.map(|s| Tracked::new(self.builder_name, s)));
self
}
pub fn push_interceptor(&mut self, interceptor: SharedInterceptor) -> &mut Self {
self.interceptors
.push(Tracked::new(self.builder_name, interceptor));
self
}
pub fn with_interceptor(mut self, interceptor: SharedInterceptor) -> Self {
self.push_interceptor(interceptor);
self
}
pub fn set_interceptors(
&mut self,
interceptors: impl Iterator<Item = SharedInterceptor>,
) -> &mut Self {
self.interceptors.clear();
self.interceptors
.extend(interceptors.map(|s| Tracked::new(self.builder_name, s)));
self
}
pub fn with_interceptors(
mut self,
interceptors: impl Iterator<Item = SharedInterceptor>,
) -> Self {
self.set_interceptors(interceptors);
self
}
pub fn retry_classifiers(&self) -> Option<&RetryClassifiers> {
self.retry_classifiers.as_ref().map(|s| &s.value)
}
pub fn set_retry_classifiers(
&mut self,
retry_classifiers: Option<RetryClassifiers>,
) -> &mut Self {
self.retry_classifiers = retry_classifiers.map(|s| Tracked::new(self.builder_name, s));
self
}
pub fn with_retry_classifiers(mut self, retry_classifiers: Option<RetryClassifiers>) -> Self {
self.retry_classifiers = retry_classifiers.map(|s| Tracked::new(self.builder_name, s));
self
}
pub fn retry_strategy(&self) -> Option<SharedRetryStrategy> {
self.retry_strategy.as_ref().map(|s| s.value.clone())
}
pub fn set_retry_strategy(&mut self, retry_strategy: Option<SharedRetryStrategy>) -> &mut Self {
self.retry_strategy = retry_strategy.map(|s| Tracked::new(self.builder_name, s));
self
}
pub fn with_retry_strategy(mut self, retry_strategy: Option<SharedRetryStrategy>) -> Self {
self.retry_strategy = retry_strategy.map(|s| Tracked::new(self.builder_name, s));
self
}
pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
self.sleep_impl.as_ref().map(|s| s.value.clone())
}
pub fn set_sleep_impl(&mut self, sleep_impl: Option<SharedAsyncSleep>) -> &mut Self {
self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s));
self
}
pub fn with_sleep_impl(mut self, sleep_impl: Option<SharedAsyncSleep>) -> Self {
self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s));
self
}
pub fn time_source(&self) -> Option<SharedTimeSource> {
self.time_source.as_ref().map(|s| s.value.clone())
}
pub fn set_time_source(&mut self, time_source: Option<SharedTimeSource>) -> &mut Self {
self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s));
self
}
pub fn with_time_source(mut self, time_source: Option<SharedTimeSource>) -> Self {
self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s));
self
}
}
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(Eq, PartialEq))]
struct Tracked<T> {
_origin: &'static str,
value: T,
}
impl<T> Tracked<T> {
fn new(origin: &'static str, value: T) -> Self {
Self {
_origin: origin,
value,
}
}
}
impl RuntimeComponentsBuilder {
#[cfg(feature = "test-util")]
pub fn for_tests() -> Self {
use crate::client::auth::AuthSchemeOptionResolver;
use crate::client::connectors::HttpConnector;
use crate::client::endpoint::{EndpointResolver, EndpointResolverParams};
use crate::client::identity::Identity;
use crate::client::identity::IdentityResolver;
use crate::client::orchestrator::Future;
use crate::client::retries::RetryStrategy;
use aws_smithy_async::rt::sleep::AsyncSleep;
use aws_smithy_async::time::TimeSource;
use aws_smithy_types::config_bag::ConfigBag;
use aws_smithy_types::endpoint::Endpoint;
#[derive(Debug)]
struct FakeAuthSchemeOptionResolver;
impl AuthSchemeOptionResolver for FakeAuthSchemeOptionResolver {
fn resolve_auth_scheme_options(
&self,
_: &crate::client::auth::AuthSchemeOptionResolverParams,
) -> Result<std::borrow::Cow<'_, [AuthSchemeId]>, crate::box_error::BoxError>
{
unreachable!("fake auth scheme option resolver must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeConnector;
impl HttpConnector for FakeConnector {
fn call(
&self,
_: crate::client::orchestrator::HttpRequest,
) -> crate::client::orchestrator::BoxFuture<crate::client::orchestrator::HttpResponse>
{
unreachable!("fake connector must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeEndpointResolver;
impl EndpointResolver for FakeEndpointResolver {
fn resolve_endpoint(&self, _: &EndpointResolverParams) -> Future<Endpoint> {
unreachable!("fake endpoint resolver must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeAuthScheme;
impl AuthScheme for FakeAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
AuthSchemeId::new("fake")
}
fn identity_resolver(
&self,
_: &dyn GetIdentityResolver,
) -> Option<SharedIdentityResolver> {
None
}
fn signer(&self) -> &dyn crate::client::auth::Signer {
unreachable!("fake http auth scheme must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeIdentityResolver;
impl IdentityResolver for FakeIdentityResolver {
fn resolve_identity(&self, _: &ConfigBag) -> Future<Identity> {
unreachable!("fake identity resolver must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeRetryStrategy;
impl RetryStrategy for FakeRetryStrategy {
fn should_attempt_initial_request(
&self,
_: &RuntimeComponents,
_: &ConfigBag,
) -> Result<crate::client::retries::ShouldAttempt, crate::box_error::BoxError>
{
unreachable!("fake retry strategy must be overridden for this test")
}
fn should_attempt_retry(
&self,
_: &crate::client::interceptors::context::InterceptorContext,
_: &RuntimeComponents,
_: &ConfigBag,
) -> Result<crate::client::retries::ShouldAttempt, crate::box_error::BoxError>
{
unreachable!("fake retry strategy must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeTimeSource;
impl TimeSource for FakeTimeSource {
fn now(&self) -> std::time::SystemTime {
unreachable!("fake time source must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeSleep;
impl AsyncSleep for FakeSleep {
fn sleep(&self, _: std::time::Duration) -> aws_smithy_async::rt::sleep::Sleep {
unreachable!("fake sleep must be overridden for this test")
}
}
Self::new("aws_smithy_runtime_api::client::runtime_components::RuntimeComponentBuilder::for_tests")
.with_auth_scheme(SharedAuthScheme::new(FakeAuthScheme))
.with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new(FakeAuthSchemeOptionResolver)))
.with_endpoint_resolver(Some(SharedEndpointResolver::new(FakeEndpointResolver)))
.with_http_connector(Some(SharedHttpConnector::new(FakeConnector)))
.with_identity_resolver(AuthSchemeId::new("fake"), SharedIdentityResolver::new(FakeIdentityResolver))
.with_retry_classifiers(Some(RetryClassifiers::new()))
.with_retry_strategy(Some(SharedRetryStrategy::new(FakeRetryStrategy)))
.with_sleep_impl(Some(SharedAsyncSleep::new(FakeSleep)))
.with_time_source(Some(SharedTimeSource::new(FakeTimeSource)))
}
}
#[derive(Debug)]
pub struct BuildError(&'static str);
impl std::error::Error for BuildError {}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub trait GetIdentityResolver: Send + Sync {
fn identity_resolver(&self, scheme_id: AuthSchemeId) -> Option<SharedIdentityResolver>;
}
impl GetIdentityResolver for RuntimeComponents {
fn identity_resolver(&self, scheme_id: AuthSchemeId) -> Option<SharedIdentityResolver> {
self.identity_resolvers
.iter()
.find(|s| s.value.scheme_id() == scheme_id)
.map(|s| s.value.identity_resolver())
}
}
#[cfg(all(test, feature = "test-util"))]
mod tests {
use super::*;
#[test]
#[allow(unreachable_pub)]
#[allow(dead_code)]
fn the_builders_should_merge() {
declare_runtime_components! {
fields for TestRc and TestRcBuilder {
#[required]
some_required_string: Option<String>,
some_optional_string: Option<String>,
#[atLeastOneRequired]
some_required_vec: Vec<String>,
some_optional_vec: Vec<String>,
}
}
let builder1 = TestRcBuilder {
builder_name: "builder1",
some_required_string: Some(Tracked::new("builder1", "override_me".into())),
some_optional_string: Some(Tracked::new("builder1", "override_me optional".into())),
some_required_vec: vec![Tracked::new("builder1", "first".into())],
some_optional_vec: vec![Tracked::new("builder1", "first optional".into())],
};
let builder2 = TestRcBuilder {
builder_name: "builder2",
some_required_string: Some(Tracked::new("builder2", "override_me_too".into())),
some_optional_string: Some(Tracked::new("builder2", "override_me_too optional".into())),
some_required_vec: vec![Tracked::new("builder2", "second".into())],
some_optional_vec: vec![Tracked::new("builder2", "second optional".into())],
};
let builder3 = TestRcBuilder {
builder_name: "builder3",
some_required_string: Some(Tracked::new("builder3", "correct".into())),
some_optional_string: Some(Tracked::new("builder3", "correct optional".into())),
some_required_vec: vec![Tracked::new("builder3", "third".into())],
some_optional_vec: vec![Tracked::new("builder3", "third optional".into())],
};
let rc = TestRcBuilder::new("root")
.merge_from(&builder1)
.merge_from(&builder2)
.merge_from(&builder3)
.build()
.expect("success");
assert_eq!(
Tracked::new("builder3", "correct".to_string()),
rc.some_required_string
);
assert_eq!(
Some(Tracked::new("builder3", "correct optional".to_string())),
rc.some_optional_string
);
assert_eq!(
vec![
Tracked::new("builder1", "first".to_string()),
Tracked::new("builder2", "second".into()),
Tracked::new("builder3", "third".into())
],
rc.some_required_vec
);
assert_eq!(
vec![
Tracked::new("builder1", "first optional".to_string()),
Tracked::new("builder2", "second optional".into()),
Tracked::new("builder3", "third optional".into())
],
rc.some_optional_vec
);
}
#[test]
#[allow(unreachable_pub)]
#[allow(dead_code)]
#[should_panic(expected = "the `_some_string` runtime component is required")]
fn require_field_singular() {
declare_runtime_components! {
fields for TestRc and TestRcBuilder {
#[required]
_some_string: Option<String>,
}
}
let rc = TestRcBuilder::new("test").build().unwrap();
let _: Tracked<String> = rc._some_string;
}
#[test]
#[allow(unreachable_pub)]
#[allow(dead_code)]
#[should_panic(expected = "at least one `_some_vec` runtime component is required")]
fn require_field_plural() {
declare_runtime_components! {
fields for TestRc and TestRcBuilder {
#[atLeastOneRequired]
_some_vec: Vec<String>,
}
}
let rc = TestRcBuilder::new("test").build().unwrap();
let _: Vec<Tracked<String>> = rc._some_vec;
}
#[test]
#[allow(unreachable_pub)]
#[allow(dead_code)]
fn optional_fields_dont_panic() {
declare_runtime_components! {
fields for TestRc and TestRcBuilder {
_some_optional_string: Option<String>,
_some_optional_vec: Vec<String>,
}
}
let rc = TestRcBuilder::new("test").build().unwrap();
let _: Option<Tracked<String>> = rc._some_optional_string;
let _: Vec<Tracked<String>> = rc._some_optional_vec;
}
#[test]
fn building_test_builder_should_not_panic() {
let _ = RuntimeComponentsBuilder::for_tests().build(); }
}