use crate::box_error::BoxError;
use crate::client::auth::{
AuthScheme, AuthSchemeId, ResolveAuthSchemeOptions, SharedAuthScheme,
SharedAuthSchemeOptionResolver,
};
use crate::client::endpoint::{ResolveEndpoint, SharedEndpointResolver};
use crate::client::http::{HttpClient, SharedHttpClient};
use crate::client::identity::{
ResolveCachedIdentity, ResolveIdentity, SharedIdentityCache, SharedIdentityResolver,
};
use crate::client::interceptors::{Intercept, SharedInterceptor};
use crate::client::retries::classifiers::{ClassifyRetry, SharedRetryClassifier};
use crate::client::retries::{RetryStrategy, SharedRetryStrategy};
use crate::impl_shared_conversions;
use crate::shared::IntoShared;
use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep};
use aws_smithy_async::time::{SharedTimeSource, TimeSource};
use aws_smithy_types::config_bag::ConfigBag;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
pub(crate) static EMPTY_RUNTIME_COMPONENTS_BUILDER: RuntimeComponentsBuilder =
RuntimeComponentsBuilder::new("empty");
pub(crate) mod sealed {
use super::*;
pub trait ValidateConfig: fmt::Debug + Send + Sync {
#[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(())
}
}
}
use sealed::ValidateConfig;
#[derive(Clone)]
enum ValidatorInner {
BaseConfigStaticFn(fn(&RuntimeComponentsBuilder, &ConfigBag) -> Result<(), BoxError>),
Shared(Arc<dyn ValidateConfig>),
}
impl fmt::Debug for ValidatorInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BaseConfigStaticFn(_) => f.debug_tuple("StaticFn").finish(),
Self::Shared(_) => f.debug_tuple("Shared").finish(),
}
}
}
#[derive(Clone, Debug)]
pub struct SharedConfigValidator {
inner: ValidatorInner,
}
impl SharedConfigValidator {
pub(crate) fn new(validator: impl ValidateConfig + 'static) -> Self {
Self {
inner: ValidatorInner::Shared(Arc::new(validator) as _),
}
}
pub fn base_client_config_fn(
validator: fn(&RuntimeComponentsBuilder, &ConfigBag) -> Result<(), BoxError>,
) -> Self {
Self {
inner: ValidatorInner::BaseConfigStaticFn(validator),
}
}
}
impl ValidateConfig for SharedConfigValidator {
fn validate_base_client_config(
&self,
runtime_components: &RuntimeComponentsBuilder,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
match &self.inner {
ValidatorInner::BaseConfigStaticFn(validator) => validator(runtime_components, cfg),
ValidatorInner::Shared(validator) => {
validator.validate_base_client_config(runtime_components, cfg)
}
}
}
fn validate_final_config(
&self,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
match &self.inner {
ValidatorInner::Shared(validator) => {
validator.validate_final_config(runtime_components, cfg)
}
_ => Ok(()),
}
}
}
impl_shared_conversions!(convert SharedConfigValidator from ValidateConfig using SharedConfigValidator::new);
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());
}
};
(OptionalAuthSchemeMap $other:ident . $name:ident => $self:ident ) => {
if let Some(m) = &$other.$name {
let mut us = $self.$name.unwrap_or_default();
us.extend(m.iter().map(|(k, v)| (k.clone(), v.clone())));
$self.$name = Some(us);
}
};
}
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
};
(OptionalAuthSchemeMap $self:ident . $name:ident atLeastOneRequired) => {{
match $self.$name {
Some(map) => map,
None => {
return Err(BuildError(concat!(
"at least one `",
stringify!($name),
"` runtime component is required"
)));
}
}
}};
(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>>
};
(OptionalAuthSchemeMap $inner_type: ident atLeastOneRequired) => { AuthSchemeMap<Tracked<$inner_type>> };
}
macro_rules! empty_builder_value {
(Option) => {
None
};
(Vec) => {
Vec::new()
};
(OptionalAuthSchemeMap) => {
None
};
}
type OptionalAuthSchemeMap<V> = Option<AuthSchemeMap<V>>;
type AuthSchemeMap<V> = HashMap<AuthSchemeId, V>;
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> {
let mut rcs = $rc_name {
$($field_name: builder_field_value!($outer_type self.$field_name $($option)?),)+
};
rcs.sort();
Ok(rcs)
}
}
};
}
declare_runtime_components! {
fields for RuntimeComponents and RuntimeComponentsBuilder {
#[required]
auth_scheme_option_resolver: Option<SharedAuthSchemeOptionResolver>,
http_client: Option<SharedHttpClient>,
#[required]
endpoint_resolver: Option<SharedEndpointResolver>,
#[atLeastOneRequired]
auth_schemes: Vec<SharedAuthScheme>,
#[required]
identity_cache: Option<SharedIdentityCache>,
#[atLeastOneRequired]
identity_resolvers: OptionalAuthSchemeMap<SharedIdentityResolver>,
interceptors: Vec<SharedInterceptor>,
retry_classifiers: Vec<SharedRetryClassifier>,
#[required]
retry_strategy: Option<SharedRetryStrategy>,
time_source: Option<SharedTimeSource>,
sleep_impl: Option<SharedAsyncSleep>,
config_validators: Vec<SharedConfigValidator>,
}
}
impl RuntimeComponents {
pub fn builder(name: &'static str) -> RuntimeComponentsBuilder {
RuntimeComponentsBuilder::new(name)
}
pub fn to_builder(&self) -> RuntimeComponentsBuilder {
RuntimeComponentsBuilder::from_runtime_components(
self.clone(),
"RuntimeComponentsBuilder::from_runtime_components",
)
}
pub fn auth_scheme_option_resolver(&self) -> SharedAuthSchemeOptionResolver {
self.auth_scheme_option_resolver.value.clone()
}
pub fn http_client(&self) -> Option<SharedHttpClient> {
self.http_client.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 identity_cache(&self) -> SharedIdentityCache {
self.identity_cache.value.clone()
}
pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
self.interceptors.iter().map(|s| s.value.clone())
}
pub fn retry_classifiers(&self) -> impl Iterator<Item = SharedRetryClassifier> + '_ {
self.retry_classifiers.iter().map(|s| s.value.clone())
}
#[cfg(debug_assertions)]
pub(crate) fn retry_classifiers_slice(&self) -> &[Tracked<SharedRetryClassifier>] {
self.retry_classifiers.as_slice()
}
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())
}
pub fn config_validators(&self) -> impl Iterator<Item = SharedConfigValidator> + '_ {
self.config_validators.iter().map(|s| s.value.clone())
}
pub fn validate_final_config(&self, cfg: &ConfigBag) -> Result<(), BoxError> {
macro_rules! validate {
(Required: $field:expr) => {
ValidateConfig::validate_final_config(&$field.value, self, cfg)?;
};
(Option: $field:expr) => {
if let Some(field) = $field.as_ref() {
ValidateConfig::validate_final_config(&field.value, self, cfg)?;
}
};
(Vec: $field:expr) => {
for entry in $field {
ValidateConfig::validate_final_config(&entry.value, self, cfg)?;
}
};
(Map: $field:expr) => {
for entry in $field.values() {
ValidateConfig::validate_final_config(&entry.value, self, cfg)?;
}
};
}
tracing::trace!(runtime_components=?self, cfg=?cfg, "validating final config");
for validator in self.config_validators() {
validator.validate_final_config(self, cfg)?;
}
validate!(Option: self.http_client);
validate!(Required: self.endpoint_resolver);
validate!(Vec: &self.auth_schemes);
validate!(Required: self.identity_cache);
validate!(Map: self.identity_resolvers);
validate!(Vec: &self.interceptors);
validate!(Required: self.retry_strategy);
validate!(Vec: &self.retry_classifiers);
Ok(())
}
fn sort(&mut self) {
self.retry_classifiers.sort_by_key(|rc| rc.value.priority());
}
}
impl RuntimeComponentsBuilder {
pub fn from_runtime_components(rc: RuntimeComponents, builder_name: &'static str) -> Self {
Self {
builder_name,
auth_scheme_option_resolver: Some(rc.auth_scheme_option_resolver),
http_client: rc.http_client,
endpoint_resolver: Some(rc.endpoint_resolver),
auth_schemes: rc.auth_schemes,
identity_cache: Some(rc.identity_cache),
identity_resolvers: Some(rc.identity_resolvers),
interceptors: rc.interceptors,
retry_classifiers: rc.retry_classifiers,
retry_strategy: Some(rc.retry_strategy),
time_source: rc.time_source,
sleep_impl: rc.sleep_impl,
config_validators: rc.config_validators,
}
}
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<impl ResolveAuthSchemeOptions + 'static>,
) -> &mut Self {
self.auth_scheme_option_resolver =
self.tracked(auth_scheme_option_resolver.map(IntoShared::into_shared));
self
}
pub fn with_auth_scheme_option_resolver(
mut self,
auth_scheme_option_resolver: Option<impl ResolveAuthSchemeOptions + 'static>,
) -> Self {
self.set_auth_scheme_option_resolver(auth_scheme_option_resolver);
self
}
pub fn http_client(&self) -> Option<SharedHttpClient> {
self.http_client.as_ref().map(|s| s.value.clone())
}
pub fn set_http_client(&mut self, connector: Option<impl HttpClient + 'static>) -> &mut Self {
self.http_client = self.tracked(connector.map(IntoShared::into_shared));
self
}
pub fn with_http_client(mut self, connector: Option<impl HttpClient + 'static>) -> Self {
self.set_http_client(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<impl ResolveEndpoint + 'static>,
) -> &mut Self {
self.endpoint_resolver =
endpoint_resolver.map(|s| Tracked::new(self.builder_name, s.into_shared()));
self
}
pub fn with_endpoint_resolver(
mut self,
endpoint_resolver: Option<impl ResolveEndpoint + 'static>,
) -> 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: impl AuthScheme + 'static) -> &mut Self {
self.auth_schemes
.push(Tracked::new(self.builder_name, auth_scheme.into_shared()));
self
}
pub fn with_auth_scheme(mut self, auth_scheme: impl AuthScheme + 'static) -> Self {
self.push_auth_scheme(auth_scheme);
self
}
pub fn identity_cache(&self) -> Option<SharedIdentityCache> {
self.identity_cache.as_ref().map(|s| s.value.clone())
}
pub fn set_identity_cache(
&mut self,
identity_cache: Option<impl ResolveCachedIdentity + 'static>,
) -> &mut Self {
self.identity_cache =
identity_cache.map(|c| Tracked::new(self.builder_name, c.into_shared()));
self
}
pub fn with_identity_cache(
mut self,
identity_cache: Option<impl ResolveCachedIdentity + 'static>,
) -> Self {
self.set_identity_cache(identity_cache);
self
}
#[deprecated(
note = "This method is broken since it does not replace an existing identity resolver of the given auth scheme ID. Use `set_identity_resolver` instead."
)]
pub fn push_identity_resolver(
&mut self,
scheme_id: AuthSchemeId,
identity_resolver: impl ResolveIdentity + 'static,
) -> &mut Self {
self.set_identity_resolver(scheme_id, identity_resolver)
}
pub fn set_identity_resolver(
&mut self,
scheme_id: AuthSchemeId,
identity_resolver: impl ResolveIdentity + 'static,
) -> &mut Self {
let mut resolvers = self.identity_resolvers.take().unwrap_or_default();
resolvers.insert(
scheme_id,
Tracked::new(self.builder_name, identity_resolver.into_shared()),
);
self.identity_resolvers = Some(resolvers);
self
}
pub fn with_identity_resolver(
mut self,
scheme_id: AuthSchemeId,
identity_resolver: impl ResolveIdentity + 'static,
) -> Self {
self.set_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: impl Intercept + 'static) -> &mut Self {
self.interceptors
.push(Tracked::new(self.builder_name, interceptor.into_shared()));
self
}
pub fn with_interceptor(mut self, interceptor: impl Intercept + 'static) -> 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) -> impl Iterator<Item = SharedRetryClassifier> + '_ {
self.retry_classifiers.iter().map(|s| s.value.clone())
}
pub fn extend_retry_classifiers(
&mut self,
retry_classifiers: impl Iterator<Item = SharedRetryClassifier>,
) -> &mut Self {
self.retry_classifiers
.extend(retry_classifiers.map(|s| Tracked::new(self.builder_name, s)));
self
}
pub fn push_retry_classifier(
&mut self,
retry_classifier: impl ClassifyRetry + 'static,
) -> &mut Self {
self.retry_classifiers.push(Tracked::new(
self.builder_name,
retry_classifier.into_shared(),
));
self
}
pub fn with_retry_classifier(mut self, retry_classifier: impl ClassifyRetry + 'static) -> Self {
self.push_retry_classifier(retry_classifier);
self
}
pub fn set_retry_classifiers(
&mut self,
retry_classifiers: impl Iterator<Item = SharedRetryClassifier>,
) -> &mut Self {
self.retry_classifiers.clear();
self.retry_classifiers
.extend(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<impl RetryStrategy + 'static>,
) -> &mut Self {
self.retry_strategy =
retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared()));
self
}
pub fn with_retry_strategy(
mut self,
retry_strategy: Option<impl RetryStrategy + 'static>,
) -> Self {
self.retry_strategy =
retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared()));
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 = self.tracked(sleep_impl);
self
}
pub fn with_sleep_impl(mut self, sleep_impl: Option<impl AsyncSleep + 'static>) -> Self {
self.set_sleep_impl(sleep_impl.map(IntoShared::into_shared));
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 = self.tracked(time_source);
self
}
pub fn with_time_source(mut self, time_source: Option<impl TimeSource + 'static>) -> Self {
self.set_time_source(time_source.map(IntoShared::into_shared));
self
}
pub fn config_validators(&self) -> impl Iterator<Item = SharedConfigValidator> + '_ {
self.config_validators.iter().map(|s| s.value.clone())
}
pub fn extend_config_validators(
&mut self,
config_validators: impl Iterator<Item = SharedConfigValidator>,
) -> &mut Self {
self.config_validators
.extend(config_validators.map(|s| Tracked::new(self.builder_name, s)));
self
}
pub fn push_config_validator(
&mut self,
config_validator: impl ValidateConfig + 'static,
) -> &mut Self {
self.config_validators.push(Tracked::new(
self.builder_name,
config_validator.into_shared(),
));
self
}
pub fn with_config_validator(
mut self,
config_validator: impl ValidateConfig + 'static,
) -> Self {
self.push_config_validator(config_validator);
self
}
pub fn validate_base_client_config(&self, cfg: &ConfigBag) -> Result<(), BoxError> {
macro_rules! validate {
($field:expr) => {
for entry in $field {
ValidateConfig::validate_base_client_config(&entry.value, self, cfg)?;
}
};
}
tracing::trace!(runtime_components=?self, cfg=?cfg, "validating base client config");
for validator in self.config_validators() {
validator.validate_base_client_config(self, cfg)?;
}
validate!(&self.http_client);
validate!(&self.endpoint_resolver);
validate!(&self.auth_schemes);
validate!(&self.identity_cache);
if let Some(resolvers) = &self.identity_resolvers {
validate!(resolvers.values())
}
validate!(&self.interceptors);
validate!(&self.retry_strategy);
Ok(())
}
fn tracked<T>(&self, v: Option<T>) -> Option<Tracked<T>> {
v.map(|v| Tracked::new(self.builder_name, v))
}
}
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub(crate) struct Tracked<T> {
_origin: &'static str,
value: T,
}
impl<T> Tracked<T> {
fn new(origin: &'static str, value: T) -> Self {
Self {
_origin: origin,
value,
}
}
#[cfg(debug_assertions)]
pub(crate) fn value(&self) -> &T {
&self.value
}
}
impl RuntimeComponentsBuilder {
#[cfg(feature = "test-util")]
pub fn for_tests() -> Self {
use crate::client::endpoint::{EndpointFuture, EndpointResolverParams};
use crate::client::identity::IdentityFuture;
#[derive(Debug)]
struct FakeAuthSchemeOptionResolver;
impl ResolveAuthSchemeOptions for FakeAuthSchemeOptionResolver {
fn resolve_auth_scheme_options(
&self,
_: &crate::client::auth::AuthSchemeOptionResolverParams,
) -> Result<std::borrow::Cow<'_, [AuthSchemeId]>, BoxError> {
unreachable!("fake auth scheme option resolver must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeClient;
impl HttpClient for FakeClient {
fn http_connector(
&self,
_: &crate::client::http::HttpConnectorSettings,
_: &RuntimeComponents,
) -> crate::client::http::SharedHttpConnector {
unreachable!("fake client must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeEndpointResolver;
impl ResolveEndpoint for FakeEndpointResolver {
fn resolve_endpoint<'a>(&'a self, _: &'a EndpointResolverParams) -> EndpointFuture<'a> {
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::Sign {
unreachable!("fake http auth scheme must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeIdentityResolver;
impl ResolveIdentity for FakeIdentityResolver {
fn resolve_identity<'a>(
&'a self,
_: &'a RuntimeComponents,
_: &'a ConfigBag,
) -> IdentityFuture<'a> {
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, 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, 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")
}
}
#[derive(Debug)]
struct FakeIdentityCache;
impl ResolveCachedIdentity for FakeIdentityCache {
fn resolve_cached_identity<'a>(
&'a self,
resolver: SharedIdentityResolver,
components: &'a RuntimeComponents,
config_bag: &'a ConfigBag,
) -> IdentityFuture<'a> {
IdentityFuture::new(async move {
resolver.resolve_identity(components, config_bag).await
})
}
}
Self::new("aws_smithy_runtime_api::client::runtime_components::RuntimeComponentBuilder::for_tests")
.with_auth_scheme(FakeAuthScheme)
.with_auth_scheme_option_resolver(Some(FakeAuthSchemeOptionResolver))
.with_endpoint_resolver(Some(FakeEndpointResolver))
.with_http_client(Some(FakeClient))
.with_identity_cache(Some(FakeIdentityCache))
.with_identity_resolver(AuthSchemeId::new("fake"), FakeIdentityResolver)
.with_retry_strategy(Some(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
.get(&scheme_id)
.map(|s| s.value.clone())
}
}
#[cfg(all(test, feature = "test-util"))]
mod tests {
use super::{BuildError, RuntimeComponentsBuilder, Tracked};
use crate::client::runtime_components::ValidateConfig;
#[derive(Clone, Debug, Eq, PartialEq)]
struct TestComponent(String);
impl ValidateConfig for TestComponent {}
impl From<&'static str> for TestComponent {
fn from(value: &'static str) -> Self {
TestComponent(value.into())
}
}
#[test]
#[allow(unreachable_pub)]
#[allow(dead_code)]
fn the_builders_should_merge() {
declare_runtime_components! {
fields for TestRc and TestRcBuilder {
#[required]
some_required_component: Option<TestComponent>,
some_optional_component: Option<TestComponent>,
#[atLeastOneRequired]
some_required_vec: Vec<TestComponent>,
some_optional_vec: Vec<TestComponent>,
}
}
impl TestRc {
fn sort(&mut self) {}
}
let builder1 = TestRcBuilder {
builder_name: "builder1",
some_required_component: Some(Tracked::new("builder1", "override_me".into())),
some_optional_component: 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_component: Some(Tracked::new("builder2", "override_me_too".into())),
some_optional_component: 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_component: Some(Tracked::new("builder3", "correct".into())),
some_optional_component: 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", TestComponent::from("correct")),
rc.some_required_component
);
assert_eq!(
Some(Tracked::new(
"builder3",
TestComponent::from("correct optional")
)),
rc.some_optional_component
);
assert_eq!(
vec![
Tracked::new("builder1", TestComponent::from("first")),
Tracked::new("builder2", TestComponent::from("second")),
Tracked::new("builder3", TestComponent::from("third"))
],
rc.some_required_vec
);
assert_eq!(
vec![
Tracked::new("builder1", TestComponent::from("first optional")),
Tracked::new("builder2", TestComponent::from("second optional")),
Tracked::new("builder3", TestComponent::from("third optional"))
],
rc.some_optional_vec
);
}
#[test]
#[allow(unreachable_pub)]
#[allow(dead_code)]
#[should_panic(expected = "the `_some_component` runtime component is required")]
fn require_field_singular() {
declare_runtime_components! {
fields for TestRc and TestRcBuilder {
#[required]
_some_component: Option<TestComponent>,
}
}
impl TestRc {
fn sort(&mut self) {}
}
let rc = TestRcBuilder::new("test").build().unwrap();
let _: Tracked<TestComponent> = rc._some_component;
}
#[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<TestComponent>,
}
}
impl TestRc {
fn sort(&mut self) {}
}
let rc = TestRcBuilder::new("test").build().unwrap();
let _: Vec<Tracked<TestComponent>> = 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_component: Option<TestComponent>,
_some_optional_vec: Vec<TestComponent>,
}
}
impl TestRc {
fn sort(&mut self) {}
}
let rc = TestRcBuilder::new("test").build().unwrap();
let _: Option<Tracked<TestComponent>> = rc._some_optional_component;
let _: Vec<Tracked<TestComponent>> = rc._some_optional_vec;
}
#[test]
fn building_test_builder_should_not_panic() {
let _ = RuntimeComponentsBuilder::for_tests().build(); }
#[test]
fn set_identity_resolver_should_replace_existing_resolver_for_given_auth_scheme() {
use crate::client::auth::AuthSchemeId;
use crate::client::identity::{Identity, IdentityFuture, ResolveIdentity};
use crate::client::runtime_components::{GetIdentityResolver, RuntimeComponents};
use aws_smithy_types::config_bag::ConfigBag;
use tokio::runtime::Runtime;
#[derive(Debug)]
struct AnotherFakeIdentityResolver;
impl ResolveIdentity for AnotherFakeIdentityResolver {
fn resolve_identity<'a>(
&'a self,
_: &'a RuntimeComponents,
_: &'a ConfigBag,
) -> IdentityFuture<'a> {
IdentityFuture::ready(Ok(Identity::new("doesn't matter", None)))
}
}
let rc = RuntimeComponentsBuilder::for_tests()
.with_identity_resolver(AuthSchemeId::new("fake"), AnotherFakeIdentityResolver)
.build()
.expect("should build RuntimeComponents");
let resolver = rc
.identity_resolver(AuthSchemeId::new("fake"))
.expect("identity resolver should be found");
let identity = Runtime::new().unwrap().block_on(async {
resolver
.resolve_identity(&rc, &ConfigBag::base())
.await
.expect("identity should be resolved")
});
assert_eq!(Some(&"doesn't matter"), identity.data::<&str>());
}
}