aws_smithy_runtime_api/client/
runtime_components.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Runtime components used to make a request and handle a response.
7//!
8//! Runtime components are trait implementations that are _always_ used by the orchestrator.
9//! There are other trait implementations that can be configured for a client, but if they
10//! aren't directly and always used by the orchestrator, then they are placed in the
11//! [`ConfigBag`] instead of in [`RuntimeComponents`].
12
13use 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    /// Validates client configuration.
42    ///
43    /// This trait can be used to validate that certain required components or config values
44    /// are available, and provide an error with helpful instructions if they are not.
45    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/// A client config validator.
85#[derive(Clone, Debug)]
86pub struct SharedConfigValidator {
87    inner: ValidatorInner,
88}
89
90impl SharedConfigValidator {
91    /// Creates a new shared config validator.
92    pub(crate) fn new(validator: impl ValidateConfig + 'static) -> Self {
93        Self {
94            inner: ValidatorInner::Shared(Arc::new(validator) as _),
95        }
96    }
97
98    /// Creates a base client validator from a function.
99    ///
100    /// A base client validator gets called upon client construction. The full
101    /// config may not be available at this time (hence why it has
102    /// [`RuntimeComponentsBuilder`] as an argument rather than [`RuntimeComponents`]).
103    /// Any error returned from the validator function will become a panic in the
104    /// client constructor.
105    ///
106    /// # Examples
107    ///
108    /// Creating a validator function:
109    /// ```no_run
110    /// use aws_smithy_runtime_api::box_error::BoxError;
111    /// use aws_smithy_runtime_api::client::runtime_components::{
112    ///     RuntimeComponentsBuilder,
113    ///     SharedConfigValidator
114    /// };
115    /// use aws_smithy_types::config_bag::ConfigBag;
116    ///
117    /// fn my_validation(
118    ///     components: &RuntimeComponentsBuilder,
119    ///     config: &ConfigBag
120    /// ) -> Result<(), BoxError> {
121    ///     if components.sleep_impl().is_none() {
122    ///         return Err("I need a sleep_impl!".into());
123    ///     }
124    ///     Ok(())
125    /// }
126    ///
127    /// let validator = SharedConfigValidator::base_client_config_fn(my_validation);
128    /// ```
129    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
168/// Internal to `declare_runtime_components!`.
169///
170/// Merges a field from one builder into another.
171macro_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}
188/// Internal to `declare_runtime_components!`.
189///
190/// This is used when creating the builder's `build` method
191/// to populate each individual field value. The `required`/`atLeastOneRequired`
192/// validations are performed here.
193macro_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}
230/// Internal to `declare_runtime_components!`.
231///
232/// Converts the field type from `Option<T>` or `Vec<T>` into `Option<Tracked<T>>` or `Vec<Tracked<T>>` respectively.
233/// Also removes the `Option` wrapper for required fields in the non-builder struct.
234macro_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}
249/// Internal to `declare_runtime_components!`.
250///
251/// Converts an `$outer_type` into an empty instantiation for that type.
252/// This is needed since `Default::default()` can't be used in a `const` function,
253/// and `RuntimeComponentsBuilder::new()` is `const`.
254macro_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
269/// Macro to define the structs for both `RuntimeComponents` and `RuntimeComponentsBuilder`.
270///
271/// This is a macro in order to keep the fields consistent between the two, and to automatically
272/// update the `merge_from` and `build` methods when new components are added.
273///
274/// It also facilitates unit testing since the overall mechanism can be unit tested with different
275/// fields that are easy to check in tests (testing with real components makes it hard
276/// to tell that the correct component was selected when merging builders).
277///
278/// # Example usage
279///
280/// The two identifiers after "fields for" become the names of the struct and builder respectively.
281/// Following that, all the fields are specified. Fields MUST be wrapped in `Option` or `Vec`.
282/// To make a field required in the non-builder struct, add `#[required]` for `Option` fields, or
283/// `#[atLeastOneRequired]` for `Vec` fields.
284///
285/// ```no_compile
286/// declare_runtime_components! {
287///     fields for TestRc and TestRcBuilder {
288///         some_optional_string: Option<String>,
289///
290///         some_optional_vec: Vec<String>,
291///
292///         #[required]
293///         some_required_string: Option<String>,
294///
295///         #[atLeastOneRequired]
296///         some_required_vec: Vec<String>,
297///     }
298/// }
299/// ```
300macro_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        /// Components that can only be set in runtime plugins that the orchestrator uses directly to call an operation.
305        #[derive(Clone, Debug)]
306        pub struct $rc_name {
307            $($field_name: runtime_component_field_type!($outer_type $inner_type $($option)?),)+
308        }
309
310        /// Builder for [`RuntimeComponents`].
311        #[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            /// Creates a new builder.
318            ///
319            /// Since multiple builders are merged together to make the final [`RuntimeComponents`],
320            /// all components added by this builder are associated with the given `name` so that
321            /// the origin of a component can be easily found when debugging.
322            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            /// Merge in components from another builder.
330            pub fn merge_from(mut self, other: &Self) -> Self {
331                $(merge!($outer_type other.$field_name => self);)+
332                self
333            }
334
335            /// Builds [`RuntimeComponents`] from this builder.
336            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        // A connector is not required since a client could technically only be used for presigning
354        http_client: Option<SharedHttpClient>,
355
356        #[required]
357        endpoint_resolver: Option<SharedEndpointResolver>,
358
359        #[atLeastOneRequired]
360        auth_schemes: Vec<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    /// Returns a builder for runtime components.
385    pub fn builder(name: &'static str) -> RuntimeComponentsBuilder {
386        RuntimeComponentsBuilder::new(name)
387    }
388
389    /// Clones and converts this [`RuntimeComponents`] into a [`RuntimeComponentsBuilder`].
390    pub fn to_builder(&self) -> RuntimeComponentsBuilder {
391        RuntimeComponentsBuilder::from_runtime_components(
392            self.clone(),
393            "RuntimeComponentsBuilder::from_runtime_components",
394        )
395    }
396
397    /// Returns the auth scheme option resolver.
398    pub fn auth_scheme_option_resolver(&self) -> SharedAuthSchemeOptionResolver {
399        self.auth_scheme_option_resolver.value.clone()
400    }
401
402    /// Returns the HTTP client.
403    pub fn http_client(&self) -> Option<SharedHttpClient> {
404        self.http_client.as_ref().map(|s| s.value.clone())
405    }
406
407    /// Returns the endpoint resolver.
408    pub fn endpoint_resolver(&self) -> SharedEndpointResolver {
409        self.endpoint_resolver.value.clone()
410    }
411
412    /// Returns the requested auth scheme if it is set.
413    pub fn auth_scheme(&self, scheme_id: AuthSchemeId) -> Option<SharedAuthScheme> {
414        self.auth_schemes
415            .iter()
416            .find(|s| s.value.scheme_id() == scheme_id)
417            .map(|s| s.value.clone())
418    }
419
420    /// Returns the identity cache.
421    pub fn identity_cache(&self) -> SharedIdentityCache {
422        self.identity_cache.value.clone()
423    }
424
425    /// Returns an iterator over the interceptors.
426    pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
427        self.interceptors.iter().map(|s| s.value.clone())
428    }
429
430    /// Returns an iterator over the retry classifiers.
431    pub fn retry_classifiers(&self) -> impl Iterator<Item = SharedRetryClassifier> + '_ {
432        self.retry_classifiers.iter().map(|s| s.value.clone())
433    }
434
435    // Needed for `impl ValidateConfig for SharedRetryClassifier {`
436    #[cfg(debug_assertions)]
437    pub(crate) fn retry_classifiers_slice(&self) -> &[Tracked<SharedRetryClassifier>] {
438        self.retry_classifiers.as_slice()
439    }
440
441    /// Returns the retry strategy.
442    pub fn retry_strategy(&self) -> SharedRetryStrategy {
443        self.retry_strategy.value.clone()
444    }
445
446    /// Returns the async sleep implementation.
447    pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
448        self.sleep_impl.as_ref().map(|s| s.value.clone())
449    }
450
451    /// Returns the time source.
452    pub fn time_source(&self) -> Option<SharedTimeSource> {
453        self.time_source.as_ref().map(|s| s.value.clone())
454    }
455
456    /// Returns the config validators.
457    pub fn config_validators(&self) -> impl Iterator<Item = SharedConfigValidator> + '_ {
458        self.config_validators.iter().map(|s| s.value.clone())
459    }
460
461    /// Validate the final client configuration.
462    ///
463    /// This is intended to be called internally by the client.
464    pub fn validate_final_config(&self, cfg: &ConfigBag) -> Result<(), BoxError> {
465        macro_rules! validate {
466            (Required: $field:expr) => {
467                ValidateConfig::validate_final_config(&$field.value, self, cfg)?;
468            };
469            (Option: $field:expr) => {
470                if let Some(field) = $field.as_ref() {
471                    ValidateConfig::validate_final_config(&field.value, self, cfg)?;
472                }
473            };
474            (Vec: $field:expr) => {
475                for entry in $field {
476                    ValidateConfig::validate_final_config(&entry.value, self, cfg)?;
477                }
478            };
479            (Map: $field:expr) => {
480                for entry in $field.values() {
481                    ValidateConfig::validate_final_config(&entry.value, self, cfg)?;
482                }
483            };
484        }
485
486        for validator in self.config_validators() {
487            validator.validate_final_config(self, cfg)?;
488        }
489
490        validate!(Option: self.http_client);
491        validate!(Required: self.endpoint_resolver);
492        validate!(Vec: &self.auth_schemes);
493        validate!(Required: self.identity_cache);
494        validate!(Map: self.identity_resolvers);
495        validate!(Vec: &self.interceptors);
496        validate!(Required: self.retry_strategy);
497        validate!(Vec: &self.retry_classifiers);
498
499        Ok(())
500    }
501
502    fn sort(&mut self) {
503        self.retry_classifiers.sort_by_key(|rc| rc.value.priority());
504    }
505}
506
507impl RuntimeComponentsBuilder {
508    /// Creates a new [`RuntimeComponentsBuilder`], inheriting all fields from the given
509    /// [`RuntimeComponents`].
510    pub fn from_runtime_components(rc: RuntimeComponents, builder_name: &'static str) -> Self {
511        Self {
512            builder_name,
513            auth_scheme_option_resolver: Some(rc.auth_scheme_option_resolver),
514            http_client: rc.http_client,
515            endpoint_resolver: Some(rc.endpoint_resolver),
516            auth_schemes: rc.auth_schemes,
517            identity_cache: Some(rc.identity_cache),
518            identity_resolvers: Some(rc.identity_resolvers),
519            interceptors: rc.interceptors,
520            retry_classifiers: rc.retry_classifiers,
521            retry_strategy: Some(rc.retry_strategy),
522            time_source: rc.time_source,
523            sleep_impl: rc.sleep_impl,
524            config_validators: rc.config_validators,
525        }
526    }
527
528    /// Returns the auth scheme option resolver.
529    pub fn auth_scheme_option_resolver(&self) -> Option<SharedAuthSchemeOptionResolver> {
530        self.auth_scheme_option_resolver
531            .as_ref()
532            .map(|s| s.value.clone())
533    }
534
535    /// Sets the auth scheme option resolver.
536    pub fn set_auth_scheme_option_resolver(
537        &mut self,
538        auth_scheme_option_resolver: Option<impl ResolveAuthSchemeOptions + 'static>,
539    ) -> &mut Self {
540        self.auth_scheme_option_resolver =
541            self.tracked(auth_scheme_option_resolver.map(IntoShared::into_shared));
542        self
543    }
544
545    /// Sets the auth scheme option resolver.
546    pub fn with_auth_scheme_option_resolver(
547        mut self,
548        auth_scheme_option_resolver: Option<impl ResolveAuthSchemeOptions + 'static>,
549    ) -> Self {
550        self.set_auth_scheme_option_resolver(auth_scheme_option_resolver);
551        self
552    }
553
554    /// Returns the HTTP client.
555    pub fn http_client(&self) -> Option<SharedHttpClient> {
556        self.http_client.as_ref().map(|s| s.value.clone())
557    }
558
559    /// Sets the HTTP client.
560    pub fn set_http_client(&mut self, connector: Option<impl HttpClient + 'static>) -> &mut Self {
561        self.http_client = self.tracked(connector.map(IntoShared::into_shared));
562        self
563    }
564
565    /// Sets the HTTP client.
566    pub fn with_http_client(mut self, connector: Option<impl HttpClient + 'static>) -> Self {
567        self.set_http_client(connector);
568        self
569    }
570
571    /// Returns the endpoint resolver.
572    pub fn endpoint_resolver(&self) -> Option<SharedEndpointResolver> {
573        self.endpoint_resolver.as_ref().map(|s| s.value.clone())
574    }
575
576    /// Sets the endpoint resolver.
577    pub fn set_endpoint_resolver(
578        &mut self,
579        endpoint_resolver: Option<impl ResolveEndpoint + 'static>,
580    ) -> &mut Self {
581        self.endpoint_resolver =
582            endpoint_resolver.map(|s| Tracked::new(self.builder_name, s.into_shared()));
583        self
584    }
585
586    /// Sets the endpoint resolver.
587    pub fn with_endpoint_resolver(
588        mut self,
589        endpoint_resolver: Option<impl ResolveEndpoint + 'static>,
590    ) -> Self {
591        self.set_endpoint_resolver(endpoint_resolver);
592        self
593    }
594
595    /// Returns the auth schemes.
596    pub fn auth_schemes(&self) -> impl Iterator<Item = SharedAuthScheme> + '_ {
597        self.auth_schemes.iter().map(|s| s.value.clone())
598    }
599
600    /// Adds an auth scheme.
601    pub fn push_auth_scheme(&mut self, auth_scheme: impl AuthScheme + 'static) -> &mut Self {
602        self.auth_schemes
603            .push(Tracked::new(self.builder_name, auth_scheme.into_shared()));
604        self
605    }
606
607    /// Adds an auth scheme.
608    pub fn with_auth_scheme(mut self, auth_scheme: impl AuthScheme + 'static) -> Self {
609        self.push_auth_scheme(auth_scheme);
610        self
611    }
612
613    /// Returns the identity cache.
614    pub fn identity_cache(&self) -> Option<SharedIdentityCache> {
615        self.identity_cache.as_ref().map(|s| s.value.clone())
616    }
617
618    /// Sets the identity cache.
619    pub fn set_identity_cache(
620        &mut self,
621        identity_cache: Option<impl ResolveCachedIdentity + 'static>,
622    ) -> &mut Self {
623        self.identity_cache =
624            identity_cache.map(|c| Tracked::new(self.builder_name, c.into_shared()));
625        self
626    }
627
628    /// Sets the identity cache.
629    pub fn with_identity_cache(
630        mut self,
631        identity_cache: Option<impl ResolveCachedIdentity + 'static>,
632    ) -> Self {
633        self.set_identity_cache(identity_cache);
634        self
635    }
636
637    /// This method is broken since it does not replace an existing identity resolver of the given auth scheme ID.
638    /// Use `set_identity_resolver` instead.
639    #[deprecated(
640        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."
641    )]
642    pub fn push_identity_resolver(
643        &mut self,
644        scheme_id: AuthSchemeId,
645        identity_resolver: impl ResolveIdentity + 'static,
646    ) -> &mut Self {
647        self.set_identity_resolver(scheme_id, identity_resolver)
648    }
649
650    /// Sets the identity resolver for a given `scheme_id`.
651    ///
652    /// If there is already an identity resolver for that `scheme_id`, this method will replace
653    /// the existing one with the passed-in `identity_resolver`.
654    pub fn set_identity_resolver(
655        &mut self,
656        scheme_id: AuthSchemeId,
657        identity_resolver: impl ResolveIdentity + 'static,
658    ) -> &mut Self {
659        let mut resolvers = self.identity_resolvers.take().unwrap_or_default();
660        resolvers.insert(
661            scheme_id,
662            Tracked::new(self.builder_name, identity_resolver.into_shared()),
663        );
664        self.identity_resolvers = Some(resolvers);
665        self
666    }
667
668    /// Adds an identity resolver.
669    pub fn with_identity_resolver(
670        mut self,
671        scheme_id: AuthSchemeId,
672        identity_resolver: impl ResolveIdentity + 'static,
673    ) -> Self {
674        self.set_identity_resolver(scheme_id, identity_resolver);
675        self
676    }
677
678    /// Returns the interceptors.
679    pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
680        self.interceptors.iter().map(|s| s.value.clone())
681    }
682
683    /// Adds all the given interceptors.
684    pub fn extend_interceptors(
685        &mut self,
686        interceptors: impl Iterator<Item = SharedInterceptor>,
687    ) -> &mut Self {
688        self.interceptors
689            .extend(interceptors.map(|s| Tracked::new(self.builder_name, s)));
690        self
691    }
692
693    /// Adds an interceptor.
694    pub fn push_interceptor(&mut self, interceptor: impl Intercept + 'static) -> &mut Self {
695        self.interceptors
696            .push(Tracked::new(self.builder_name, interceptor.into_shared()));
697        self
698    }
699
700    /// Adds an interceptor.
701    pub fn with_interceptor(mut self, interceptor: impl Intercept + 'static) -> Self {
702        self.push_interceptor(interceptor);
703        self
704    }
705
706    /// Directly sets the interceptors and clears out any that were previously pushed.
707    pub fn set_interceptors(
708        &mut self,
709        interceptors: impl Iterator<Item = SharedInterceptor>,
710    ) -> &mut Self {
711        self.interceptors.clear();
712        self.interceptors
713            .extend(interceptors.map(|s| Tracked::new(self.builder_name, s)));
714        self
715    }
716
717    /// Directly sets the interceptors and clears out any that were previously pushed.
718    pub fn with_interceptors(
719        mut self,
720        interceptors: impl Iterator<Item = SharedInterceptor>,
721    ) -> Self {
722        self.set_interceptors(interceptors);
723        self
724    }
725
726    /// Returns the retry classifiers.
727    pub fn retry_classifiers(&self) -> impl Iterator<Item = SharedRetryClassifier> + '_ {
728        self.retry_classifiers.iter().map(|s| s.value.clone())
729    }
730
731    /// Adds all the given retry classifiers.
732    pub fn extend_retry_classifiers(
733        &mut self,
734        retry_classifiers: impl Iterator<Item = SharedRetryClassifier>,
735    ) -> &mut Self {
736        self.retry_classifiers
737            .extend(retry_classifiers.map(|s| Tracked::new(self.builder_name, s)));
738        self
739    }
740
741    /// Adds a retry_classifier.
742    pub fn push_retry_classifier(
743        &mut self,
744        retry_classifier: impl ClassifyRetry + 'static,
745    ) -> &mut Self {
746        self.retry_classifiers.push(Tracked::new(
747            self.builder_name,
748            retry_classifier.into_shared(),
749        ));
750        self
751    }
752
753    /// Adds a retry_classifier.
754    pub fn with_retry_classifier(mut self, retry_classifier: impl ClassifyRetry + 'static) -> Self {
755        self.push_retry_classifier(retry_classifier);
756        self
757    }
758
759    /// Directly sets the retry_classifiers and clears out any that were previously pushed.
760    pub fn set_retry_classifiers(
761        &mut self,
762        retry_classifiers: impl Iterator<Item = SharedRetryClassifier>,
763    ) -> &mut Self {
764        self.retry_classifiers.clear();
765        self.retry_classifiers
766            .extend(retry_classifiers.map(|s| Tracked::new(self.builder_name, s)));
767        self
768    }
769
770    /// Returns the retry strategy.
771    pub fn retry_strategy(&self) -> Option<SharedRetryStrategy> {
772        self.retry_strategy.as_ref().map(|s| s.value.clone())
773    }
774
775    /// Sets the retry strategy.
776    pub fn set_retry_strategy(
777        &mut self,
778        retry_strategy: Option<impl RetryStrategy + 'static>,
779    ) -> &mut Self {
780        self.retry_strategy =
781            retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared()));
782        self
783    }
784
785    /// Sets the retry strategy.
786    pub fn with_retry_strategy(
787        mut self,
788        retry_strategy: Option<impl RetryStrategy + 'static>,
789    ) -> Self {
790        self.retry_strategy =
791            retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared()));
792        self
793    }
794
795    /// Returns the async sleep implementation.
796    pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
797        self.sleep_impl.as_ref().map(|s| s.value.clone())
798    }
799
800    /// Sets the async sleep implementation.
801    pub fn set_sleep_impl(&mut self, sleep_impl: Option<SharedAsyncSleep>) -> &mut Self {
802        self.sleep_impl = self.tracked(sleep_impl);
803        self
804    }
805
806    /// Sets the async sleep implementation.
807    pub fn with_sleep_impl(mut self, sleep_impl: Option<impl AsyncSleep + 'static>) -> Self {
808        self.set_sleep_impl(sleep_impl.map(IntoShared::into_shared));
809        self
810    }
811
812    /// Returns the time source.
813    pub fn time_source(&self) -> Option<SharedTimeSource> {
814        self.time_source.as_ref().map(|s| s.value.clone())
815    }
816
817    /// Sets the time source.
818    pub fn set_time_source(&mut self, time_source: Option<SharedTimeSource>) -> &mut Self {
819        self.time_source = self.tracked(time_source);
820        self
821    }
822
823    /// Sets the time source.
824    pub fn with_time_source(mut self, time_source: Option<impl TimeSource + 'static>) -> Self {
825        self.set_time_source(time_source.map(IntoShared::into_shared));
826        self
827    }
828
829    /// Returns the config validators.
830    pub fn config_validators(&self) -> impl Iterator<Item = SharedConfigValidator> + '_ {
831        self.config_validators.iter().map(|s| s.value.clone())
832    }
833
834    /// Adds all the given config validators.
835    pub fn extend_config_validators(
836        &mut self,
837        config_validators: impl Iterator<Item = SharedConfigValidator>,
838    ) -> &mut Self {
839        self.config_validators
840            .extend(config_validators.map(|s| Tracked::new(self.builder_name, s)));
841        self
842    }
843
844    /// Adds a config validator.
845    pub fn push_config_validator(
846        &mut self,
847        config_validator: impl ValidateConfig + 'static,
848    ) -> &mut Self {
849        self.config_validators.push(Tracked::new(
850            self.builder_name,
851            config_validator.into_shared(),
852        ));
853        self
854    }
855
856    /// Adds a config validator.
857    pub fn with_config_validator(
858        mut self,
859        config_validator: impl ValidateConfig + 'static,
860    ) -> Self {
861        self.push_config_validator(config_validator);
862        self
863    }
864
865    /// Validate the base client configuration.
866    ///
867    /// This is intended to be called internally by the client.
868    pub fn validate_base_client_config(&self, cfg: &ConfigBag) -> Result<(), BoxError> {
869        macro_rules! validate {
870            ($field:expr) => {
871                #[allow(for_loops_over_fallibles)]
872                for entry in $field {
873                    ValidateConfig::validate_base_client_config(&entry.value, self, cfg)?;
874                }
875            };
876        }
877
878        for validator in self.config_validators() {
879            validator.validate_base_client_config(self, cfg)?;
880        }
881        validate!(&self.http_client);
882        validate!(&self.endpoint_resolver);
883        validate!(&self.auth_schemes);
884        validate!(&self.identity_cache);
885        if let Some(resolvers) = &self.identity_resolvers {
886            validate!(resolvers.values())
887        }
888        validate!(&self.interceptors);
889        validate!(&self.retry_strategy);
890        Ok(())
891    }
892
893    /// Converts this builder into [`TimeComponents`].
894    pub fn into_time_components(mut self) -> TimeComponents {
895        TimeComponents {
896            sleep_impl: self.sleep_impl.take().map(|s| s.value),
897            time_source: self.time_source.take().map(|s| s.value),
898        }
899    }
900
901    /// Wraps `v` in tracking associated with this builder
902    fn tracked<T>(&self, v: Option<T>) -> Option<Tracked<T>> {
903        v.map(|v| Tracked::new(self.builder_name, v))
904    }
905}
906
907/// Time-related subset of components that can be extracted directly from [`RuntimeComponentsBuilder`] prior to validation.
908#[derive(Debug)]
909pub struct TimeComponents {
910    sleep_impl: Option<SharedAsyncSleep>,
911    time_source: Option<SharedTimeSource>,
912}
913
914impl TimeComponents {
915    /// Returns the async sleep implementation if one is available.
916    pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
917        self.sleep_impl.clone()
918    }
919
920    /// Returns the time source if one is available.
921    pub fn time_source(&self) -> Option<SharedTimeSource> {
922        self.time_source.clone()
923    }
924}
925
926#[derive(Clone, Debug)]
927#[cfg_attr(test, derive(Eq, PartialEq))]
928pub(crate) struct Tracked<T> {
929    _origin: &'static str,
930    value: T,
931}
932
933impl<T> Tracked<T> {
934    fn new(origin: &'static str, value: T) -> Self {
935        Self {
936            _origin: origin,
937            value,
938        }
939    }
940
941    #[cfg(debug_assertions)]
942    pub(crate) fn value(&self) -> &T {
943        &self.value
944    }
945}
946
947impl RuntimeComponentsBuilder {
948    /// Creates a runtime components builder with all the required components filled in with fake (panicking) implementations.
949    #[cfg(feature = "test-util")]
950    pub fn for_tests() -> Self {
951        use crate::client::endpoint::{EndpointFuture, EndpointResolverParams};
952        use crate::client::identity::IdentityFuture;
953
954        #[derive(Debug)]
955        struct FakeAuthSchemeOptionResolver;
956        impl ResolveAuthSchemeOptions for FakeAuthSchemeOptionResolver {
957            fn resolve_auth_scheme_options(
958                &self,
959                _: &crate::client::auth::AuthSchemeOptionResolverParams,
960            ) -> Result<std::borrow::Cow<'_, [AuthSchemeId]>, BoxError> {
961                unreachable!("fake auth scheme option resolver must be overridden for this test")
962            }
963        }
964
965        #[derive(Debug)]
966        struct FakeClient;
967        impl HttpClient for FakeClient {
968            fn http_connector(
969                &self,
970                _: &crate::client::http::HttpConnectorSettings,
971                _: &RuntimeComponents,
972            ) -> crate::client::http::SharedHttpConnector {
973                unreachable!("fake client must be overridden for this test")
974            }
975        }
976
977        #[derive(Debug)]
978        struct FakeEndpointResolver;
979        impl ResolveEndpoint for FakeEndpointResolver {
980            fn resolve_endpoint<'a>(&'a self, _: &'a EndpointResolverParams) -> EndpointFuture<'a> {
981                unreachable!("fake endpoint resolver must be overridden for this test")
982            }
983        }
984
985        #[derive(Debug)]
986        struct FakeAuthScheme;
987        impl AuthScheme for FakeAuthScheme {
988            fn scheme_id(&self) -> AuthSchemeId {
989                AuthSchemeId::new("fake")
990            }
991
992            fn identity_resolver(
993                &self,
994                _: &dyn GetIdentityResolver,
995            ) -> Option<SharedIdentityResolver> {
996                None
997            }
998
999            fn signer(&self) -> &dyn crate::client::auth::Sign {
1000                unreachable!("fake http auth scheme must be overridden for this test")
1001            }
1002        }
1003
1004        #[derive(Debug)]
1005        struct FakeIdentityResolver;
1006        impl ResolveIdentity for FakeIdentityResolver {
1007            fn resolve_identity<'a>(
1008                &'a self,
1009                _: &'a RuntimeComponents,
1010                _: &'a ConfigBag,
1011            ) -> IdentityFuture<'a> {
1012                unreachable!("fake identity resolver must be overridden for this test")
1013            }
1014        }
1015
1016        #[derive(Debug)]
1017        struct FakeRetryStrategy;
1018        impl RetryStrategy for FakeRetryStrategy {
1019            fn should_attempt_initial_request(
1020                &self,
1021                _: &RuntimeComponents,
1022                _: &ConfigBag,
1023            ) -> Result<crate::client::retries::ShouldAttempt, BoxError> {
1024                unreachable!("fake retry strategy must be overridden for this test")
1025            }
1026
1027            fn should_attempt_retry(
1028                &self,
1029                _: &crate::client::interceptors::context::InterceptorContext,
1030                _: &RuntimeComponents,
1031                _: &ConfigBag,
1032            ) -> Result<crate::client::retries::ShouldAttempt, BoxError> {
1033                unreachable!("fake retry strategy must be overridden for this test")
1034            }
1035        }
1036
1037        #[derive(Debug)]
1038        struct FakeTimeSource;
1039        impl TimeSource for FakeTimeSource {
1040            fn now(&self) -> std::time::SystemTime {
1041                unreachable!("fake time source must be overridden for this test")
1042            }
1043        }
1044
1045        #[derive(Debug)]
1046        struct FakeSleep;
1047        impl AsyncSleep for FakeSleep {
1048            fn sleep(&self, _: std::time::Duration) -> aws_smithy_async::rt::sleep::Sleep {
1049                unreachable!("fake sleep must be overridden for this test")
1050            }
1051        }
1052
1053        #[derive(Debug)]
1054        struct FakeIdentityCache;
1055        impl ResolveCachedIdentity for FakeIdentityCache {
1056            fn resolve_cached_identity<'a>(
1057                &'a self,
1058                resolver: SharedIdentityResolver,
1059                components: &'a RuntimeComponents,
1060                config_bag: &'a ConfigBag,
1061            ) -> IdentityFuture<'a> {
1062                IdentityFuture::new(async move {
1063                    resolver.resolve_identity(components, config_bag).await
1064                })
1065            }
1066        }
1067
1068        Self::new("aws_smithy_runtime_api::client::runtime_components::RuntimeComponentBuilder::for_tests")
1069            .with_auth_scheme(FakeAuthScheme)
1070            .with_auth_scheme_option_resolver(Some(FakeAuthSchemeOptionResolver))
1071            .with_endpoint_resolver(Some(FakeEndpointResolver))
1072            .with_http_client(Some(FakeClient))
1073            .with_identity_cache(Some(FakeIdentityCache))
1074            .with_identity_resolver(AuthSchemeId::new("fake"), FakeIdentityResolver)
1075            .with_retry_strategy(Some(FakeRetryStrategy))
1076            .with_sleep_impl(Some(SharedAsyncSleep::new(FakeSleep)))
1077            .with_time_source(Some(SharedTimeSource::new(FakeTimeSource)))
1078    }
1079}
1080
1081/// An error that occurs when building runtime components.
1082#[derive(Debug)]
1083pub struct BuildError(&'static str);
1084
1085impl std::error::Error for BuildError {}
1086
1087impl fmt::Display for BuildError {
1088    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1089        write!(f, "{}", self.0)
1090    }
1091}
1092
1093/// A trait for retrieving a shared identity resolver.
1094///
1095/// This trait exists so that [`AuthScheme::identity_resolver`]
1096/// can have access to configured identity resolvers without having access to all the runtime components.
1097pub trait GetIdentityResolver: Send + Sync {
1098    /// Returns the requested identity resolver if it is set.
1099    fn identity_resolver(&self, scheme_id: AuthSchemeId) -> Option<SharedIdentityResolver>;
1100}
1101
1102impl GetIdentityResolver for RuntimeComponents {
1103    fn identity_resolver(&self, scheme_id: AuthSchemeId) -> Option<SharedIdentityResolver> {
1104        self.identity_resolvers
1105            .get(&scheme_id)
1106            .map(|s| s.value.clone())
1107    }
1108}
1109
1110#[cfg(all(test, feature = "test-util"))]
1111mod tests {
1112    use super::{BuildError, RuntimeComponentsBuilder, Tracked};
1113    use crate::client::runtime_components::ValidateConfig;
1114
1115    #[derive(Clone, Debug, Eq, PartialEq)]
1116    struct TestComponent(String);
1117    impl ValidateConfig for TestComponent {}
1118    impl From<&'static str> for TestComponent {
1119        fn from(value: &'static str) -> Self {
1120            TestComponent(value.into())
1121        }
1122    }
1123
1124    #[test]
1125    #[allow(unreachable_pub)]
1126    #[allow(dead_code)]
1127    fn the_builders_should_merge() {
1128        declare_runtime_components! {
1129            fields for TestRc and TestRcBuilder {
1130                #[required]
1131                some_required_component: Option<TestComponent>,
1132
1133                some_optional_component: Option<TestComponent>,
1134
1135                #[atLeastOneRequired]
1136                some_required_vec: Vec<TestComponent>,
1137
1138                some_optional_vec: Vec<TestComponent>,
1139            }
1140        }
1141
1142        impl TestRc {
1143            fn sort(&mut self) {}
1144        }
1145
1146        let builder1 = TestRcBuilder {
1147            builder_name: "builder1",
1148            some_required_component: Some(Tracked::new("builder1", "override_me".into())),
1149            some_optional_component: Some(Tracked::new("builder1", "override_me optional".into())),
1150            some_required_vec: vec![Tracked::new("builder1", "first".into())],
1151            some_optional_vec: vec![Tracked::new("builder1", "first optional".into())],
1152        };
1153        let builder2 = TestRcBuilder {
1154            builder_name: "builder2",
1155            some_required_component: Some(Tracked::new("builder2", "override_me_too".into())),
1156            some_optional_component: Some(Tracked::new(
1157                "builder2",
1158                "override_me_too optional".into(),
1159            )),
1160            some_required_vec: vec![Tracked::new("builder2", "second".into())],
1161            some_optional_vec: vec![Tracked::new("builder2", "second optional".into())],
1162        };
1163        let builder3 = TestRcBuilder {
1164            builder_name: "builder3",
1165            some_required_component: Some(Tracked::new("builder3", "correct".into())),
1166            some_optional_component: Some(Tracked::new("builder3", "correct optional".into())),
1167            some_required_vec: vec![Tracked::new("builder3", "third".into())],
1168            some_optional_vec: vec![Tracked::new("builder3", "third optional".into())],
1169        };
1170        let rc = TestRcBuilder::new("root")
1171            .merge_from(&builder1)
1172            .merge_from(&builder2)
1173            .merge_from(&builder3)
1174            .build()
1175            .expect("success");
1176        assert_eq!(
1177            Tracked::new("builder3", TestComponent::from("correct")),
1178            rc.some_required_component
1179        );
1180        assert_eq!(
1181            Some(Tracked::new(
1182                "builder3",
1183                TestComponent::from("correct optional")
1184            )),
1185            rc.some_optional_component
1186        );
1187        assert_eq!(
1188            vec![
1189                Tracked::new("builder1", TestComponent::from("first")),
1190                Tracked::new("builder2", TestComponent::from("second")),
1191                Tracked::new("builder3", TestComponent::from("third"))
1192            ],
1193            rc.some_required_vec
1194        );
1195        assert_eq!(
1196            vec![
1197                Tracked::new("builder1", TestComponent::from("first optional")),
1198                Tracked::new("builder2", TestComponent::from("second optional")),
1199                Tracked::new("builder3", TestComponent::from("third optional"))
1200            ],
1201            rc.some_optional_vec
1202        );
1203    }
1204
1205    #[test]
1206    #[allow(unreachable_pub)]
1207    #[allow(dead_code)]
1208    #[should_panic(expected = "the `_some_component` runtime component is required")]
1209    fn require_field_singular() {
1210        declare_runtime_components! {
1211            fields for TestRc and TestRcBuilder {
1212                #[required]
1213                _some_component: Option<TestComponent>,
1214            }
1215        }
1216
1217        impl TestRc {
1218            fn sort(&mut self) {}
1219        }
1220
1221        let rc = TestRcBuilder::new("test").build().unwrap();
1222
1223        // Ensure the correct types were used
1224        let _: Tracked<TestComponent> = rc._some_component;
1225    }
1226
1227    #[test]
1228    #[allow(unreachable_pub)]
1229    #[allow(dead_code)]
1230    #[should_panic(expected = "at least one `_some_vec` runtime component is required")]
1231    fn require_field_plural() {
1232        declare_runtime_components! {
1233            fields for TestRc and TestRcBuilder {
1234                #[atLeastOneRequired]
1235                _some_vec: Vec<TestComponent>,
1236            }
1237        }
1238
1239        impl TestRc {
1240            fn sort(&mut self) {}
1241        }
1242
1243        let rc = TestRcBuilder::new("test").build().unwrap();
1244
1245        // Ensure the correct types were used
1246        let _: Vec<Tracked<TestComponent>> = rc._some_vec;
1247    }
1248
1249    #[test]
1250    #[allow(unreachable_pub)]
1251    #[allow(dead_code)]
1252    fn optional_fields_dont_panic() {
1253        declare_runtime_components! {
1254            fields for TestRc and TestRcBuilder {
1255                _some_optional_component: Option<TestComponent>,
1256                _some_optional_vec: Vec<TestComponent>,
1257            }
1258        }
1259
1260        impl TestRc {
1261            fn sort(&mut self) {}
1262        }
1263
1264        let rc = TestRcBuilder::new("test").build().unwrap();
1265
1266        // Ensure the correct types were used
1267        let _: Option<Tracked<TestComponent>> = rc._some_optional_component;
1268        let _: Vec<Tracked<TestComponent>> = rc._some_optional_vec;
1269    }
1270
1271    #[test]
1272    fn building_test_builder_should_not_panic() {
1273        let _ = RuntimeComponentsBuilder::for_tests().build(); // should not panic
1274    }
1275
1276    #[test]
1277    fn set_identity_resolver_should_replace_existing_resolver_for_given_auth_scheme() {
1278        use crate::client::auth::AuthSchemeId;
1279        use crate::client::identity::{Identity, IdentityFuture, ResolveIdentity};
1280        use crate::client::runtime_components::{GetIdentityResolver, RuntimeComponents};
1281        use aws_smithy_types::config_bag::ConfigBag;
1282        use tokio::runtime::Runtime;
1283
1284        #[derive(Debug)]
1285        struct AnotherFakeIdentityResolver;
1286        impl ResolveIdentity for AnotherFakeIdentityResolver {
1287            fn resolve_identity<'a>(
1288                &'a self,
1289                _: &'a RuntimeComponents,
1290                _: &'a ConfigBag,
1291            ) -> IdentityFuture<'a> {
1292                IdentityFuture::ready(Ok(Identity::new("doesn't matter", None)))
1293            }
1294        }
1295
1296        // Set a different `IdentityResolver` for the `fake` auth scheme already configured in
1297        // a test runtime components builder
1298        let rc = RuntimeComponentsBuilder::for_tests()
1299            .with_identity_resolver(AuthSchemeId::new("fake"), AnotherFakeIdentityResolver)
1300            .build()
1301            .expect("should build RuntimeComponents");
1302
1303        let resolver = rc
1304            .identity_resolver(AuthSchemeId::new("fake"))
1305            .expect("identity resolver should be found");
1306
1307        let identity = Runtime::new().unwrap().block_on(async {
1308            resolver
1309                .resolve_identity(&rc, &ConfigBag::base())
1310                .await
1311                .expect("identity should be resolved")
1312        });
1313
1314        assert_eq!(Some(&"doesn't matter"), identity.data::<&str>());
1315    }
1316}