Skip to main content

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