1use crate::box_error::BoxError;
14use crate::client::auth::{
15 AuthScheme, AuthSchemeId, ResolveAuthSchemeOptions, SharedAuthScheme,
16 SharedAuthSchemeOptionResolver,
17};
18use crate::client::endpoint::{ResolveEndpoint, SharedEndpointResolver};
19use crate::client::http::{HttpClient, SharedHttpClient};
20use crate::client::identity::{
21 ResolveCachedIdentity, ResolveIdentity, SharedIdentityCache, SharedIdentityResolver,
22};
23use crate::client::interceptors::{Intercept, SharedInterceptor};
24use crate::client::retries::classifiers::{ClassifyRetry, SharedRetryClassifier};
25use crate::client::retries::{RetryStrategy, SharedRetryStrategy};
26use crate::impl_shared_conversions;
27use crate::shared::IntoShared;
28use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep};
29use aws_smithy_async::time::{SharedTimeSource, TimeSource};
30use aws_smithy_types::config_bag::ConfigBag;
31use std::collections::HashMap;
32use std::fmt;
33use std::sync::Arc;
34
35pub(crate) static EMPTY_RUNTIME_COMPONENTS_BUILDER: RuntimeComponentsBuilder =
36 RuntimeComponentsBuilder::new("empty");
37
38pub(crate) mod sealed {
39 use super::*;
40
41 pub trait ValidateConfig: fmt::Debug + Send + Sync {
46 #[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
47 fn validate_base_client_config(
48 &self,
49 runtime_components: &RuntimeComponentsBuilder,
50 cfg: &ConfigBag,
51 ) -> Result<(), BoxError> {
52 let _ = (runtime_components, cfg);
53 Ok(())
54 }
55
56 #[doc = include_str!("../../rustdoc/validate_final_config.md")]
57 fn validate_final_config(
58 &self,
59 runtime_components: &RuntimeComponents,
60 cfg: &ConfigBag,
61 ) -> Result<(), BoxError> {
62 let _ = (runtime_components, cfg);
63 Ok(())
64 }
65 }
66}
67use sealed::ValidateConfig;
68
69#[derive(Clone)]
70enum ValidatorInner {
71 BaseConfigStaticFn(fn(&RuntimeComponentsBuilder, &ConfigBag) -> Result<(), BoxError>),
72 Shared(Arc<dyn ValidateConfig>),
73}
74
75impl fmt::Debug for ValidatorInner {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self {
78 Self::BaseConfigStaticFn(_) => f.debug_tuple("StaticFn").finish(),
79 Self::Shared(_) => f.debug_tuple("Shared").finish(),
80 }
81 }
82}
83
84#[derive(Clone, Debug)]
86pub struct SharedConfigValidator {
87 inner: ValidatorInner,
88}
89
90impl SharedConfigValidator {
91 pub(crate) fn new(validator: impl ValidateConfig + 'static) -> Self {
93 Self {
94 inner: ValidatorInner::Shared(Arc::new(validator) as _),
95 }
96 }
97
98 pub fn base_client_config_fn(
130 validator: fn(&RuntimeComponentsBuilder, &ConfigBag) -> Result<(), BoxError>,
131 ) -> Self {
132 Self {
133 inner: ValidatorInner::BaseConfigStaticFn(validator),
134 }
135 }
136}
137
138impl ValidateConfig for SharedConfigValidator {
139 fn validate_base_client_config(
140 &self,
141 runtime_components: &RuntimeComponentsBuilder,
142 cfg: &ConfigBag,
143 ) -> Result<(), BoxError> {
144 match &self.inner {
145 ValidatorInner::BaseConfigStaticFn(validator) => validator(runtime_components, cfg),
146 ValidatorInner::Shared(validator) => {
147 validator.validate_base_client_config(runtime_components, cfg)
148 }
149 }
150 }
151
152 fn validate_final_config(
153 &self,
154 runtime_components: &RuntimeComponents,
155 cfg: &ConfigBag,
156 ) -> Result<(), BoxError> {
157 match &self.inner {
158 ValidatorInner::Shared(validator) => {
159 validator.validate_final_config(runtime_components, cfg)
160 }
161 _ => Ok(()),
162 }
163 }
164}
165
166impl_shared_conversions!(convert SharedConfigValidator from ValidateConfig using SharedConfigValidator::new);
167
168macro_rules! merge {
172 (Option $other:ident . $name:ident => $self:ident) => {
173 $self.$name = $other.$name.clone().or($self.$name.take());
174 };
175 (Vec $other:ident . $name:ident => $self:ident) => {
176 if !$other.$name.is_empty() {
177 $self.$name.extend($other.$name.iter().cloned());
178 }
179 };
180 (OptionalAuthSchemeMap $other:ident . $name:ident => $self:ident ) => {
181 if let Some(m) = &$other.$name {
182 let mut us = $self.$name.unwrap_or_default();
183 us.extend(m.iter().map(|(k, v)| (k.clone(), v.clone())));
184 $self.$name = Some(us);
185 }
186 };
187}
188macro_rules! builder_field_value {
194 (Option $self:ident . $name:ident) => {
195 $self.$name
196 };
197 (Option $self:ident . $name:ident required) => {
198 $self.$name.ok_or(BuildError(concat!(
199 "the `",
200 stringify!($name),
201 "` runtime component is required"
202 )))?
203 };
204 (Vec $self:ident . $name:ident) => {
205 $self.$name
206 };
207 (OptionalAuthSchemeMap $self:ident . $name:ident atLeastOneRequired) => {{
208 match $self.$name {
209 Some(map) => map,
210 None => {
211 return Err(BuildError(concat!(
212 "at least one `",
213 stringify!($name),
214 "` runtime component is required"
215 )));
216 }
217 }
218 }};
219 (Vec $self:ident . $name:ident atLeastOneRequired) => {{
220 if $self.$name.is_empty() {
221 return Err(BuildError(concat!(
222 "at least one `",
223 stringify!($name),
224 "` runtime component is required"
225 )));
226 }
227 $self.$name
228 }};
229}
230macro_rules! runtime_component_field_type {
235 (Option $inner_type:ident) => {
236 Option<Tracked<$inner_type>>
237 };
238 (Option $inner_type:ident required) => {
239 Tracked<$inner_type>
240 };
241 (Vec $inner_type:ident) => {
242 Vec<Tracked<$inner_type>>
243 };
244 (Vec $inner_type:ident atLeastOneRequired) => {
245 Vec<Tracked<$inner_type>>
246 };
247 (OptionalAuthSchemeMap $inner_type: ident atLeastOneRequired) => { AuthSchemeMap<Tracked<$inner_type>> };
248}
249macro_rules! empty_builder_value {
255 (Option) => {
256 None
257 };
258 (Vec) => {
259 Vec::new()
260 };
261 (OptionalAuthSchemeMap) => {
262 None
263 };
264}
265
266type OptionalAuthSchemeMap<V> = Option<AuthSchemeMap<V>>;
267type AuthSchemeMap<V> = HashMap<AuthSchemeId, V>;
268
269macro_rules! declare_runtime_components {
301 (fields for $rc_name:ident and $builder_name:ident {
302 $($(#[$option:ident])? $field_name:ident : $outer_type:ident<$inner_type:ident> ,)+
303 }) => {
304 #[derive(Clone, Debug)]
306 pub struct $rc_name {
307 $($field_name: runtime_component_field_type!($outer_type $inner_type $($option)?),)+
308 }
309
310 #[derive(Clone, Debug)]
312 pub struct $builder_name {
313 builder_name: &'static str,
314 $($field_name: $outer_type<Tracked<$inner_type>>,)+
315 }
316 impl $builder_name {
317 pub const fn new(name: &'static str) -> Self {
323 Self {
324 builder_name: name,
325 $($field_name: empty_builder_value!($outer_type),)+
326 }
327 }
328
329 pub fn merge_from(mut self, other: &Self) -> Self {
331 $(merge!($outer_type other.$field_name => self);)+
332 self
333 }
334
335 pub fn build(self) -> Result<$rc_name, BuildError> {
337 let mut rcs = $rc_name {
338 $($field_name: builder_field_value!($outer_type self.$field_name $($option)?),)+
339 };
340 rcs.sort();
341
342 Ok(rcs)
343 }
344 }
345 };
346}
347
348declare_runtime_components! {
349 fields for RuntimeComponents and RuntimeComponentsBuilder {
350 #[required]
351 auth_scheme_option_resolver: Option<SharedAuthSchemeOptionResolver>,
352
353 http_client: Option<SharedHttpClient>,
355
356 #[required]
357 endpoint_resolver: Option<SharedEndpointResolver>,
358
359 #[atLeastOneRequired]
360 auth_schemes: OptionalAuthSchemeMap<SharedAuthScheme>,
361
362 #[required]
363 identity_cache: Option<SharedIdentityCache>,
364
365 #[atLeastOneRequired]
366 identity_resolvers: OptionalAuthSchemeMap<SharedIdentityResolver>,
367
368 interceptors: Vec<SharedInterceptor>,
369
370 retry_classifiers: Vec<SharedRetryClassifier>,
371
372 #[required]
373 retry_strategy: Option<SharedRetryStrategy>,
374
375 time_source: Option<SharedTimeSource>,
376
377 sleep_impl: Option<SharedAsyncSleep>,
378
379 config_validators: Vec<SharedConfigValidator>,
380 }
381}
382
383impl RuntimeComponents {
384 pub fn builder(name: &'static str) -> RuntimeComponentsBuilder {
386 RuntimeComponentsBuilder::new(name)
387 }
388
389 pub fn to_builder(&self) -> RuntimeComponentsBuilder {
391 RuntimeComponentsBuilder::from_runtime_components(
392 self.clone(),
393 "RuntimeComponentsBuilder::from_runtime_components",
394 )
395 }
396
397 pub fn auth_scheme_option_resolver(&self) -> SharedAuthSchemeOptionResolver {
399 self.auth_scheme_option_resolver.value.clone()
400 }
401
402 pub fn http_client(&self) -> Option<SharedHttpClient> {
404 self.http_client.as_ref().map(|s| s.value.clone())
405 }
406
407 pub fn endpoint_resolver(&self) -> SharedEndpointResolver {
409 self.endpoint_resolver.value.clone()
410 }
411
412 pub fn auth_scheme(&self, scheme_id: impl AsRef<AuthSchemeId>) -> Option<SharedAuthScheme> {
414 self.auth_schemes
415 .get(scheme_id.as_ref())
416 .map(|s| s.value.clone())
417 }
418
419 pub fn identity_cache(&self) -> SharedIdentityCache {
421 self.identity_cache.value.clone()
422 }
423
424 pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
426 self.interceptors.iter().map(|s| s.value.clone())
427 }
428
429 pub fn retry_classifiers(&self) -> impl Iterator<Item = SharedRetryClassifier> + '_ {
431 self.retry_classifiers.iter().map(|s| s.value.clone())
432 }
433
434 #[cfg(debug_assertions)]
436 pub(crate) fn retry_classifiers_slice(&self) -> &[Tracked<SharedRetryClassifier>] {
437 self.retry_classifiers.as_slice()
438 }
439
440 pub fn retry_strategy(&self) -> SharedRetryStrategy {
442 self.retry_strategy.value.clone()
443 }
444
445 pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
447 self.sleep_impl.as_ref().map(|s| s.value.clone())
448 }
449
450 pub fn time_source(&self) -> Option<SharedTimeSource> {
452 self.time_source.as_ref().map(|s| s.value.clone())
453 }
454
455 pub fn config_validators(&self) -> impl Iterator<Item = SharedConfigValidator> + '_ {
457 self.config_validators.iter().map(|s| s.value.clone())
458 }
459
460 pub fn validate_final_config(&self, cfg: &ConfigBag) -> Result<(), BoxError> {
464 macro_rules! validate {
465 (Required: $field:expr) => {
466 ValidateConfig::validate_final_config(&$field.value, self, cfg)?;
467 };
468 (Option: $field:expr) => {
469 if let Some(field) = $field.as_ref() {
470 ValidateConfig::validate_final_config(&field.value, self, cfg)?;
471 }
472 };
473 (Vec: $field:expr) => {
474 for entry in $field {
475 ValidateConfig::validate_final_config(&entry.value, self, cfg)?;
476 }
477 };
478 (Map: $field:expr) => {
479 for entry in $field.values() {
480 ValidateConfig::validate_final_config(&entry.value, self, cfg)?;
481 }
482 };
483 }
484
485 for validator in self.config_validators() {
486 validator.validate_final_config(self, cfg)?;
487 }
488
489 validate!(Option: self.http_client);
490 validate!(Required: self.endpoint_resolver);
491 validate!(Map: &self.auth_schemes);
492 validate!(Required: self.identity_cache);
493 validate!(Map: self.identity_resolvers);
494 validate!(Vec: &self.interceptors);
495 validate!(Required: self.retry_strategy);
496 validate!(Vec: &self.retry_classifiers);
497
498 Ok(())
499 }
500
501 fn sort(&mut self) {
502 self.retry_classifiers.sort_by_key(|rc| rc.value.priority());
503 }
504}
505
506impl RuntimeComponentsBuilder {
507 pub fn from_runtime_components(rc: RuntimeComponents, builder_name: &'static str) -> Self {
510 Self {
511 builder_name,
512 auth_scheme_option_resolver: Some(rc.auth_scheme_option_resolver),
513 http_client: rc.http_client,
514 endpoint_resolver: Some(rc.endpoint_resolver),
515 auth_schemes: Some(rc.auth_schemes),
516 identity_cache: Some(rc.identity_cache),
517 identity_resolvers: Some(rc.identity_resolvers),
518 interceptors: rc.interceptors,
519 retry_classifiers: rc.retry_classifiers,
520 retry_strategy: Some(rc.retry_strategy),
521 time_source: rc.time_source,
522 sleep_impl: rc.sleep_impl,
523 config_validators: rc.config_validators,
524 }
525 }
526
527 pub fn auth_scheme_option_resolver(&self) -> Option<SharedAuthSchemeOptionResolver> {
529 self.auth_scheme_option_resolver
530 .as_ref()
531 .map(|s| s.value.clone())
532 }
533
534 pub fn set_auth_scheme_option_resolver(
536 &mut self,
537 auth_scheme_option_resolver: Option<impl ResolveAuthSchemeOptions + 'static>,
538 ) -> &mut Self {
539 self.auth_scheme_option_resolver =
540 self.tracked(auth_scheme_option_resolver.map(IntoShared::into_shared));
541 self
542 }
543
544 pub fn with_auth_scheme_option_resolver(
546 mut self,
547 auth_scheme_option_resolver: Option<impl ResolveAuthSchemeOptions + 'static>,
548 ) -> Self {
549 self.set_auth_scheme_option_resolver(auth_scheme_option_resolver);
550 self
551 }
552
553 pub fn http_client(&self) -> Option<SharedHttpClient> {
555 self.http_client.as_ref().map(|s| s.value.clone())
556 }
557
558 pub fn set_http_client(&mut self, connector: Option<impl HttpClient + 'static>) -> &mut Self {
560 self.http_client = self.tracked(connector.map(IntoShared::into_shared));
561 self
562 }
563
564 pub fn with_http_client(mut self, connector: Option<impl HttpClient + 'static>) -> Self {
566 self.set_http_client(connector);
567 self
568 }
569
570 pub fn endpoint_resolver(&self) -> Option<SharedEndpointResolver> {
572 self.endpoint_resolver.as_ref().map(|s| s.value.clone())
573 }
574
575 pub fn set_endpoint_resolver(
577 &mut self,
578 endpoint_resolver: Option<impl ResolveEndpoint + 'static>,
579 ) -> &mut Self {
580 self.endpoint_resolver =
581 endpoint_resolver.map(|s| Tracked::new(self.builder_name, s.into_shared()));
582 self
583 }
584
585 pub fn with_endpoint_resolver(
587 mut self,
588 endpoint_resolver: Option<impl ResolveEndpoint + 'static>,
589 ) -> Self {
590 self.set_endpoint_resolver(endpoint_resolver);
591 self
592 }
593
594 pub fn auth_schemes(&self) -> impl Iterator<Item = SharedAuthScheme> + '_ {
596 self.auth_schemes
597 .iter()
598 .flat_map(|s| s.values().map(|t| t.value.clone()))
599 }
600
601 pub fn push_auth_scheme(&mut self, auth_scheme: impl AuthScheme + 'static) -> &mut Self {
603 let mut auth_schemes = self.auth_schemes.take().unwrap_or_default();
604 auth_schemes.insert(
605 auth_scheme.scheme_id(),
606 Tracked::new(self.builder_name, auth_scheme.into_shared()),
607 );
608 self.auth_schemes = Some(auth_schemes);
609 self
610 }
611
612 pub fn with_auth_scheme(mut self, auth_scheme: impl AuthScheme + 'static) -> Self {
614 self.push_auth_scheme(auth_scheme);
615 self
616 }
617
618 pub fn identity_cache(&self) -> Option<SharedIdentityCache> {
620 self.identity_cache.as_ref().map(|s| s.value.clone())
621 }
622
623 pub fn set_identity_cache(
625 &mut self,
626 identity_cache: Option<impl ResolveCachedIdentity + 'static>,
627 ) -> &mut Self {
628 self.identity_cache =
629 identity_cache.map(|c| Tracked::new(self.builder_name, c.into_shared()));
630 self
631 }
632
633 pub fn with_identity_cache(
635 mut self,
636 identity_cache: Option<impl ResolveCachedIdentity + 'static>,
637 ) -> Self {
638 self.set_identity_cache(identity_cache);
639 self
640 }
641
642 pub fn identity_resolver(&self, scheme_id: &AuthSchemeId) -> Option<SharedIdentityResolver> {
644 self.identity_resolvers
645 .as_ref()
646 .and_then(|resolvers| resolvers.get(scheme_id))
647 .map(|tracked| tracked.value.clone())
648 }
649
650 pub fn has_identity_resolvers(&self) -> bool {
652 self.identity_resolvers
653 .as_ref()
654 .is_some_and(|resolvers| !resolvers.is_empty())
655 }
656
657 #[deprecated(
660 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."
661 )]
662 pub fn push_identity_resolver(
663 &mut self,
664 scheme_id: AuthSchemeId,
665 identity_resolver: impl ResolveIdentity + 'static,
666 ) -> &mut Self {
667 self.set_identity_resolver(scheme_id, identity_resolver)
668 }
669
670 pub fn set_identity_resolver(
675 &mut self,
676 scheme_id: AuthSchemeId,
677 identity_resolver: impl ResolveIdentity + 'static,
678 ) -> &mut Self {
679 let mut resolvers = self.identity_resolvers.take().unwrap_or_default();
680 resolvers.insert(
681 scheme_id,
682 Tracked::new(self.builder_name, identity_resolver.into_shared()),
683 );
684 self.identity_resolvers = Some(resolvers);
685 self
686 }
687
688 pub fn with_identity_resolver(
690 mut self,
691 scheme_id: AuthSchemeId,
692 identity_resolver: impl ResolveIdentity + 'static,
693 ) -> Self {
694 self.set_identity_resolver(scheme_id, identity_resolver);
695 self
696 }
697
698 pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
700 self.interceptors.iter().map(|s| s.value.clone())
701 }
702
703 pub fn extend_interceptors(
705 &mut self,
706 interceptors: impl Iterator<Item = SharedInterceptor>,
707 ) -> &mut Self {
708 self.interceptors
709 .extend(interceptors.map(|s| Tracked::new(self.builder_name, s)));
710 self
711 }
712
713 pub fn push_interceptor(&mut self, interceptor: impl Intercept + 'static) -> &mut Self {
715 self.interceptors
716 .push(Tracked::new(self.builder_name, interceptor.into_shared()));
717 self
718 }
719
720 pub fn with_interceptor(mut self, interceptor: impl Intercept + 'static) -> Self {
722 self.push_interceptor(interceptor);
723 self
724 }
725
726 pub fn set_interceptors(
728 &mut self,
729 interceptors: impl Iterator<Item = SharedInterceptor>,
730 ) -> &mut Self {
731 self.interceptors.clear();
732 self.interceptors
733 .extend(interceptors.map(|s| Tracked::new(self.builder_name, s)));
734 self
735 }
736
737 pub fn with_interceptors(
739 mut self,
740 interceptors: impl Iterator<Item = SharedInterceptor>,
741 ) -> Self {
742 self.set_interceptors(interceptors);
743 self
744 }
745
746 pub fn retry_classifiers(&self) -> impl Iterator<Item = SharedRetryClassifier> + '_ {
748 self.retry_classifiers.iter().map(|s| s.value.clone())
749 }
750
751 pub fn extend_retry_classifiers(
753 &mut self,
754 retry_classifiers: impl Iterator<Item = SharedRetryClassifier>,
755 ) -> &mut Self {
756 self.retry_classifiers
757 .extend(retry_classifiers.map(|s| Tracked::new(self.builder_name, s)));
758 self
759 }
760
761 pub fn push_retry_classifier(
763 &mut self,
764 retry_classifier: impl ClassifyRetry + 'static,
765 ) -> &mut Self {
766 self.retry_classifiers.push(Tracked::new(
767 self.builder_name,
768 retry_classifier.into_shared(),
769 ));
770 self
771 }
772
773 pub fn with_retry_classifier(mut self, retry_classifier: impl ClassifyRetry + 'static) -> Self {
775 self.push_retry_classifier(retry_classifier);
776 self
777 }
778
779 pub fn set_retry_classifiers(
781 &mut self,
782 retry_classifiers: impl Iterator<Item = SharedRetryClassifier>,
783 ) -> &mut Self {
784 self.retry_classifiers.clear();
785 self.retry_classifiers
786 .extend(retry_classifiers.map(|s| Tracked::new(self.builder_name, s)));
787 self
788 }
789
790 pub fn retry_strategy(&self) -> Option<SharedRetryStrategy> {
792 self.retry_strategy.as_ref().map(|s| s.value.clone())
793 }
794
795 pub fn set_retry_strategy(
797 &mut self,
798 retry_strategy: Option<impl RetryStrategy + 'static>,
799 ) -> &mut Self {
800 self.retry_strategy =
801 retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared()));
802 self
803 }
804
805 pub fn with_retry_strategy(
807 mut self,
808 retry_strategy: Option<impl RetryStrategy + 'static>,
809 ) -> Self {
810 self.retry_strategy =
811 retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared()));
812 self
813 }
814
815 pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
817 self.sleep_impl.as_ref().map(|s| s.value.clone())
818 }
819
820 pub fn set_sleep_impl(&mut self, sleep_impl: Option<SharedAsyncSleep>) -> &mut Self {
822 self.sleep_impl = self.tracked(sleep_impl);
823 self
824 }
825
826 pub fn with_sleep_impl(mut self, sleep_impl: Option<impl AsyncSleep + 'static>) -> Self {
828 self.set_sleep_impl(sleep_impl.map(IntoShared::into_shared));
829 self
830 }
831
832 pub fn time_source(&self) -> Option<SharedTimeSource> {
834 self.time_source.as_ref().map(|s| s.value.clone())
835 }
836
837 pub fn set_time_source(&mut self, time_source: Option<SharedTimeSource>) -> &mut Self {
839 self.time_source = self.tracked(time_source);
840 self
841 }
842
843 pub fn with_time_source(mut self, time_source: Option<impl TimeSource + 'static>) -> Self {
845 self.set_time_source(time_source.map(IntoShared::into_shared));
846 self
847 }
848
849 pub fn config_validators(&self) -> impl Iterator<Item = SharedConfigValidator> + '_ {
851 self.config_validators.iter().map(|s| s.value.clone())
852 }
853
854 pub fn extend_config_validators(
856 &mut self,
857 config_validators: impl Iterator<Item = SharedConfigValidator>,
858 ) -> &mut Self {
859 self.config_validators
860 .extend(config_validators.map(|s| Tracked::new(self.builder_name, s)));
861 self
862 }
863
864 pub fn push_config_validator(
866 &mut self,
867 config_validator: impl ValidateConfig + 'static,
868 ) -> &mut Self {
869 self.config_validators.push(Tracked::new(
870 self.builder_name,
871 config_validator.into_shared(),
872 ));
873 self
874 }
875
876 pub fn with_config_validator(
878 mut self,
879 config_validator: impl ValidateConfig + 'static,
880 ) -> Self {
881 self.push_config_validator(config_validator);
882 self
883 }
884
885 pub fn validate_base_client_config(&self, cfg: &ConfigBag) -> Result<(), BoxError> {
889 macro_rules! validate {
890 ($field:expr) => {
891 #[allow(for_loops_over_fallibles)]
892 for entry in $field {
893 ValidateConfig::validate_base_client_config(&entry.value, self, cfg)?;
894 }
895 };
896 }
897
898 for validator in self.config_validators() {
899 validator.validate_base_client_config(self, cfg)?;
900 }
901 validate!(&self.http_client);
902 validate!(&self.endpoint_resolver);
903 if let Some(auth_schemes) = &self.auth_schemes {
904 validate!(auth_schemes.values())
905 }
906 validate!(&self.identity_cache);
907 if let Some(resolvers) = &self.identity_resolvers {
908 validate!(resolvers.values())
909 }
910 validate!(&self.interceptors);
911 validate!(&self.retry_strategy);
912 Ok(())
913 }
914
915 pub fn into_time_components(mut self) -> TimeComponents {
917 TimeComponents {
918 sleep_impl: self.sleep_impl.take().map(|s| s.value),
919 time_source: self.time_source.take().map(|s| s.value),
920 }
921 }
922
923 fn tracked<T>(&self, v: Option<T>) -> Option<Tracked<T>> {
925 v.map(|v| Tracked::new(self.builder_name, v))
926 }
927}
928
929#[derive(Debug)]
931pub struct TimeComponents {
932 sleep_impl: Option<SharedAsyncSleep>,
933 time_source: Option<SharedTimeSource>,
934}
935
936impl TimeComponents {
937 pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
939 self.sleep_impl.clone()
940 }
941
942 pub fn time_source(&self) -> Option<SharedTimeSource> {
944 self.time_source.clone()
945 }
946}
947
948#[derive(Clone, Debug)]
949#[cfg_attr(test, derive(Eq, PartialEq))]
950pub(crate) struct Tracked<T> {
951 _origin: &'static str,
952 value: T,
953}
954
955impl<T> Tracked<T> {
956 fn new(origin: &'static str, value: T) -> Self {
957 Self {
958 _origin: origin,
959 value,
960 }
961 }
962
963 #[cfg(debug_assertions)]
964 pub(crate) fn value(&self) -> &T {
965 &self.value
966 }
967}
968
969impl RuntimeComponentsBuilder {
970 #[cfg(feature = "test-util")]
972 pub fn for_tests() -> Self {
973 use crate::client::endpoint::{EndpointFuture, EndpointResolverParams};
974 use crate::client::identity::IdentityFuture;
975
976 #[derive(Debug)]
977 struct FakeAuthSchemeOptionResolver;
978 impl ResolveAuthSchemeOptions for FakeAuthSchemeOptionResolver {
979 fn resolve_auth_scheme_options(
980 &self,
981 _: &crate::client::auth::AuthSchemeOptionResolverParams,
982 ) -> Result<std::borrow::Cow<'_, [AuthSchemeId]>, BoxError> {
983 unreachable!("fake auth scheme option resolver must be overridden for this test")
984 }
985 }
986
987 #[derive(Debug)]
988 struct FakeClient;
989 impl HttpClient for FakeClient {
990 fn http_connector(
991 &self,
992 _: &crate::client::http::HttpConnectorSettings,
993 _: &RuntimeComponents,
994 ) -> crate::client::http::SharedHttpConnector {
995 unreachable!("fake client must be overridden for this test")
996 }
997 }
998
999 #[derive(Debug)]
1000 struct FakeEndpointResolver;
1001 impl ResolveEndpoint for FakeEndpointResolver {
1002 fn resolve_endpoint<'a>(&'a self, _: &'a EndpointResolverParams) -> EndpointFuture<'a> {
1003 unreachable!("fake endpoint resolver must be overridden for this test")
1004 }
1005 }
1006
1007 #[derive(Debug)]
1008 struct FakeAuthScheme;
1009 impl AuthScheme for FakeAuthScheme {
1010 fn scheme_id(&self) -> AuthSchemeId {
1011 AuthSchemeId::new("fake")
1012 }
1013
1014 fn identity_resolver(
1015 &self,
1016 _: &dyn GetIdentityResolver,
1017 ) -> Option<SharedIdentityResolver> {
1018 None
1019 }
1020
1021 fn signer(&self) -> &dyn crate::client::auth::Sign {
1022 unreachable!("fake http auth scheme must be overridden for this test")
1023 }
1024 }
1025
1026 #[derive(Debug)]
1027 struct FakeIdentityResolver;
1028 impl ResolveIdentity for FakeIdentityResolver {
1029 fn resolve_identity<'a>(
1030 &'a self,
1031 _: &'a RuntimeComponents,
1032 _: &'a ConfigBag,
1033 ) -> IdentityFuture<'a> {
1034 unreachable!("fake identity resolver must be overridden for this test")
1035 }
1036 }
1037
1038 #[derive(Debug)]
1039 struct FakeRetryStrategy;
1040 impl RetryStrategy for FakeRetryStrategy {
1041 fn should_attempt_initial_request(
1042 &self,
1043 _: &RuntimeComponents,
1044 _: &ConfigBag,
1045 ) -> Result<crate::client::retries::ShouldAttempt, BoxError> {
1046 unreachable!("fake retry strategy must be overridden for this test")
1047 }
1048
1049 fn should_attempt_retry(
1050 &self,
1051 _: &crate::client::interceptors::context::InterceptorContext,
1052 _: &RuntimeComponents,
1053 _: &ConfigBag,
1054 ) -> Result<crate::client::retries::ShouldAttempt, BoxError> {
1055 unreachable!("fake retry strategy must be overridden for this test")
1056 }
1057 }
1058
1059 #[derive(Debug)]
1060 struct FakeTimeSource;
1061 impl TimeSource for FakeTimeSource {
1062 fn now(&self) -> std::time::SystemTime {
1063 unreachable!("fake time source must be overridden for this test")
1064 }
1065 }
1066
1067 #[derive(Debug)]
1068 struct FakeSleep;
1069 impl AsyncSleep for FakeSleep {
1070 fn sleep(&self, _: std::time::Duration) -> aws_smithy_async::rt::sleep::Sleep {
1071 unreachable!("fake sleep must be overridden for this test")
1072 }
1073 }
1074
1075 #[derive(Debug)]
1076 struct FakeIdentityCache;
1077 impl ResolveCachedIdentity for FakeIdentityCache {
1078 fn resolve_cached_identity<'a>(
1079 &'a self,
1080 resolver: SharedIdentityResolver,
1081 components: &'a RuntimeComponents,
1082 config_bag: &'a ConfigBag,
1083 ) -> IdentityFuture<'a> {
1084 IdentityFuture::new(async move {
1085 resolver.resolve_identity(components, config_bag).await
1086 })
1087 }
1088 }
1089
1090 Self::new("aws_smithy_runtime_api::client::runtime_components::RuntimeComponentBuilder::for_tests")
1091 .with_auth_scheme(FakeAuthScheme)
1092 .with_auth_scheme_option_resolver(Some(FakeAuthSchemeOptionResolver))
1093 .with_endpoint_resolver(Some(FakeEndpointResolver))
1094 .with_http_client(Some(FakeClient))
1095 .with_identity_cache(Some(FakeIdentityCache))
1096 .with_identity_resolver(AuthSchemeId::new("fake"), FakeIdentityResolver)
1097 .with_retry_strategy(Some(FakeRetryStrategy))
1098 .with_sleep_impl(Some(SharedAsyncSleep::new(FakeSleep)))
1099 .with_time_source(Some(SharedTimeSource::new(FakeTimeSource)))
1100 }
1101}
1102
1103#[derive(Debug)]
1105pub struct BuildError(&'static str);
1106
1107impl std::error::Error for BuildError {}
1108
1109impl fmt::Display for BuildError {
1110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1111 write!(f, "{}", self.0)
1112 }
1113}
1114
1115pub trait GetIdentityResolver: Send + Sync {
1120 fn identity_resolver(&self, scheme_id: AuthSchemeId) -> Option<SharedIdentityResolver>;
1122}
1123
1124impl GetIdentityResolver for RuntimeComponents {
1125 fn identity_resolver(&self, scheme_id: AuthSchemeId) -> Option<SharedIdentityResolver> {
1126 self.identity_resolvers
1127 .get(&scheme_id)
1128 .map(|s| s.value.clone())
1129 }
1130}
1131
1132#[cfg(all(test, feature = "test-util"))]
1133mod tests {
1134 use super::{BuildError, RuntimeComponentsBuilder, Tracked};
1135 use crate::client::runtime_components::ValidateConfig;
1136
1137 #[derive(Clone, Debug, Eq, PartialEq)]
1138 struct TestComponent(String);
1139 impl ValidateConfig for TestComponent {}
1140 impl From<&'static str> for TestComponent {
1141 fn from(value: &'static str) -> Self {
1142 TestComponent(value.into())
1143 }
1144 }
1145
1146 #[test]
1147 #[allow(unreachable_pub)]
1148 #[allow(dead_code)]
1149 fn the_builders_should_merge() {
1150 declare_runtime_components! {
1151 fields for TestRc and TestRcBuilder {
1152 #[required]
1153 some_required_component: Option<TestComponent>,
1154
1155 some_optional_component: Option<TestComponent>,
1156
1157 #[atLeastOneRequired]
1158 some_required_vec: Vec<TestComponent>,
1159
1160 some_optional_vec: Vec<TestComponent>,
1161 }
1162 }
1163
1164 impl TestRc {
1165 fn sort(&mut self) {}
1166 }
1167
1168 let builder1 = TestRcBuilder {
1169 builder_name: "builder1",
1170 some_required_component: Some(Tracked::new("builder1", "override_me".into())),
1171 some_optional_component: Some(Tracked::new("builder1", "override_me optional".into())),
1172 some_required_vec: vec![Tracked::new("builder1", "first".into())],
1173 some_optional_vec: vec![Tracked::new("builder1", "first optional".into())],
1174 };
1175 let builder2 = TestRcBuilder {
1176 builder_name: "builder2",
1177 some_required_component: Some(Tracked::new("builder2", "override_me_too".into())),
1178 some_optional_component: Some(Tracked::new(
1179 "builder2",
1180 "override_me_too optional".into(),
1181 )),
1182 some_required_vec: vec![Tracked::new("builder2", "second".into())],
1183 some_optional_vec: vec![Tracked::new("builder2", "second optional".into())],
1184 };
1185 let builder3 = TestRcBuilder {
1186 builder_name: "builder3",
1187 some_required_component: Some(Tracked::new("builder3", "correct".into())),
1188 some_optional_component: Some(Tracked::new("builder3", "correct optional".into())),
1189 some_required_vec: vec![Tracked::new("builder3", "third".into())],
1190 some_optional_vec: vec![Tracked::new("builder3", "third optional".into())],
1191 };
1192 let rc = TestRcBuilder::new("root")
1193 .merge_from(&builder1)
1194 .merge_from(&builder2)
1195 .merge_from(&builder3)
1196 .build()
1197 .expect("success");
1198 assert_eq!(
1199 Tracked::new("builder3", TestComponent::from("correct")),
1200 rc.some_required_component
1201 );
1202 assert_eq!(
1203 Some(Tracked::new(
1204 "builder3",
1205 TestComponent::from("correct optional")
1206 )),
1207 rc.some_optional_component
1208 );
1209 assert_eq!(
1210 vec![
1211 Tracked::new("builder1", TestComponent::from("first")),
1212 Tracked::new("builder2", TestComponent::from("second")),
1213 Tracked::new("builder3", TestComponent::from("third"))
1214 ],
1215 rc.some_required_vec
1216 );
1217 assert_eq!(
1218 vec![
1219 Tracked::new("builder1", TestComponent::from("first optional")),
1220 Tracked::new("builder2", TestComponent::from("second optional")),
1221 Tracked::new("builder3", TestComponent::from("third optional"))
1222 ],
1223 rc.some_optional_vec
1224 );
1225 }
1226
1227 #[test]
1228 #[allow(unreachable_pub)]
1229 #[allow(dead_code)]
1230 #[should_panic(expected = "the `_some_component` runtime component is required")]
1231 fn require_field_singular() {
1232 declare_runtime_components! {
1233 fields for TestRc and TestRcBuilder {
1234 #[required]
1235 _some_component: Option<TestComponent>,
1236 }
1237 }
1238
1239 impl TestRc {
1240 fn sort(&mut self) {}
1241 }
1242
1243 let rc = TestRcBuilder::new("test").build().unwrap();
1244
1245 let _: Tracked<TestComponent> = rc._some_component;
1247 }
1248
1249 #[test]
1250 #[allow(unreachable_pub)]
1251 #[allow(dead_code)]
1252 #[should_panic(expected = "at least one `_some_vec` runtime component is required")]
1253 fn require_field_plural() {
1254 declare_runtime_components! {
1255 fields for TestRc and TestRcBuilder {
1256 #[atLeastOneRequired]
1257 _some_vec: Vec<TestComponent>,
1258 }
1259 }
1260
1261 impl TestRc {
1262 fn sort(&mut self) {}
1263 }
1264
1265 let rc = TestRcBuilder::new("test").build().unwrap();
1266
1267 let _: Vec<Tracked<TestComponent>> = rc._some_vec;
1269 }
1270
1271 #[test]
1272 #[allow(unreachable_pub)]
1273 #[allow(dead_code)]
1274 fn optional_fields_dont_panic() {
1275 declare_runtime_components! {
1276 fields for TestRc and TestRcBuilder {
1277 _some_optional_component: Option<TestComponent>,
1278 _some_optional_vec: Vec<TestComponent>,
1279 }
1280 }
1281
1282 impl TestRc {
1283 fn sort(&mut self) {}
1284 }
1285
1286 let rc = TestRcBuilder::new("test").build().unwrap();
1287
1288 let _: Option<Tracked<TestComponent>> = rc._some_optional_component;
1290 let _: Vec<Tracked<TestComponent>> = rc._some_optional_vec;
1291 }
1292
1293 #[test]
1294 fn building_test_builder_should_not_panic() {
1295 let _ = RuntimeComponentsBuilder::for_tests().build(); }
1297
1298 #[test]
1299 fn set_identity_resolver_should_replace_existing_resolver_for_given_auth_scheme() {
1300 use crate::client::auth::AuthSchemeId;
1301 use crate::client::identity::{Identity, IdentityFuture, ResolveIdentity};
1302 use crate::client::runtime_components::{GetIdentityResolver, RuntimeComponents};
1303 use aws_smithy_types::config_bag::ConfigBag;
1304 use tokio::runtime::Runtime;
1305
1306 #[derive(Debug)]
1307 struct AnotherFakeIdentityResolver;
1308 impl ResolveIdentity for AnotherFakeIdentityResolver {
1309 fn resolve_identity<'a>(
1310 &'a self,
1311 _: &'a RuntimeComponents,
1312 _: &'a ConfigBag,
1313 ) -> IdentityFuture<'a> {
1314 IdentityFuture::ready(Ok(Identity::new("doesn't matter", None)))
1315 }
1316 }
1317
1318 let rc = RuntimeComponentsBuilder::for_tests()
1321 .with_identity_resolver(AuthSchemeId::new("fake"), AnotherFakeIdentityResolver)
1322 .build()
1323 .expect("should build RuntimeComponents");
1324
1325 let resolver = rc
1326 .identity_resolver(AuthSchemeId::new("fake"))
1327 .expect("identity resolver should be found");
1328
1329 let identity = Runtime::new().unwrap().block_on(async {
1330 resolver
1331 .resolve_identity(&rc, &ConfigBag::base())
1332 .await
1333 .expect("identity should be resolved")
1334 });
1335
1336 assert_eq!(Some(&"doesn't matter"), identity.data::<&str>());
1337 }
1338}