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: 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    /// 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: impl AsRef<AuthSchemeId>) -> Option<SharedAuthScheme> {
414        self.auth_schemes
415            .get(scheme_id.as_ref())
416            .map(|s| s.value.clone())
417    }
418
419    /// Returns the identity cache.
420    pub fn identity_cache(&self) -> SharedIdentityCache {
421        self.identity_cache.value.clone()
422    }
423
424    /// Returns an iterator over the interceptors.
425    pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
426        self.interceptors.iter().map(|s| s.value.clone())
427    }
428
429    /// Returns an iterator over the retry classifiers.
430    pub fn retry_classifiers(&self) -> impl Iterator<Item = SharedRetryClassifier> + '_ {
431        self.retry_classifiers.iter().map(|s| s.value.clone())
432    }
433
434    // Needed for `impl ValidateConfig for SharedRetryClassifier {`
435    #[cfg(debug_assertions)]
436    pub(crate) fn retry_classifiers_slice(&self) -> &[Tracked<SharedRetryClassifier>] {
437        self.retry_classifiers.as_slice()
438    }
439
440    /// Returns the retry strategy.
441    pub fn retry_strategy(&self) -> SharedRetryStrategy {
442        self.retry_strategy.value.clone()
443    }
444
445    /// Returns the async sleep implementation.
446    pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
447        self.sleep_impl.as_ref().map(|s| s.value.clone())
448    }
449
450    /// Returns the time source.
451    pub fn time_source(&self) -> Option<SharedTimeSource> {
452        self.time_source.as_ref().map(|s| s.value.clone())
453    }
454
455    /// Returns the config validators.
456    pub fn config_validators(&self) -> impl Iterator<Item = SharedConfigValidator> + '_ {
457        self.config_validators.iter().map(|s| s.value.clone())
458    }
459
460    /// Validate the final client configuration.
461    ///
462    /// This is intended to be called internally by the client.
463    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    /// Creates a new [`RuntimeComponentsBuilder`], inheriting all fields from the given
508    /// [`RuntimeComponents`].
509    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    /// Returns the auth scheme option resolver.
528    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    /// Sets the auth scheme option resolver.
535    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    /// Sets the auth scheme option resolver.
545    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    /// Returns the HTTP client.
554    pub fn http_client(&self) -> Option<SharedHttpClient> {
555        self.http_client.as_ref().map(|s| s.value.clone())
556    }
557
558    /// Sets the HTTP client.
559    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    /// Sets the HTTP client.
565    pub fn with_http_client(mut self, connector: Option<impl HttpClient + 'static>) -> Self {
566        self.set_http_client(connector);
567        self
568    }
569
570    /// Returns the endpoint resolver.
571    pub fn endpoint_resolver(&self) -> Option<SharedEndpointResolver> {
572        self.endpoint_resolver.as_ref().map(|s| s.value.clone())
573    }
574
575    /// Sets the endpoint resolver.
576    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    /// Sets the endpoint resolver.
586    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    /// Returns the auth schemes.
595    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    /// Adds an auth scheme, replacing the existing one.
602    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    /// Adds an auth scheme.
613    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    /// Returns the identity cache.
619    pub fn identity_cache(&self) -> Option<SharedIdentityCache> {
620        self.identity_cache.as_ref().map(|s| s.value.clone())
621    }
622
623    /// Sets the identity cache.
624    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    /// Sets the identity cache.
634    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    /// Returns [`SharedIdentityResolver`] configured in the builder for a given `scheme_id`.
643    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    /// This method is broken since it does not replace an existing identity resolver of the given auth scheme ID.
651    /// Use `set_identity_resolver` instead.
652    #[deprecated(
653        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."
654    )]
655    pub fn push_identity_resolver(
656        &mut self,
657        scheme_id: AuthSchemeId,
658        identity_resolver: impl ResolveIdentity + 'static,
659    ) -> &mut Self {
660        self.set_identity_resolver(scheme_id, identity_resolver)
661    }
662
663    /// Sets the identity resolver for a given `scheme_id`.
664    ///
665    /// If there is already an identity resolver for that `scheme_id`, this method will replace
666    /// the existing one with the passed-in `identity_resolver`.
667    pub fn set_identity_resolver(
668        &mut self,
669        scheme_id: AuthSchemeId,
670        identity_resolver: impl ResolveIdentity + 'static,
671    ) -> &mut Self {
672        let mut resolvers = self.identity_resolvers.take().unwrap_or_default();
673        resolvers.insert(
674            scheme_id,
675            Tracked::new(self.builder_name, identity_resolver.into_shared()),
676        );
677        self.identity_resolvers = Some(resolvers);
678        self
679    }
680
681    /// Adds an identity resolver.
682    pub fn with_identity_resolver(
683        mut self,
684        scheme_id: AuthSchemeId,
685        identity_resolver: impl ResolveIdentity + 'static,
686    ) -> Self {
687        self.set_identity_resolver(scheme_id, identity_resolver);
688        self
689    }
690
691    /// Returns the interceptors.
692    pub fn interceptors(&self) -> impl Iterator<Item = SharedInterceptor> + '_ {
693        self.interceptors.iter().map(|s| s.value.clone())
694    }
695
696    /// Adds all the given interceptors.
697    pub fn extend_interceptors(
698        &mut self,
699        interceptors: impl Iterator<Item = SharedInterceptor>,
700    ) -> &mut Self {
701        self.interceptors
702            .extend(interceptors.map(|s| Tracked::new(self.builder_name, s)));
703        self
704    }
705
706    /// Adds an interceptor.
707    pub fn push_interceptor(&mut self, interceptor: impl Intercept + 'static) -> &mut Self {
708        self.interceptors
709            .push(Tracked::new(self.builder_name, interceptor.into_shared()));
710        self
711    }
712
713    /// Adds an interceptor.
714    pub fn with_interceptor(mut self, interceptor: impl Intercept + 'static) -> Self {
715        self.push_interceptor(interceptor);
716        self
717    }
718
719    /// Directly sets the interceptors and clears out any that were previously pushed.
720    pub fn set_interceptors(
721        &mut self,
722        interceptors: impl Iterator<Item = SharedInterceptor>,
723    ) -> &mut Self {
724        self.interceptors.clear();
725        self.interceptors
726            .extend(interceptors.map(|s| Tracked::new(self.builder_name, s)));
727        self
728    }
729
730    /// Directly sets the interceptors and clears out any that were previously pushed.
731    pub fn with_interceptors(
732        mut self,
733        interceptors: impl Iterator<Item = SharedInterceptor>,
734    ) -> Self {
735        self.set_interceptors(interceptors);
736        self
737    }
738
739    /// Returns the retry classifiers.
740    pub fn retry_classifiers(&self) -> impl Iterator<Item = SharedRetryClassifier> + '_ {
741        self.retry_classifiers.iter().map(|s| s.value.clone())
742    }
743
744    /// Adds all the given retry classifiers.
745    pub fn extend_retry_classifiers(
746        &mut self,
747        retry_classifiers: impl Iterator<Item = SharedRetryClassifier>,
748    ) -> &mut Self {
749        self.retry_classifiers
750            .extend(retry_classifiers.map(|s| Tracked::new(self.builder_name, s)));
751        self
752    }
753
754    /// Adds a retry_classifier.
755    pub fn push_retry_classifier(
756        &mut self,
757        retry_classifier: impl ClassifyRetry + 'static,
758    ) -> &mut Self {
759        self.retry_classifiers.push(Tracked::new(
760            self.builder_name,
761            retry_classifier.into_shared(),
762        ));
763        self
764    }
765
766    /// Adds a retry_classifier.
767    pub fn with_retry_classifier(mut self, retry_classifier: impl ClassifyRetry + 'static) -> Self {
768        self.push_retry_classifier(retry_classifier);
769        self
770    }
771
772    /// Directly sets the retry_classifiers and clears out any that were previously pushed.
773    pub fn set_retry_classifiers(
774        &mut self,
775        retry_classifiers: impl Iterator<Item = SharedRetryClassifier>,
776    ) -> &mut Self {
777        self.retry_classifiers.clear();
778        self.retry_classifiers
779            .extend(retry_classifiers.map(|s| Tracked::new(self.builder_name, s)));
780        self
781    }
782
783    /// Returns the retry strategy.
784    pub fn retry_strategy(&self) -> Option<SharedRetryStrategy> {
785        self.retry_strategy.as_ref().map(|s| s.value.clone())
786    }
787
788    /// Sets the retry strategy.
789    pub fn set_retry_strategy(
790        &mut self,
791        retry_strategy: Option<impl RetryStrategy + 'static>,
792    ) -> &mut Self {
793        self.retry_strategy =
794            retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared()));
795        self
796    }
797
798    /// Sets the retry strategy.
799    pub fn with_retry_strategy(
800        mut self,
801        retry_strategy: Option<impl RetryStrategy + 'static>,
802    ) -> Self {
803        self.retry_strategy =
804            retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared()));
805        self
806    }
807
808    /// Returns the async sleep implementation.
809    pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
810        self.sleep_impl.as_ref().map(|s| s.value.clone())
811    }
812
813    /// Sets the async sleep implementation.
814    pub fn set_sleep_impl(&mut self, sleep_impl: Option<SharedAsyncSleep>) -> &mut Self {
815        self.sleep_impl = self.tracked(sleep_impl);
816        self
817    }
818
819    /// Sets the async sleep implementation.
820    pub fn with_sleep_impl(mut self, sleep_impl: Option<impl AsyncSleep + 'static>) -> Self {
821        self.set_sleep_impl(sleep_impl.map(IntoShared::into_shared));
822        self
823    }
824
825    /// Returns the time source.
826    pub fn time_source(&self) -> Option<SharedTimeSource> {
827        self.time_source.as_ref().map(|s| s.value.clone())
828    }
829
830    /// Sets the time source.
831    pub fn set_time_source(&mut self, time_source: Option<SharedTimeSource>) -> &mut Self {
832        self.time_source = self.tracked(time_source);
833        self
834    }
835
836    /// Sets the time source.
837    pub fn with_time_source(mut self, time_source: Option<impl TimeSource + 'static>) -> Self {
838        self.set_time_source(time_source.map(IntoShared::into_shared));
839        self
840    }
841
842    /// Returns the config validators.
843    pub fn config_validators(&self) -> impl Iterator<Item = SharedConfigValidator> + '_ {
844        self.config_validators.iter().map(|s| s.value.clone())
845    }
846
847    /// Adds all the given config validators.
848    pub fn extend_config_validators(
849        &mut self,
850        config_validators: impl Iterator<Item = SharedConfigValidator>,
851    ) -> &mut Self {
852        self.config_validators
853            .extend(config_validators.map(|s| Tracked::new(self.builder_name, s)));
854        self
855    }
856
857    /// Adds a config validator.
858    pub fn push_config_validator(
859        &mut self,
860        config_validator: impl ValidateConfig + 'static,
861    ) -> &mut Self {
862        self.config_validators.push(Tracked::new(
863            self.builder_name,
864            config_validator.into_shared(),
865        ));
866        self
867    }
868
869    /// Adds a config validator.
870    pub fn with_config_validator(
871        mut self,
872        config_validator: impl ValidateConfig + 'static,
873    ) -> Self {
874        self.push_config_validator(config_validator);
875        self
876    }
877
878    /// Validate the base client configuration.
879    ///
880    /// This is intended to be called internally by the client.
881    pub fn validate_base_client_config(&self, cfg: &ConfigBag) -> Result<(), BoxError> {
882        macro_rules! validate {
883            ($field:expr) => {
884                #[allow(for_loops_over_fallibles)]
885                for entry in $field {
886                    ValidateConfig::validate_base_client_config(&entry.value, self, cfg)?;
887                }
888            };
889        }
890
891        for validator in self.config_validators() {
892            validator.validate_base_client_config(self, cfg)?;
893        }
894        validate!(&self.http_client);
895        validate!(&self.endpoint_resolver);
896        if let Some(auth_schemes) = &self.auth_schemes {
897            validate!(auth_schemes.values())
898        }
899        validate!(&self.identity_cache);
900        if let Some(resolvers) = &self.identity_resolvers {
901            validate!(resolvers.values())
902        }
903        validate!(&self.interceptors);
904        validate!(&self.retry_strategy);
905        Ok(())
906    }
907
908    /// Converts this builder into [`TimeComponents`].
909    pub fn into_time_components(mut self) -> TimeComponents {
910        TimeComponents {
911            sleep_impl: self.sleep_impl.take().map(|s| s.value),
912            time_source: self.time_source.take().map(|s| s.value),
913        }
914    }
915
916    /// Wraps `v` in tracking associated with this builder
917    fn tracked<T>(&self, v: Option<T>) -> Option<Tracked<T>> {
918        v.map(|v| Tracked::new(self.builder_name, v))
919    }
920}
921
922/// Time-related subset of components that can be extracted directly from [`RuntimeComponentsBuilder`] prior to validation.
923#[derive(Debug)]
924pub struct TimeComponents {
925    sleep_impl: Option<SharedAsyncSleep>,
926    time_source: Option<SharedTimeSource>,
927}
928
929impl TimeComponents {
930    /// Returns the async sleep implementation if one is available.
931    pub fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
932        self.sleep_impl.clone()
933    }
934
935    /// Returns the time source if one is available.
936    pub fn time_source(&self) -> Option<SharedTimeSource> {
937        self.time_source.clone()
938    }
939}
940
941#[derive(Clone, Debug)]
942#[cfg_attr(test, derive(Eq, PartialEq))]
943pub(crate) struct Tracked<T> {
944    _origin: &'static str,
945    value: T,
946}
947
948impl<T> Tracked<T> {
949    fn new(origin: &'static str, value: T) -> Self {
950        Self {
951            _origin: origin,
952            value,
953        }
954    }
955
956    #[cfg(debug_assertions)]
957    pub(crate) fn value(&self) -> &T {
958        &self.value
959    }
960}
961
962impl RuntimeComponentsBuilder {
963    /// Creates a runtime components builder with all the required components filled in with fake (panicking) implementations.
964    #[cfg(feature = "test-util")]
965    pub fn for_tests() -> Self {
966        use crate::client::endpoint::{EndpointFuture, EndpointResolverParams};
967        use crate::client::identity::IdentityFuture;
968
969        #[derive(Debug)]
970        struct FakeAuthSchemeOptionResolver;
971        impl ResolveAuthSchemeOptions for FakeAuthSchemeOptionResolver {
972            fn resolve_auth_scheme_options(
973                &self,
974                _: &crate::client::auth::AuthSchemeOptionResolverParams,
975            ) -> Result<std::borrow::Cow<'_, [AuthSchemeId]>, BoxError> {
976                unreachable!("fake auth scheme option resolver must be overridden for this test")
977            }
978        }
979
980        #[derive(Debug)]
981        struct FakeClient;
982        impl HttpClient for FakeClient {
983            fn http_connector(
984                &self,
985                _: &crate::client::http::HttpConnectorSettings,
986                _: &RuntimeComponents,
987            ) -> crate::client::http::SharedHttpConnector {
988                unreachable!("fake client must be overridden for this test")
989            }
990        }
991
992        #[derive(Debug)]
993        struct FakeEndpointResolver;
994        impl ResolveEndpoint for FakeEndpointResolver {
995            fn resolve_endpoint<'a>(&'a self, _: &'a EndpointResolverParams) -> EndpointFuture<'a> {
996                unreachable!("fake endpoint resolver must be overridden for this test")
997            }
998        }
999
1000        #[derive(Debug)]
1001        struct FakeAuthScheme;
1002        impl AuthScheme for FakeAuthScheme {
1003            fn scheme_id(&self) -> AuthSchemeId {
1004                AuthSchemeId::new("fake")
1005            }
1006
1007            fn identity_resolver(
1008                &self,
1009                _: &dyn GetIdentityResolver,
1010            ) -> Option<SharedIdentityResolver> {
1011                None
1012            }
1013
1014            fn signer(&self) -> &dyn crate::client::auth::Sign {
1015                unreachable!("fake http auth scheme must be overridden for this test")
1016            }
1017        }
1018
1019        #[derive(Debug)]
1020        struct FakeIdentityResolver;
1021        impl ResolveIdentity for FakeIdentityResolver {
1022            fn resolve_identity<'a>(
1023                &'a self,
1024                _: &'a RuntimeComponents,
1025                _: &'a ConfigBag,
1026            ) -> IdentityFuture<'a> {
1027                unreachable!("fake identity resolver must be overridden for this test")
1028            }
1029        }
1030
1031        #[derive(Debug)]
1032        struct FakeRetryStrategy;
1033        impl RetryStrategy for FakeRetryStrategy {
1034            fn should_attempt_initial_request(
1035                &self,
1036                _: &RuntimeComponents,
1037                _: &ConfigBag,
1038            ) -> Result<crate::client::retries::ShouldAttempt, BoxError> {
1039                unreachable!("fake retry strategy must be overridden for this test")
1040            }
1041
1042            fn should_attempt_retry(
1043                &self,
1044                _: &crate::client::interceptors::context::InterceptorContext,
1045                _: &RuntimeComponents,
1046                _: &ConfigBag,
1047            ) -> Result<crate::client::retries::ShouldAttempt, BoxError> {
1048                unreachable!("fake retry strategy must be overridden for this test")
1049            }
1050        }
1051
1052        #[derive(Debug)]
1053        struct FakeTimeSource;
1054        impl TimeSource for FakeTimeSource {
1055            fn now(&self) -> std::time::SystemTime {
1056                unreachable!("fake time source must be overridden for this test")
1057            }
1058        }
1059
1060        #[derive(Debug)]
1061        struct FakeSleep;
1062        impl AsyncSleep for FakeSleep {
1063            fn sleep(&self, _: std::time::Duration) -> aws_smithy_async::rt::sleep::Sleep {
1064                unreachable!("fake sleep must be overridden for this test")
1065            }
1066        }
1067
1068        #[derive(Debug)]
1069        struct FakeIdentityCache;
1070        impl ResolveCachedIdentity for FakeIdentityCache {
1071            fn resolve_cached_identity<'a>(
1072                &'a self,
1073                resolver: SharedIdentityResolver,
1074                components: &'a RuntimeComponents,
1075                config_bag: &'a ConfigBag,
1076            ) -> IdentityFuture<'a> {
1077                IdentityFuture::new(async move {
1078                    resolver.resolve_identity(components, config_bag).await
1079                })
1080            }
1081        }
1082
1083        Self::new("aws_smithy_runtime_api::client::runtime_components::RuntimeComponentBuilder::for_tests")
1084            .with_auth_scheme(FakeAuthScheme)
1085            .with_auth_scheme_option_resolver(Some(FakeAuthSchemeOptionResolver))
1086            .with_endpoint_resolver(Some(FakeEndpointResolver))
1087            .with_http_client(Some(FakeClient))
1088            .with_identity_cache(Some(FakeIdentityCache))
1089            .with_identity_resolver(AuthSchemeId::new("fake"), FakeIdentityResolver)
1090            .with_retry_strategy(Some(FakeRetryStrategy))
1091            .with_sleep_impl(Some(SharedAsyncSleep::new(FakeSleep)))
1092            .with_time_source(Some(SharedTimeSource::new(FakeTimeSource)))
1093    }
1094}
1095
1096/// An error that occurs when building runtime components.
1097#[derive(Debug)]
1098pub struct BuildError(&'static str);
1099
1100impl std::error::Error for BuildError {}
1101
1102impl fmt::Display for BuildError {
1103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1104        write!(f, "{}", self.0)
1105    }
1106}
1107
1108/// A trait for retrieving a shared identity resolver.
1109///
1110/// This trait exists so that [`AuthScheme::identity_resolver`]
1111/// can have access to configured identity resolvers without having access to all the runtime components.
1112pub trait GetIdentityResolver: Send + Sync {
1113    /// Returns the requested identity resolver if it is set.
1114    fn identity_resolver(&self, scheme_id: AuthSchemeId) -> Option<SharedIdentityResolver>;
1115}
1116
1117impl GetIdentityResolver for RuntimeComponents {
1118    fn identity_resolver(&self, scheme_id: AuthSchemeId) -> Option<SharedIdentityResolver> {
1119        self.identity_resolvers
1120            .get(&scheme_id)
1121            .map(|s| s.value.clone())
1122    }
1123}
1124
1125#[cfg(all(test, feature = "test-util"))]
1126mod tests {
1127    use super::{BuildError, RuntimeComponentsBuilder, Tracked};
1128    use crate::client::runtime_components::ValidateConfig;
1129
1130    #[derive(Clone, Debug, Eq, PartialEq)]
1131    struct TestComponent(String);
1132    impl ValidateConfig for TestComponent {}
1133    impl From<&'static str> for TestComponent {
1134        fn from(value: &'static str) -> Self {
1135            TestComponent(value.into())
1136        }
1137    }
1138
1139    #[test]
1140    #[allow(unreachable_pub)]
1141    #[allow(dead_code)]
1142    fn the_builders_should_merge() {
1143        declare_runtime_components! {
1144            fields for TestRc and TestRcBuilder {
1145                #[required]
1146                some_required_component: Option<TestComponent>,
1147
1148                some_optional_component: Option<TestComponent>,
1149
1150                #[atLeastOneRequired]
1151                some_required_vec: Vec<TestComponent>,
1152
1153                some_optional_vec: Vec<TestComponent>,
1154            }
1155        }
1156
1157        impl TestRc {
1158            fn sort(&mut self) {}
1159        }
1160
1161        let builder1 = TestRcBuilder {
1162            builder_name: "builder1",
1163            some_required_component: Some(Tracked::new("builder1", "override_me".into())),
1164            some_optional_component: Some(Tracked::new("builder1", "override_me optional".into())),
1165            some_required_vec: vec![Tracked::new("builder1", "first".into())],
1166            some_optional_vec: vec![Tracked::new("builder1", "first optional".into())],
1167        };
1168        let builder2 = TestRcBuilder {
1169            builder_name: "builder2",
1170            some_required_component: Some(Tracked::new("builder2", "override_me_too".into())),
1171            some_optional_component: Some(Tracked::new(
1172                "builder2",
1173                "override_me_too optional".into(),
1174            )),
1175            some_required_vec: vec![Tracked::new("builder2", "second".into())],
1176            some_optional_vec: vec![Tracked::new("builder2", "second optional".into())],
1177        };
1178        let builder3 = TestRcBuilder {
1179            builder_name: "builder3",
1180            some_required_component: Some(Tracked::new("builder3", "correct".into())),
1181            some_optional_component: Some(Tracked::new("builder3", "correct optional".into())),
1182            some_required_vec: vec![Tracked::new("builder3", "third".into())],
1183            some_optional_vec: vec![Tracked::new("builder3", "third optional".into())],
1184        };
1185        let rc = TestRcBuilder::new("root")
1186            .merge_from(&builder1)
1187            .merge_from(&builder2)
1188            .merge_from(&builder3)
1189            .build()
1190            .expect("success");
1191        assert_eq!(
1192            Tracked::new("builder3", TestComponent::from("correct")),
1193            rc.some_required_component
1194        );
1195        assert_eq!(
1196            Some(Tracked::new(
1197                "builder3",
1198                TestComponent::from("correct optional")
1199            )),
1200            rc.some_optional_component
1201        );
1202        assert_eq!(
1203            vec![
1204                Tracked::new("builder1", TestComponent::from("first")),
1205                Tracked::new("builder2", TestComponent::from("second")),
1206                Tracked::new("builder3", TestComponent::from("third"))
1207            ],
1208            rc.some_required_vec
1209        );
1210        assert_eq!(
1211            vec![
1212                Tracked::new("builder1", TestComponent::from("first optional")),
1213                Tracked::new("builder2", TestComponent::from("second optional")),
1214                Tracked::new("builder3", TestComponent::from("third optional"))
1215            ],
1216            rc.some_optional_vec
1217        );
1218    }
1219
1220    #[test]
1221    #[allow(unreachable_pub)]
1222    #[allow(dead_code)]
1223    #[should_panic(expected = "the `_some_component` runtime component is required")]
1224    fn require_field_singular() {
1225        declare_runtime_components! {
1226            fields for TestRc and TestRcBuilder {
1227                #[required]
1228                _some_component: Option<TestComponent>,
1229            }
1230        }
1231
1232        impl TestRc {
1233            fn sort(&mut self) {}
1234        }
1235
1236        let rc = TestRcBuilder::new("test").build().unwrap();
1237
1238        // Ensure the correct types were used
1239        let _: Tracked<TestComponent> = rc._some_component;
1240    }
1241
1242    #[test]
1243    #[allow(unreachable_pub)]
1244    #[allow(dead_code)]
1245    #[should_panic(expected = "at least one `_some_vec` runtime component is required")]
1246    fn require_field_plural() {
1247        declare_runtime_components! {
1248            fields for TestRc and TestRcBuilder {
1249                #[atLeastOneRequired]
1250                _some_vec: Vec<TestComponent>,
1251            }
1252        }
1253
1254        impl TestRc {
1255            fn sort(&mut self) {}
1256        }
1257
1258        let rc = TestRcBuilder::new("test").build().unwrap();
1259
1260        // Ensure the correct types were used
1261        let _: Vec<Tracked<TestComponent>> = rc._some_vec;
1262    }
1263
1264    #[test]
1265    #[allow(unreachable_pub)]
1266    #[allow(dead_code)]
1267    fn optional_fields_dont_panic() {
1268        declare_runtime_components! {
1269            fields for TestRc and TestRcBuilder {
1270                _some_optional_component: Option<TestComponent>,
1271                _some_optional_vec: Vec<TestComponent>,
1272            }
1273        }
1274
1275        impl TestRc {
1276            fn sort(&mut self) {}
1277        }
1278
1279        let rc = TestRcBuilder::new("test").build().unwrap();
1280
1281        // Ensure the correct types were used
1282        let _: Option<Tracked<TestComponent>> = rc._some_optional_component;
1283        let _: Vec<Tracked<TestComponent>> = rc._some_optional_vec;
1284    }
1285
1286    #[test]
1287    fn building_test_builder_should_not_panic() {
1288        let _ = RuntimeComponentsBuilder::for_tests().build(); // should not panic
1289    }
1290
1291    #[test]
1292    fn set_identity_resolver_should_replace_existing_resolver_for_given_auth_scheme() {
1293        use crate::client::auth::AuthSchemeId;
1294        use crate::client::identity::{Identity, IdentityFuture, ResolveIdentity};
1295        use crate::client::runtime_components::{GetIdentityResolver, RuntimeComponents};
1296        use aws_smithy_types::config_bag::ConfigBag;
1297        use tokio::runtime::Runtime;
1298
1299        #[derive(Debug)]
1300        struct AnotherFakeIdentityResolver;
1301        impl ResolveIdentity for AnotherFakeIdentityResolver {
1302            fn resolve_identity<'a>(
1303                &'a self,
1304                _: &'a RuntimeComponents,
1305                _: &'a ConfigBag,
1306            ) -> IdentityFuture<'a> {
1307                IdentityFuture::ready(Ok(Identity::new("doesn't matter", None)))
1308            }
1309        }
1310
1311        // Set a different `IdentityResolver` for the `fake` auth scheme already configured in
1312        // a test runtime components builder
1313        let rc = RuntimeComponentsBuilder::for_tests()
1314            .with_identity_resolver(AuthSchemeId::new("fake"), AnotherFakeIdentityResolver)
1315            .build()
1316            .expect("should build RuntimeComponents");
1317
1318        let resolver = rc
1319            .identity_resolver(AuthSchemeId::new("fake"))
1320            .expect("identity resolver should be found");
1321
1322        let identity = Runtime::new().unwrap().block_on(async {
1323            resolver
1324                .resolve_identity(&rc, &ConfigBag::base())
1325                .await
1326                .expect("identity should be resolved")
1327        });
1328
1329        assert_eq!(Some(&"doesn't matter"), identity.data::<&str>());
1330    }
1331}