Skip to main content

aws_config/
lib.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6/* Automatically managed default lints */
7#![cfg_attr(docsrs, feature(doc_cfg))]
8/* End of automatically managed default lints */
9#![allow(clippy::derive_partial_eq_without_eq)]
10#![warn(
11    missing_debug_implementations,
12    missing_docs,
13    rust_2018_idioms,
14    rustdoc::missing_crate_level_docs,
15    unreachable_pub
16)]
17// Allow disallowed methods in tests
18#![cfg_attr(test, allow(clippy::disallowed_methods))]
19
20//! `aws-config` provides implementations of region and credential resolution.
21//!
22//! These implementations can be used either via the default chain implementation
23//! [`from_env`]/[`ConfigLoader`] or ad-hoc individual credential and region providers.
24//!
25//! [`ConfigLoader`] can combine different configuration sources into an AWS shared-config:
26//! [`SdkConfig`]. `SdkConfig` can be used configure an AWS service client.
27//!
28//! # Examples
29//!
30//! Load default SDK configuration:
31//! ```no_run
32//! use aws_config::BehaviorVersion;
33//! mod aws_sdk_dynamodb {
34//! #   pub struct Client;
35//! #   impl Client {
36//! #     pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
37//! #   }
38//! # }
39//! # async fn docs() {
40//! let config = aws_config::load_defaults(BehaviorVersion::v2023_11_09()).await;
41//! let client = aws_sdk_dynamodb::Client::new(&config);
42//! # }
43//! ```
44//!
45//! Load SDK configuration with a region override:
46//! ```no_run
47//! # mod aws_sdk_dynamodb {
48//! #   pub struct Client;
49//! #   impl Client {
50//! #     pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
51//! #   }
52//! # }
53//! # async fn docs() {
54//! # use aws_config::meta::region::RegionProviderChain;
55//! let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
56//! // Note: requires the `behavior-version-latest` feature enabled
57//! let config = aws_config::from_env().region(region_provider).load().await;
58//! let client = aws_sdk_dynamodb::Client::new(&config);
59//! # }
60//! ```
61//!
62//! Override configuration after construction of `SdkConfig`:
63//!
64//! ```no_run
65//! # use aws_credential_types::provider::ProvideCredentials;
66//! # use aws_types::SdkConfig;
67//! # mod aws_sdk_dynamodb {
68//! #   pub mod config {
69//! #     pub struct Builder;
70//! #     impl Builder {
71//! #       pub fn credentials_provider(
72//! #         self,
73//! #         credentials_provider: impl aws_credential_types::provider::ProvideCredentials + 'static) -> Self { self }
74//! #       pub fn build(self) -> Builder { self }
75//! #     }
76//! #     impl From<&aws_types::SdkConfig> for Builder {
77//! #       fn from(_: &aws_types::SdkConfig) -> Self {
78//! #           todo!()
79//! #       }
80//! #     }
81//! #   }
82//! #   pub struct Client;
83//! #   impl Client {
84//! #     pub fn from_conf(conf: config::Builder) -> Self { Client }
85//! #     pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
86//! #   }
87//! # }
88//! # async fn docs() {
89//! # use aws_config::meta::region::RegionProviderChain;
90//! # fn custom_provider(base: &SdkConfig) -> impl ProvideCredentials {
91//! #   base.credentials_provider().unwrap().clone()
92//! # }
93//! let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
94//! let custom_credentials_provider = custom_provider(&sdk_config);
95//! let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
96//!   .credentials_provider(custom_credentials_provider)
97//!   .build();
98//! let client = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
99//! # }
100//! ```
101
102pub use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
103// Re-export types from aws-types
104pub use aws_types::{
105    app_name::{AppName, InvalidAppName},
106    region::Region,
107    sdk_ua_metadata::{FrameworkMetadata, InvalidFrameworkMetadata},
108    SdkConfig,
109};
110/// Load default sources for all configuration with override support
111pub use loader::ConfigLoader;
112
113/// Types for configuring identity caching.
114pub mod identity {
115    pub use aws_smithy_runtime::client::identity::IdentityCache;
116    pub use aws_smithy_runtime::client::identity::LazyCacheBuilder;
117}
118
119#[allow(dead_code)]
120const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
121
122mod http_credential_provider;
123mod json_credentials;
124#[cfg(test)]
125mod test_case;
126
127pub mod credential_process;
128pub mod default_provider;
129pub mod ecs;
130mod env_service_config;
131pub mod environment;
132pub mod imds;
133#[cfg(feature = "credentials-login")]
134pub mod login;
135pub mod meta;
136pub mod profile;
137pub mod provider_config;
138pub mod retry;
139mod sensitive_command;
140#[cfg(feature = "sso")]
141pub mod sso;
142pub mod stalled_stream_protection;
143pub mod sts;
144pub mod timeout;
145pub mod web_identity_token;
146
147/// Create a config loader with the _latest_ defaults.
148///
149/// This loader will always set [`BehaviorVersion::latest`].
150///
151/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
152///
153/// # Examples
154/// ```no_run
155/// # async fn create_config() {
156/// let config = aws_config::from_env().region("us-east-1").load().await;
157/// # }
158/// ```
159#[cfg(feature = "behavior-version-latest")]
160pub fn from_env() -> ConfigLoader {
161    ConfigLoader::default().behavior_version(BehaviorVersion::latest())
162}
163
164/// Load default configuration with the _latest_ defaults.
165///
166/// Convenience wrapper equivalent to `aws_config::load_defaults(BehaviorVersion::latest()).await`
167///
168/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
169#[cfg(feature = "behavior-version-latest")]
170pub async fn load_from_env() -> SdkConfig {
171    from_env().load().await
172}
173
174/// Create a config loader with the _latest_ defaults.
175#[cfg(not(feature = "behavior-version-latest"))]
176#[deprecated(
177    note = "Use the `aws_config::defaults` function. If you don't care about future default behavior changes, you can continue to use this function by enabling the `behavior-version-latest` feature. Doing so will make this deprecation notice go away."
178)]
179pub fn from_env() -> ConfigLoader {
180    ConfigLoader::default().behavior_version(BehaviorVersion::latest())
181}
182
183/// Load default configuration with the _latest_ defaults.
184#[cfg(not(feature = "behavior-version-latest"))]
185#[deprecated(
186    note = "Use the `aws_config::load_defaults` function. If you don't care about future default behavior changes, you can continue to use this function by enabling the `behavior-version-latest` feature. Doing so will make this deprecation notice go away."
187)]
188pub async fn load_from_env() -> SdkConfig {
189    load_defaults(BehaviorVersion::latest()).await
190}
191
192/// Create a config loader with the defaults for the given behavior version.
193///
194/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
195///
196/// # Examples
197/// ```no_run
198/// # async fn create_config() {
199/// use aws_config::BehaviorVersion;
200/// let config = aws_config::defaults(BehaviorVersion::v2023_11_09())
201///     .region("us-east-1")
202///     .load()
203///     .await;
204/// # }
205/// ```
206pub fn defaults(version: BehaviorVersion) -> ConfigLoader {
207    ConfigLoader::default().behavior_version(version)
208}
209
210/// Load default configuration with the given behavior version.
211///
212/// Convenience wrapper equivalent to `aws_config::defaults(behavior_version).load().await`
213///
214/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
215pub async fn load_defaults(version: BehaviorVersion) -> SdkConfig {
216    defaults(version).load().await
217}
218
219mod loader {
220    use crate::env_service_config::EnvServiceConfig;
221    use aws_credential_types::provider::{
222        token::{ProvideToken, SharedTokenProvider},
223        ProvideCredentials, SharedCredentialsProvider,
224    };
225    use aws_credential_types::Credentials;
226    use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
227    use aws_smithy_async::time::{SharedTimeSource, TimeSource};
228    use aws_smithy_runtime::client::identity::IdentityCache;
229    use aws_smithy_runtime_api::client::auth::AuthSchemePreference;
230    use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
231    use aws_smithy_runtime_api::client::http::HttpClient;
232    use aws_smithy_runtime_api::client::identity::{ResolveCachedIdentity, SharedIdentityCache};
233    use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
234    use aws_smithy_runtime_api::shared::IntoShared;
235    use aws_smithy_schema::protocol::{ClientProtocol, SharedClientProtocol};
236    use aws_smithy_types::checksum_config::{
237        RequestChecksumCalculation, ResponseChecksumValidation,
238    };
239    use aws_smithy_types::retry::RetryConfig;
240    use aws_smithy_types::timeout::TimeoutConfig;
241    use aws_types::app_name::AppName;
242    use aws_types::docs_for;
243    use aws_types::endpoint_config::AccountIdEndpointMode;
244    use aws_types::origin::Origin;
245    use aws_types::os_shim_internal::{Env, Fs};
246    use aws_types::region::SigningRegionSet;
247    use aws_types::sdk_config::SharedHttpClient;
248    use aws_types::sdk_ua_metadata::FrameworkMetadata;
249    use aws_types::SdkConfig;
250
251    use crate::default_provider::{
252        account_id_endpoint_mode, app_name, auth_scheme_preference, checksums, credentials,
253        disable_clock_skew_correction, disable_request_compression, endpoint_url,
254        ignore_configured_endpoint_urls as ignore_ep, region, request_min_compression_size_bytes,
255        retry_config, sigv4a_signing_region_set, timeout_config, use_dual_stack, use_fips,
256    };
257    use crate::meta::region::ProvideRegion;
258    #[allow(deprecated)]
259    use crate::profile::profile_file::ProfileFiles;
260    use crate::provider_config::ProviderConfig;
261
262    #[derive(Default, Debug)]
263    enum TriStateOption<T> {
264        /// No option was set by the user. We can set up the default.
265        #[default]
266        NotSet,
267        /// The option was explicitly unset. Do not set up a default.
268        ExplicitlyUnset,
269        /// Use the given user provided option.
270        Set(T),
271    }
272
273    /// Load a cross-service [`SdkConfig`] from the environment
274    ///
275    /// This builder supports overriding individual components of the generated config. Overriding a component
276    /// will skip the standard resolution chain from **for that component**. For example,
277    /// if you override the region provider, _even if that provider returns None_, the default region provider
278    /// chain will not be used.
279    #[derive(Default, Debug)]
280    pub struct ConfigLoader {
281        app_name: Option<AppName>,
282        framework_metadata: Vec<FrameworkMetadata>,
283        auth_scheme_preference: Option<AuthSchemePreference>,
284        sigv4a_signing_region_set: Option<SigningRegionSet>,
285        identity_cache: Option<SharedIdentityCache>,
286        credentials_provider: TriStateOption<SharedCredentialsProvider>,
287        token_provider: Option<SharedTokenProvider>,
288        account_id_endpoint_mode: Option<AccountIdEndpointMode>,
289        endpoint_url: Option<String>,
290        region: Option<Box<dyn ProvideRegion>>,
291        retry_config: Option<RetryConfig>,
292        sleep: Option<SharedAsyncSleep>,
293        timeout_config: Option<TimeoutConfig>,
294        provider_config: Option<ProviderConfig>,
295        http_client: Option<SharedHttpClient>,
296        profile_name_override: Option<String>,
297        #[allow(deprecated)]
298        profile_files_override: Option<ProfileFiles>,
299        use_fips: Option<bool>,
300        use_dual_stack: Option<bool>,
301        time_source: Option<SharedTimeSource>,
302        disable_request_compression: Option<bool>,
303        disable_clock_skew_correction: Option<bool>,
304        request_min_compression_size_bytes: Option<u32>,
305        stalled_stream_protection_config: Option<StalledStreamProtectionConfig>,
306        env: Option<Env>,
307        fs: Option<Fs>,
308        behavior_version: Option<BehaviorVersion>,
309        request_checksum_calculation: Option<RequestChecksumCalculation>,
310        response_checksum_validation: Option<ResponseChecksumValidation>,
311        protocol: Option<SharedClientProtocol>,
312    }
313
314    impl ConfigLoader {
315        /// Sets the [`BehaviorVersion`] used to build [`SdkConfig`].
316        pub fn behavior_version(mut self, behavior_version: BehaviorVersion) -> Self {
317            self.behavior_version = Some(behavior_version);
318            self
319        }
320
321        /// Override the region used to build [`SdkConfig`].
322        ///
323        /// # Examples
324        /// ```no_run
325        /// # async fn create_config() {
326        /// use aws_types::region::Region;
327        /// let config = aws_config::from_env()
328        ///     .region(Region::new("us-east-1"))
329        ///     .load().await;
330        /// # }
331        /// ```
332        pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
333            self.region = Some(Box::new(region));
334            self
335        }
336
337        /// Override the retry_config used to build [`SdkConfig`].
338        ///
339        /// # Examples
340        /// ```no_run
341        /// # async fn create_config() {
342        /// use aws_config::retry::RetryConfig;
343        ///
344        /// let config = aws_config::from_env()
345        ///     .retry_config(RetryConfig::standard().with_max_attempts(2))
346        ///     .load()
347        ///     .await;
348        /// # }
349        /// ```
350        pub fn retry_config(mut self, retry_config: RetryConfig) -> Self {
351            self.retry_config = Some(retry_config);
352            self
353        }
354
355        /// Override the timeout config used to build [`SdkConfig`].
356        ///
357        /// This will be merged with timeouts coming from the timeout information provider, which
358        /// currently includes a default `CONNECT` timeout of `3.1s`.
359        ///
360        /// If you want to disable timeouts, use [`TimeoutConfig::disabled`]. If you want to disable
361        /// a specific timeout, use `TimeoutConfig::set_<type>(None)`.
362        ///
363        /// **Note: This only sets timeouts for calls to AWS services.** Timeouts for the credentials
364        /// provider chain are configured separately.
365        ///
366        /// # Examples
367        /// ```no_run
368        /// # use std::time::Duration;
369        /// # async fn create_config() {
370        /// use aws_config::timeout::TimeoutConfig;
371        ///
372        /// let config = aws_config::from_env()
373        ///    .timeout_config(
374        ///        TimeoutConfig::builder()
375        ///            .operation_timeout(Duration::from_secs(5))
376        ///            .build()
377        ///    )
378        ///    .load()
379        ///    .await;
380        /// # }
381        /// ```
382        pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self {
383            self.timeout_config = Some(timeout_config);
384            self
385        }
386
387        /// Override the sleep implementation for this [`ConfigLoader`].
388        ///
389        /// The sleep implementation is used to create timeout futures.
390        /// You generally won't need to change this unless you're using an async runtime other
391        /// than Tokio.
392        pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self {
393            // it's possible that we could wrapping an `Arc in an `Arc` and that's OK
394            self.sleep = Some(sleep.into_shared());
395            self
396        }
397
398        /// Set the time source used for tasks like signing requests.
399        ///
400        /// You generally won't need to change this unless you're compiling for a target
401        /// that can't provide a default, such as WASM, or unless you're writing a test against
402        /// the client that needs a fixed time.
403        pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self {
404            self.time_source = Some(time_source.into_shared());
405            self
406        }
407
408        /// Override the [`HttpClient`] for this [`ConfigLoader`].
409        ///
410        /// The HTTP client will be used for both AWS services and credentials providers.
411        ///
412        /// If you wish to use a separate HTTP client for credentials providers when creating clients,
413        /// then override the HTTP client set with this function on the client-specific `Config`s.
414        pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self {
415            self.http_client = Some(http_client.into_shared());
416            self
417        }
418
419        /// Sets the client protocol to use for serialization and deserialization.
420        ///
421        /// This overrides the default protocol determined by the service model.
422        ///
423        /// # Transport
424        ///
425        /// This setter is HTTP-specific. `self.protocol` is typed
426        /// `Option<SharedClientProtocol>` which elides to the HTTP specialization,
427        /// and only `SharedClientProtocol<http::Request, http::Response>` has a
428        /// `Storable` impl. The `impl ClientProtocol + 'static` bound here
429        /// elides to `impl ClientProtocol<http::Request, http::Response>` to
430        /// match. A non-HTTP transport would add its own setter paired with its
431        /// own `Storable` newtype rather than generalizing this one — the
432        /// underlying `ClientProtocol<Req, Res>` trait is already
433        /// transport-generic.
434        pub fn protocol(mut self, protocol: impl ClientProtocol + 'static) -> Self {
435            self.protocol = Some(SharedClientProtocol::new(protocol));
436            self
437        }
438
439        #[doc = docs_for!(auth_scheme_preference)]
440        ///
441        /// # Examples
442        /// ```no_run
443        /// # use aws_smithy_runtime_api::client::auth::AuthSchemeId;
444        /// # async fn create_config() {
445        /// let config = aws_config::from_env()
446        ///     // Favors a custom auth scheme over the SigV4 auth scheme.
447        ///     // Note: This will not result in an error, even if the custom scheme is missing from the resolved auth schemes.
448        ///     .auth_scheme_preference([AuthSchemeId::from("custom"), aws_runtime::auth::sigv4::SCHEME_ID])
449        ///     .load()
450        ///     .await;
451        /// # }
452        /// ```
453        pub fn auth_scheme_preference(
454            mut self,
455            auth_scheme_preference: impl Into<AuthSchemePreference>,
456        ) -> Self {
457            self.auth_scheme_preference = Some(auth_scheme_preference.into());
458            self
459        }
460
461        #[doc = docs_for!(sigv4a_signing_region_set)]
462        pub fn sigv4a_signing_region_set(
463            mut self,
464            sigv4a_signing_region_set: impl Into<SigningRegionSet>,
465        ) -> Self {
466            self.sigv4a_signing_region_set = Some(sigv4a_signing_region_set.into());
467            self
468        }
469
470        /// Override the identity cache used to build [`SdkConfig`].
471        ///
472        /// The identity cache caches AWS credentials and SSO tokens. By default, a lazy cache is used
473        /// that will load credentials upon first request, cache them, and then reload them during
474        /// another request when they are close to expiring.
475        ///
476        /// # Examples
477        ///
478        /// Change a setting on the default lazy caching implementation:
479        /// ```no_run
480        /// use aws_config::identity::IdentityCache;
481        /// use std::time::Duration;
482        ///
483        /// # async fn create_config() {
484        /// let config = aws_config::from_env()
485        ///     .identity_cache(
486        ///         IdentityCache::lazy()
487        ///             // Change the load timeout to 10 seconds.
488        ///             // Note: there are other timeouts that could trigger if the load timeout is too long.
489        ///             .load_timeout(Duration::from_secs(10))
490        ///             .build()
491        ///     )
492        ///     .load()
493        ///     .await;
494        /// # }
495        /// ```
496        pub fn identity_cache(
497            mut self,
498            identity_cache: impl ResolveCachedIdentity + 'static,
499        ) -> Self {
500            self.identity_cache = Some(identity_cache.into_shared());
501            self
502        }
503
504        /// Override the credentials provider used to build [`SdkConfig`].
505        ///
506        /// # Examples
507        ///
508        /// Override the credentials provider but load the default value for region:
509        /// ```no_run
510        /// # use aws_credential_types::Credentials;
511        /// # fn create_my_credential_provider() -> Credentials {
512        /// #     Credentials::new("example", "example", None, None, "example")
513        /// # }
514        /// # async fn create_config() {
515        /// let config = aws_config::from_env()
516        ///     .credentials_provider(create_my_credential_provider())
517        ///     .load()
518        ///     .await;
519        /// # }
520        /// ```
521        pub fn credentials_provider(
522            mut self,
523            credentials_provider: impl ProvideCredentials + 'static,
524        ) -> Self {
525            self.credentials_provider =
526                TriStateOption::Set(SharedCredentialsProvider::new(credentials_provider));
527            self
528        }
529
530        /// Don't use credentials to sign requests.
531        ///
532        /// Turning off signing with credentials is necessary in some cases, such as using
533        /// anonymous auth for S3, calling operations in STS that don't require a signature,
534        /// or using token-based auth.
535        ///
536        /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you
537        /// want. If credentials are disabled, requests cannot be signed. For these use cases, use
538        /// [`test_credentials`](Self::test_credentials).
539        ///
540        /// # Examples
541        ///
542        /// Turn off credentials in order to call a service without signing:
543        /// ```no_run
544        /// # async fn create_config() {
545        /// let config = aws_config::from_env()
546        ///     .no_credentials()
547        ///     .load()
548        ///     .await;
549        /// # }
550        /// ```
551        pub fn no_credentials(mut self) -> Self {
552            self.credentials_provider = TriStateOption::ExplicitlyUnset;
553            self
554        }
555
556        /// Set test credentials for use when signing requests
557        pub fn test_credentials(self) -> Self {
558            #[allow(unused_mut)]
559            let mut ret = self.credentials_provider(Credentials::for_tests());
560            #[cfg(feature = "sso")]
561            {
562                use aws_smithy_runtime_api::client::identity::http::Token;
563                ret = ret.token_provider(Token::for_tests());
564            }
565            ret
566        }
567
568        /// Ignore any environment variables on the host during config resolution
569        ///
570        /// This allows for testing in a reproducible environment that ensures any
571        /// environment variables from the host do not influence environment variable
572        /// resolution.
573        pub fn empty_test_environment(mut self) -> Self {
574            self.env = Some(Env::from_slice(&[]));
575            self
576        }
577
578        /// Override the access token provider used to build [`SdkConfig`].
579        ///
580        /// # Examples
581        ///
582        /// Override the token provider but load the default value for region:
583        /// ```no_run
584        /// # use aws_credential_types::Token;
585        /// # fn create_my_token_provider() -> Token {
586        /// #     Token::new("example", None)
587        /// # }
588        /// # async fn create_config() {
589        /// let config = aws_config::from_env()
590        ///     .token_provider(create_my_token_provider())
591        ///     .load()
592        ///     .await;
593        /// # }
594        /// ```
595        pub fn token_provider(mut self, token_provider: impl ProvideToken + 'static) -> Self {
596            self.token_provider = Some(SharedTokenProvider::new(token_provider));
597            self
598        }
599
600        /// Override the name of the app used to build [`SdkConfig`].
601        ///
602        /// This _optional_ name is used to identify the application in the user agent header that
603        /// gets sent along with requests.
604        ///
605        /// The app name is selected from an ordered list of sources:
606        /// 1. This override.
607        /// 2. The value of the `AWS_SDK_UA_APP_ID` environment variable.
608        /// 3. Profile files from the key `sdk_ua_app_id`
609        ///
610        /// If none of those sources are set the value is `None` and it is not added to the user agent header.
611        ///
612        /// # Examples
613        /// ```no_run
614        /// # async fn create_config() {
615        /// use aws_config::AppName;
616        /// let config = aws_config::from_env()
617        ///     .app_name(AppName::new("my-app-name").expect("valid app name"))
618        ///     .load().await;
619        /// # }
620        /// ```
621        pub fn app_name(mut self, app_name: AppName) -> Self {
622            self.app_name = Some(app_name);
623            self
624        }
625
626        /// Appends framework metadata to the user agent.
627        ///
628        /// This _optional_ metadata identifies a software framework or third-party library that is
629        /// being used with the SDK. It is rendered into the user agent (as `lib/{name}/{version}`)
630        /// so that libraries built on top of the AWS SDK can self-identify in the requests they
631        /// make. Each call appends another entry rather than replacing previous ones.
632        ///
633        /// Unlike the app name, framework metadata has no environment variable or profile source;
634        /// it can only be set programmatically.
635        ///
636        /// Entries are de-duplicated on `(name, version)`, rendered in first-seen order, and the
637        /// total number of unique entries included in the user agent is capped (currently at 10);
638        /// additional entries beyond the cap are dropped with a warning.
639        ///
640        /// # Examples
641        /// ```no_run
642        /// # async fn create_config() {
643        /// use aws_config::FrameworkMetadata;
644        /// let config = aws_config::from_env()
645        ///     .framework_metadata(FrameworkMetadata::new("some-framework", Some("1.0")).expect("valid framework metadata"))
646        ///     .load().await;
647        /// # }
648        /// ```
649        pub fn framework_metadata(mut self, framework_metadata: FrameworkMetadata) -> Self {
650            self.framework_metadata.push(framework_metadata);
651            self
652        }
653
654        /// Provides the ability to programmatically override the profile files that get loaded by the SDK.
655        ///
656        /// The [`Default`] for `ProfileFiles` includes the default SDK config and credential files located in
657        /// `~/.aws/config` and `~/.aws/credentials` respectively.
658        ///
659        /// Any number of config and credential files may be added to the `ProfileFiles` file set, with the
660        /// only requirement being that there is at least one of each. Profile file locations will produce an
661        /// error if they don't exist, but the default config/credentials files paths are exempt from this validation.
662        ///
663        /// # Example: Using a custom profile file path
664        ///
665        /// ```no_run
666        /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
667        /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind};
668        ///
669        /// # async fn example() {
670        /// let profile_files = ProfileFiles::builder()
671        ///     .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file")
672        ///     .build();
673        /// let sdk_config = aws_config::from_env()
674        ///     .profile_files(profile_files)
675        ///     .load()
676        ///     .await;
677        /// # }
678        #[allow(deprecated)]
679        pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
680            self.profile_files_override = Some(profile_files);
681            self
682        }
683
684        /// Override the profile name used by configuration providers
685        ///
686        /// Profile name is selected from an ordered list of sources:
687        /// 1. This override.
688        /// 2. The value of the `AWS_PROFILE` environment variable.
689        /// 3. `default`
690        ///
691        /// Each AWS profile has a name. For example, in the file below, the profiles are named
692        /// `dev`, `prod` and `staging`:
693        /// ```ini
694        /// [dev]
695        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
696        ///
697        /// [staging]
698        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
699        ///
700        /// [prod]
701        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
702        /// ```
703        ///
704        /// See [Named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
705        /// for more information about naming profiles.
706        ///
707        /// # Example: Using a custom profile name
708        ///
709        /// ```no_run
710        /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
711        ///
712        /// # async fn example() {
713        /// let sdk_config = aws_config::from_env()
714        ///     .profile_name("prod")
715        ///     .load()
716        ///     .await;
717        /// # }
718        pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
719            self.profile_name_override = Some(profile_name.into());
720            self
721        }
722
723        #[doc = docs_for!(account_id_endpoint_mode)]
724        pub fn account_id_endpoint_mode(
725            mut self,
726            account_id_endpoint_mode: AccountIdEndpointMode,
727        ) -> Self {
728            self.account_id_endpoint_mode = Some(account_id_endpoint_mode);
729            self
730        }
731
732        /// Override the endpoint URL used for **all** AWS services.
733        ///
734        /// This method will override the endpoint URL used for **all** AWS services. This primarily
735        /// exists to set a static endpoint for tools like `LocalStack`. When sending requests to
736        /// production AWS services, this method should only be used for service-specific behavior.
737        ///
738        /// When this method is used, the [`Region`](aws_types::region::Region) is only used for signing;
739        /// It is **not** used to route the request.
740        ///
741        /// # Examples
742        ///
743        /// Use a static endpoint for all services
744        /// ```no_run
745        /// # async fn create_config() {
746        /// let sdk_config = aws_config::from_env()
747        ///     .endpoint_url("http://localhost:1234")
748        ///     .load()
749        ///     .await;
750        /// # }
751        pub fn endpoint_url(mut self, endpoint_url: impl Into<String>) -> Self {
752            self.endpoint_url = Some(endpoint_url.into());
753            self
754        }
755
756        #[doc = docs_for!(use_fips)]
757        pub fn use_fips(mut self, use_fips: bool) -> Self {
758            self.use_fips = Some(use_fips);
759            self
760        }
761
762        #[doc = docs_for!(use_dual_stack)]
763        pub fn use_dual_stack(mut self, use_dual_stack: bool) -> Self {
764            self.use_dual_stack = Some(use_dual_stack);
765            self
766        }
767
768        #[doc = docs_for!(disable_request_compression)]
769        pub fn disable_request_compression(mut self, disable_request_compression: bool) -> Self {
770            self.disable_request_compression = Some(disable_request_compression);
771            self
772        }
773
774        #[doc = docs_for!(disable_clock_skew_correction)]
775        pub fn disable_clock_skew_correction(
776            mut self,
777            disable_clock_skew_correction: bool,
778        ) -> Self {
779            self.disable_clock_skew_correction = Some(disable_clock_skew_correction);
780            self
781        }
782
783        #[doc = docs_for!(request_min_compression_size_bytes)]
784        pub fn request_min_compression_size_bytes(mut self, size: u32) -> Self {
785            self.request_min_compression_size_bytes = Some(size);
786            self
787        }
788
789        /// Override the [`StalledStreamProtectionConfig`] used to build [`SdkConfig`].
790        ///
791        /// This configures stalled stream protection. When enabled, download streams
792        /// that stop (stream no data) for longer than a configured grace period will return an error.
793        ///
794        /// By default, streams that transmit less than one byte per-second for five seconds will
795        /// be cancelled.
796        ///
797        /// _Note_: When an override is provided, the default implementation is replaced.
798        ///
799        /// # Examples
800        /// ```no_run
801        /// # async fn create_config() {
802        /// use aws_config::stalled_stream_protection::StalledStreamProtectionConfig;
803        /// use std::time::Duration;
804        /// let config = aws_config::from_env()
805        ///     .stalled_stream_protection(
806        ///         StalledStreamProtectionConfig::enabled()
807        ///             .grace_period(Duration::from_secs(1))
808        ///             .build()
809        ///     )
810        ///     .load()
811        ///     .await;
812        /// # }
813        /// ```
814        pub fn stalled_stream_protection(
815            mut self,
816            stalled_stream_protection_config: StalledStreamProtectionConfig,
817        ) -> Self {
818            self.stalled_stream_protection_config = Some(stalled_stream_protection_config);
819            self
820        }
821
822        /// Set the checksum calculation strategy to use when making requests.
823        /// # Examples
824        /// ```
825        /// use aws_types::SdkConfig;
826        /// use aws_smithy_types::checksum_config::RequestChecksumCalculation;
827        /// let config = SdkConfig::builder().request_checksum_calculation(RequestChecksumCalculation::WhenSupported).build();
828        /// ```
829        pub fn request_checksum_calculation(
830            mut self,
831            request_checksum_calculation: RequestChecksumCalculation,
832        ) -> Self {
833            self.request_checksum_calculation = Some(request_checksum_calculation);
834            self
835        }
836
837        /// Set the checksum calculation strategy to use for responses.
838        /// # Examples
839        /// ```
840        /// use aws_types::SdkConfig;
841        /// use aws_smithy_types::checksum_config::ResponseChecksumValidation;
842        /// let config = SdkConfig::builder().response_checksum_validation(ResponseChecksumValidation::WhenSupported).build();
843        /// ```
844        pub fn response_checksum_validation(
845            mut self,
846            response_checksum_validation: ResponseChecksumValidation,
847        ) -> Self {
848            self.response_checksum_validation = Some(response_checksum_validation);
849            self
850        }
851
852        /// Load the default configuration chain
853        ///
854        /// If fields have been overridden during builder construction, the override values will be used.
855        ///
856        /// Otherwise, the default values for each field will be provided.
857        ///
858        /// NOTE: When an override is provided, the default implementation is **not** used as a fallback.
859        /// This means that if you provide a region provider that does not return a region, no region will
860        /// be set in the resulting [`SdkConfig`].
861        pub async fn load(self) -> SdkConfig {
862            let time_source = self.time_source.unwrap_or_default();
863
864            let sleep_impl = if self.sleep.is_some() {
865                self.sleep
866            } else {
867                if default_async_sleep().is_none() {
868                    tracing::warn!(
869                        "An implementation of AsyncSleep was requested by calling default_async_sleep \
870                         but no default was set.
871                         This happened when ConfigLoader::load was called during Config construction. \
872                         You can fix this by setting a sleep_impl on the ConfigLoader before calling \
873                         load or by enabling the rt-tokio feature"
874                    );
875                }
876                default_async_sleep()
877            };
878
879            let conf = self
880                .provider_config
881                .unwrap_or_else(|| {
882                    let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone())
883                        .with_fs(self.fs.unwrap_or_default())
884                        .with_env(self.env.unwrap_or_default());
885                    if let Some(http_client) = self.http_client.clone() {
886                        config = config.with_http_client(http_client);
887                    }
888                    config
889                })
890                .with_behavior_version(self.behavior_version)
891                .with_profile_config(self.profile_files_override, self.profile_name_override);
892
893            let use_fips = if let Some(use_fips) = self.use_fips {
894                Some(use_fips)
895            } else {
896                use_fips::use_fips_provider(&conf).await
897            };
898
899            let use_dual_stack = if let Some(use_dual_stack) = self.use_dual_stack {
900                Some(use_dual_stack)
901            } else {
902                use_dual_stack::use_dual_stack_provider(&conf).await
903            };
904
905            let conf = conf
906                .with_use_fips(use_fips)
907                .with_use_dual_stack(use_dual_stack);
908
909            let region = if let Some(provider) = self.region {
910                provider.region().await
911            } else {
912                region::Builder::default()
913                    .configure(&conf)
914                    .build()
915                    .region()
916                    .await
917            };
918            let conf = conf.with_region(region.clone());
919
920            let app_name = if self.app_name.is_some() {
921                self.app_name
922            } else {
923                app_name::default_provider()
924                    .configure(&conf)
925                    .app_name()
926                    .await
927            };
928
929            let disable_request_compression = if self.disable_request_compression.is_some() {
930                self.disable_request_compression
931            } else {
932                disable_request_compression::disable_request_compression_provider(&conf).await
933            };
934
935            let disable_clock_skew_correction = if self.disable_clock_skew_correction.is_some() {
936                self.disable_clock_skew_correction
937            } else {
938                disable_clock_skew_correction::disable_clock_skew_correction_provider(&conf).await
939            };
940
941            let request_min_compression_size_bytes =
942                if self.request_min_compression_size_bytes.is_some() {
943                    self.request_min_compression_size_bytes
944                } else {
945                    request_min_compression_size_bytes::request_min_compression_size_bytes_provider(
946                        &conf,
947                    )
948                    .await
949                };
950
951            let base_config = timeout_config::default_provider()
952                .configure(&conf)
953                .timeout_config()
954                .await;
955            let mut timeout_config = self
956                .timeout_config
957                .unwrap_or_else(|| TimeoutConfig::builder().build());
958            timeout_config.take_defaults_from(&base_config);
959
960            let (retry_config, retry_config_explicitly_set) = match self.retry_config {
961                Some(rc) => (rc, true),
962                None => (
963                    retry_config::default_provider()
964                        .configure(&conf)
965                        .retry_config()
966                        .await,
967                    false,
968                ),
969            };
970            let conf = conf
971                .with_retry_config(retry_config.clone())
972                .with_timeout_config(timeout_config.clone());
973
974            let credentials_provider = match self.credentials_provider {
975                TriStateOption::Set(provider) => Some(provider),
976                TriStateOption::NotSet => {
977                    let mut builder =
978                        credentials::DefaultCredentialsChain::builder().configure(conf.clone());
979                    builder.set_region(region.clone());
980                    Some(SharedCredentialsProvider::new(builder.build().await))
981                }
982                TriStateOption::ExplicitlyUnset => None,
983            };
984
985            let profiles = conf.profile().await;
986            let ignore_configured_endpoint_urls = if self.endpoint_url.is_some() {
987                // If an endpoint URL is set programmatically, the ignore flag is irrelevant
988                // because programmatic config always takes precedence.
989                false
990            } else {
991                ignore_ep::ignore_configured_endpoint_urls_provider(&conf)
992                    .await
993                    .unwrap_or_default()
994            };
995            let service_config = EnvServiceConfig {
996                env: conf.env(),
997                env_config_sections: profiles.cloned().unwrap_or_default(),
998                ignore_configured_endpoint_urls,
999            };
1000            let mut builder = SdkConfig::builder()
1001                .region(region.clone())
1002                .timeout_config(timeout_config)
1003                .time_source(time_source)
1004                .service_config(service_config);
1005
1006            if retry_config_explicitly_set {
1007                builder.insert_origin("retry_config", Origin::shared_config());
1008            }
1009            builder = builder.retry_config(retry_config);
1010
1011            // If an endpoint URL is set programmatically, then our work is done.
1012            let endpoint_url = if self.endpoint_url.is_some() {
1013                builder.insert_origin("endpoint_url", Origin::shared_config());
1014                self.endpoint_url
1015            } else if ignore_configured_endpoint_urls {
1016                // If yes, log a trace and return `None`.
1017                tracing::trace!(
1018                    "`ignore_configured_endpoint_urls` is set, any endpoint URLs configured in the environment will be ignored. \
1019                    NOTE: Endpoint URLs set programmatically WILL still be respected"
1020                );
1021                None
1022            } else {
1023                // Otherwise, attempt to resolve one.
1024                let (v, origin) = endpoint_url::endpoint_url_provider_with_origin(&conf).await;
1025                builder.insert_origin("endpoint_url", origin);
1026                v
1027            };
1028
1029            let token_provider = match self.token_provider {
1030                Some(provider) => {
1031                    builder.insert_origin("token_provider", Origin::shared_config());
1032                    Some(provider)
1033                }
1034                None => {
1035                    #[cfg(feature = "sso")]
1036                    {
1037                        let mut builder =
1038                            crate::default_provider::token::DefaultTokenChain::builder()
1039                                .configure(conf.clone());
1040                        builder.set_region(region);
1041                        Some(SharedTokenProvider::new(builder.build().await))
1042                    }
1043                    #[cfg(not(feature = "sso"))]
1044                    {
1045                        None
1046                    }
1047                    // Not setting `Origin` in this arm, and that's good for now as long as we know
1048                    // it's not programmatically set in the shared config.
1049                    // We can consider adding `Origin::Default` if needed.
1050                }
1051            };
1052
1053            builder.set_endpoint_url(endpoint_url);
1054            builder.set_behavior_version(self.behavior_version);
1055            builder.set_http_client(self.http_client);
1056            builder.set_protocol(self.protocol);
1057            builder.set_app_name(app_name);
1058            builder.set_framework_metadata(self.framework_metadata);
1059
1060            let identity_cache = match self.identity_cache {
1061                None => match self.behavior_version {
1062                    #[allow(deprecated)]
1063                    Some(bv) if bv.is_at_least(BehaviorVersion::v2024_03_28()) => {
1064                        Some(IdentityCache::lazy().build())
1065                    }
1066                    _ => None,
1067                },
1068                Some(user_cache) => Some(user_cache),
1069            };
1070
1071            let request_checksum_calculation =
1072                if let Some(request_checksum_calculation) = self.request_checksum_calculation {
1073                    Some(request_checksum_calculation)
1074                } else {
1075                    checksums::request_checksum_calculation_provider(&conf).await
1076                };
1077
1078            let response_checksum_validation =
1079                if let Some(response_checksum_validation) = self.response_checksum_validation {
1080                    Some(response_checksum_validation)
1081                } else {
1082                    checksums::response_checksum_validation_provider(&conf).await
1083                };
1084
1085            let account_id_endpoint_mode =
1086                if let Some(acccount_id_endpoint_mode) = self.account_id_endpoint_mode {
1087                    Some(acccount_id_endpoint_mode)
1088                } else {
1089                    account_id_endpoint_mode::account_id_endpoint_mode_provider(&conf).await
1090                };
1091
1092            let auth_scheme_preference =
1093                if let Some(auth_scheme_preference) = self.auth_scheme_preference {
1094                    builder.insert_origin("auth_scheme_preference", Origin::shared_config());
1095                    Some(auth_scheme_preference)
1096                } else {
1097                    auth_scheme_preference::auth_scheme_preference_provider(&conf).await
1098                    // Not setting `Origin` in this arm, and that's good for now as long as we know
1099                    // it's not programmatically set in the shared config.
1100                };
1101
1102            let sigv4a_signing_region_set =
1103                if let Some(sigv4a_signing_region_set) = self.sigv4a_signing_region_set {
1104                    Some(sigv4a_signing_region_set)
1105                } else {
1106                    sigv4a_signing_region_set::sigv4a_signing_region_set_provider(&conf).await
1107                };
1108
1109            builder.set_request_checksum_calculation(request_checksum_calculation);
1110            builder.set_response_checksum_validation(response_checksum_validation);
1111            builder.set_identity_cache(identity_cache);
1112            builder.set_credentials_provider(credentials_provider);
1113            builder.set_token_provider(token_provider);
1114            builder.set_sleep_impl(sleep_impl);
1115            builder.set_use_fips(use_fips);
1116            builder.set_use_dual_stack(use_dual_stack);
1117            builder.set_disable_request_compression(disable_request_compression);
1118            builder.set_disable_clock_skew_correction(disable_clock_skew_correction);
1119            builder.set_request_min_compression_size_bytes(request_min_compression_size_bytes);
1120            builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
1121            builder.set_account_id_endpoint_mode(account_id_endpoint_mode);
1122            builder.set_auth_scheme_preference(auth_scheme_preference);
1123            builder.set_sigv4a_signing_region_set(sigv4a_signing_region_set);
1124            builder.build()
1125        }
1126    }
1127
1128    #[cfg(any(test, feature = "test-util"))]
1129    impl ConfigLoader {
1130        /// Override the environment variables used during config resolution.
1131        ///
1132        /// This is intended for testing only.
1133        pub fn env(mut self, env: Env) -> Self {
1134            self.env = Some(env);
1135            self
1136        }
1137
1138        /// Override the filesystem used during config resolution.
1139        ///
1140        /// This is intended for testing only.
1141        pub fn fs(mut self, fs: Fs) -> Self {
1142            self.fs = Some(fs);
1143            self
1144        }
1145    }
1146
1147    #[cfg(test)]
1148    mod test {
1149        #[allow(deprecated)]
1150        use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
1151        use crate::test_case::{no_traffic_client, InstantSleep};
1152        use crate::BehaviorVersion;
1153        use crate::{defaults, ConfigLoader};
1154        use aws_credential_types::provider::ProvideCredentials;
1155        use aws_smithy_async::rt::sleep::TokioSleep;
1156        use aws_smithy_async::test_util::tick_advance_sleep::tick_advance_time_and_sleep;
1157        use aws_smithy_http_client::test_util::{infallible_client_fn, NeverClient};
1158        use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
1159        use aws_smithy_runtime_api::client::identity::{
1160            ResolveCachedIdentity, SharedIdentityResolver,
1161        };
1162        use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
1163        use aws_types::app_name::AppName;
1164        use aws_types::origin::Origin;
1165        use aws_types::os_shim_internal::{Env, Fs};
1166        use aws_types::sdk_config::{RequestChecksumCalculation, ResponseChecksumValidation};
1167        use std::sync::atomic::{AtomicUsize, Ordering};
1168        use std::sync::Arc;
1169        use std::time::Duration;
1170
1171        #[tokio::test]
1172        async fn provider_config_used() {
1173            let (_guard, logs_rx) = capture_test_logs();
1174            let env = Env::from_slice(&[
1175                ("AWS_MAX_ATTEMPTS", "10"),
1176                ("AWS_REGION", "us-west-4"),
1177                ("AWS_ACCESS_KEY_ID", "akid"),
1178                ("AWS_SECRET_ACCESS_KEY", "secret"),
1179            ]);
1180            let fs =
1181                Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]);
1182            let loader = defaults(BehaviorVersion::latest())
1183                .sleep_impl(TokioSleep::new())
1184                .env(env)
1185                .fs(fs)
1186                .http_client(NeverClient::new())
1187                .profile_name("custom")
1188                .profile_files(
1189                    #[allow(deprecated)]
1190                    ProfileFiles::builder()
1191                        .with_file(
1192                            #[allow(deprecated)]
1193                            ProfileFileKind::Config,
1194                            "test_config",
1195                        )
1196                        .build(),
1197                )
1198                .load()
1199                .await;
1200            assert_eq!(10, loader.retry_config().unwrap().max_attempts());
1201            assert_eq!("us-west-4", loader.region().unwrap().as_ref());
1202            assert_eq!(
1203                "akid",
1204                loader
1205                    .credentials_provider()
1206                    .unwrap()
1207                    .provide_credentials()
1208                    .await
1209                    .unwrap()
1210                    .access_key_id(),
1211            );
1212            assert_eq!(Some(&AppName::new("correct").unwrap()), loader.app_name());
1213
1214            let num_config_loader_logs = logs_rx.contents()
1215                .lines()
1216                // The logger uses fancy formatting, so we have to account for that.
1217                .filter(|l| l.contains("config file loaded \u{1b}[3mpath\u{1b}[0m\u{1b}[2m=\u{1b}[0mSome(\"test_config\") \u{1b}[3msize\u{1b}[0m\u{1b}[2m=\u{1b}"))
1218                .count();
1219
1220            match num_config_loader_logs {
1221                0 => panic!("no config file logs found!"),
1222                1 => (),
1223                more => panic!("the config file was parsed more than once! (parsed {more})",),
1224            };
1225        }
1226
1227        fn base_conf() -> ConfigLoader {
1228            defaults(BehaviorVersion::latest())
1229                .sleep_impl(InstantSleep)
1230                .http_client(no_traffic_client())
1231        }
1232
1233        #[tokio::test]
1234        async fn test_origin_programmatic() {
1235            let _ = tracing_subscriber::fmt::try_init();
1236            let loader = base_conf()
1237                .test_credentials()
1238                .profile_name("custom")
1239                .profile_files(
1240                    #[allow(deprecated)]
1241                    ProfileFiles::builder()
1242                        .with_contents(
1243                            #[allow(deprecated)]
1244                            ProfileFileKind::Config,
1245                            "[profile custom]\nendpoint_url = http://localhost:8989",
1246                        )
1247                        .build(),
1248                )
1249                .endpoint_url("http://localhost:1111")
1250                .load()
1251                .await;
1252            assert_eq!(Origin::shared_config(), loader.get_origin("endpoint_url"));
1253        }
1254
1255        #[tokio::test]
1256        async fn test_origin_env() {
1257            let _ = tracing_subscriber::fmt::try_init();
1258            let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://localhost:7878")]);
1259            let loader = base_conf()
1260                .test_credentials()
1261                .env(env)
1262                .profile_name("custom")
1263                .profile_files(
1264                    #[allow(deprecated)]
1265                    ProfileFiles::builder()
1266                        .with_contents(
1267                            #[allow(deprecated)]
1268                            ProfileFileKind::Config,
1269                            "[profile custom]\nendpoint_url = http://localhost:8989",
1270                        )
1271                        .build(),
1272                )
1273                .load()
1274                .await;
1275            assert_eq!(
1276                Origin::shared_environment_variable(),
1277                loader.get_origin("endpoint_url")
1278            );
1279        }
1280
1281        #[tokio::test]
1282        async fn test_origin_fs() {
1283            let _ = tracing_subscriber::fmt::try_init();
1284            let loader = base_conf()
1285                .test_credentials()
1286                .profile_name("custom")
1287                .profile_files(
1288                    #[allow(deprecated)]
1289                    ProfileFiles::builder()
1290                        .with_contents(
1291                            #[allow(deprecated)]
1292                            ProfileFileKind::Config,
1293                            "[profile custom]\nendpoint_url = http://localhost:8989",
1294                        )
1295                        .build(),
1296                )
1297                .load()
1298                .await;
1299            assert_eq!(
1300                Origin::shared_profile_file(),
1301                loader.get_origin("endpoint_url")
1302            );
1303        }
1304
1305        #[tokio::test]
1306        async fn load_use_fips() {
1307            let conf = base_conf().use_fips(true).load().await;
1308            assert_eq!(Some(true), conf.use_fips());
1309        }
1310
1311        #[tokio::test]
1312        async fn load_dual_stack() {
1313            let conf = base_conf().use_dual_stack(false).load().await;
1314            assert_eq!(Some(false), conf.use_dual_stack());
1315
1316            let conf = base_conf().load().await;
1317            assert_eq!(None, conf.use_dual_stack());
1318        }
1319
1320        #[tokio::test]
1321        async fn load_disable_request_compression() {
1322            let conf = base_conf().disable_request_compression(true).load().await;
1323            assert_eq!(Some(true), conf.disable_request_compression());
1324
1325            let conf = base_conf().load().await;
1326            assert_eq!(None, conf.disable_request_compression());
1327        }
1328
1329        #[tokio::test]
1330        async fn load_request_min_compression_size_bytes() {
1331            let conf = base_conf()
1332                .request_min_compression_size_bytes(99)
1333                .load()
1334                .await;
1335            assert_eq!(Some(99), conf.request_min_compression_size_bytes());
1336
1337            let conf = base_conf().load().await;
1338            assert_eq!(None, conf.request_min_compression_size_bytes());
1339        }
1340
1341        #[tokio::test]
1342        async fn app_name() {
1343            let app_name = AppName::new("my-app-name").unwrap();
1344            let conf = base_conf().app_name(app_name.clone()).load().await;
1345            assert_eq!(Some(&app_name), conf.app_name());
1346        }
1347
1348        #[tokio::test]
1349        async fn framework_metadata() {
1350            use aws_types::sdk_ua_metadata::FrameworkMetadata;
1351
1352            let one = FrameworkMetadata::new("framework-one", Some("1.0")).unwrap();
1353            let two = FrameworkMetadata::new("framework-two", Some("2.0")).unwrap();
1354            let conf = base_conf()
1355                .framework_metadata(one.clone())
1356                .framework_metadata(two.clone())
1357                .load()
1358                .await;
1359            assert_eq!(&[one, two], conf.framework_metadata());
1360        }
1361
1362        #[tokio::test]
1363        async fn request_checksum_calculation() {
1364            let conf = base_conf()
1365                .request_checksum_calculation(RequestChecksumCalculation::WhenRequired)
1366                .load()
1367                .await;
1368            assert_eq!(
1369                Some(RequestChecksumCalculation::WhenRequired),
1370                conf.request_checksum_calculation()
1371            );
1372        }
1373
1374        #[tokio::test]
1375        async fn response_checksum_validation() {
1376            let conf = base_conf()
1377                .response_checksum_validation(ResponseChecksumValidation::WhenRequired)
1378                .load()
1379                .await;
1380            assert_eq!(
1381                Some(ResponseChecksumValidation::WhenRequired),
1382                conf.response_checksum_validation()
1383            );
1384        }
1385
1386        #[cfg(feature = "default-https-client")]
1387        #[tokio::test]
1388        async fn disable_default_credentials() {
1389            let config = defaults(BehaviorVersion::latest())
1390                .no_credentials()
1391                .load()
1392                .await;
1393            assert!(config.credentials_provider().is_none());
1394        }
1395
1396        #[cfg(feature = "default-https-client")]
1397        #[tokio::test]
1398        async fn identity_cache_defaulted() {
1399            let config = defaults(BehaviorVersion::latest()).load().await;
1400
1401            assert!(config.identity_cache().is_some());
1402        }
1403
1404        #[cfg(feature = "default-https-client")]
1405        #[allow(deprecated)]
1406        #[tokio::test]
1407        async fn identity_cache_old_behavior_version() {
1408            // Previously, `load()` did not need an explicit HTTP client because
1409            // internal providers (e.g. IMDS) built their Operation without a
1410            // BehaviorVersion, so default_plugins defaulted to latest() and the
1411            // `default-https-client` code path provided one automatically.
1412            //
1413            // Now that BehaviorVersion is threaded through to Operation::builder(),
1414            // the old BV here (v2023_11_09) causes default_plugins to use the
1415            // legacy hyper 0.14 code path, which returns None without
1416            // `legacy-rustls-ring`. NeverClient satisfies the debug_assertions
1417            // check in Operation::build() without additional TLS dependencies.
1418            let config = defaults(BehaviorVersion::v2023_11_09())
1419                .http_client(NeverClient::new())
1420                .sleep_impl(InstantSleep)
1421                .load()
1422                .await;
1423
1424            assert!(config.identity_cache().is_none());
1425        }
1426
1427        #[tokio::test]
1428        async fn connector_is_shared() {
1429            let num_requests = Arc::new(AtomicUsize::new(0));
1430            let movable = num_requests.clone();
1431            let http_client = infallible_client_fn(move |_req| {
1432                movable.fetch_add(1, Ordering::Relaxed);
1433                http::Response::new("ok!")
1434            });
1435            let config = defaults(BehaviorVersion::latest())
1436                .fs(Fs::from_slice(&[]))
1437                .env(Env::from_slice(&[]))
1438                .http_client(http_client.clone())
1439                .load()
1440                .await;
1441            config
1442                .credentials_provider()
1443                .unwrap()
1444                .provide_credentials()
1445                .await
1446                .expect_err("did not expect credentials to be loaded—no traffic is allowed");
1447            let num_requests = num_requests.load(Ordering::Relaxed);
1448            assert!(num_requests > 0, "{}", num_requests);
1449        }
1450
1451        #[tokio::test]
1452        async fn endpoint_urls_may_be_ignored_from_env() {
1453            let fs = Fs::from_slice(&[(
1454                "test_config",
1455                "[profile custom]\nendpoint_url = http://profile",
1456            )]);
1457            let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1458
1459            let conf = base_conf().use_dual_stack(false).load().await;
1460            assert_eq!(Some(false), conf.use_dual_stack());
1461
1462            let conf = base_conf().load().await;
1463            assert_eq!(None, conf.use_dual_stack());
1464
1465            // Check that we get nothing back because the env said we should ignore endpoints
1466            let config = base_conf()
1467                .fs(fs.clone())
1468                .env(env)
1469                .profile_name("custom")
1470                .profile_files(
1471                    #[allow(deprecated)]
1472                    ProfileFiles::builder()
1473                        .with_file(
1474                            #[allow(deprecated)]
1475                            ProfileFileKind::Config,
1476                            "test_config",
1477                        )
1478                        .build(),
1479                )
1480                .load()
1481                .await;
1482            assert_eq!(None, config.endpoint_url());
1483
1484            // Check that without the env, we DO get something back
1485            let config = base_conf()
1486                .fs(fs)
1487                .profile_name("custom")
1488                .profile_files(
1489                    #[allow(deprecated)]
1490                    ProfileFiles::builder()
1491                        .with_file(
1492                            #[allow(deprecated)]
1493                            ProfileFileKind::Config,
1494                            "test_config",
1495                        )
1496                        .build(),
1497                )
1498                .load()
1499                .await;
1500            assert_eq!(Some("http://profile"), config.endpoint_url());
1501        }
1502
1503        #[tokio::test]
1504        async fn endpoint_urls_may_be_ignored_from_profile() {
1505            let fs = Fs::from_slice(&[(
1506                "test_config",
1507                "[profile custom]\nignore_configured_endpoint_urls = true",
1508            )]);
1509            let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://environment")]);
1510
1511            // Check that we get nothing back because the profile said we should ignore endpoints
1512            let config = base_conf()
1513                .fs(fs)
1514                .env(env.clone())
1515                .profile_name("custom")
1516                .profile_files(
1517                    #[allow(deprecated)]
1518                    ProfileFiles::builder()
1519                        .with_file(
1520                            #[allow(deprecated)]
1521                            ProfileFileKind::Config,
1522                            "test_config",
1523                        )
1524                        .build(),
1525                )
1526                .load()
1527                .await;
1528            assert_eq!(None, config.endpoint_url());
1529
1530            // Check that without the profile, we DO get something back
1531            let config = base_conf().env(env).load().await;
1532            assert_eq!(Some("http://environment"), config.endpoint_url());
1533        }
1534
1535        #[tokio::test]
1536        async fn programmatic_endpoint_urls_may_not_be_ignored() {
1537            let fs = Fs::from_slice(&[(
1538                "test_config",
1539                "[profile custom]\nignore_configured_endpoint_urls = true",
1540            )]);
1541            let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1542
1543            // Check that we get something back because we explicitly set the loader's endpoint URL
1544            let config = base_conf()
1545                .fs(fs)
1546                .env(env)
1547                .endpoint_url("http://localhost")
1548                .profile_name("custom")
1549                .profile_files(
1550                    #[allow(deprecated)]
1551                    ProfileFiles::builder()
1552                        .with_file(
1553                            #[allow(deprecated)]
1554                            ProfileFileKind::Config,
1555                            "test_config",
1556                        )
1557                        .build(),
1558                )
1559                .load()
1560                .await;
1561            assert_eq!(Some("http://localhost"), config.endpoint_url());
1562        }
1563
1564        #[tokio::test]
1565        async fn retry_config_propagated_to_inner_sts_client() {
1566            let request_count = Arc::new(AtomicUsize::new(0));
1567            let counter = request_count.clone();
1568
1569            // Return STS Throttling error for every request
1570            let http_client = infallible_client_fn(move |_req| {
1571                counter.fetch_add(1, Ordering::Relaxed);
1572                http::Response::builder()
1573                    .status(400)
1574                    .body(
1575                        r#"<ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
1576                            <Error>
1577                                <Type>Sender</Type>
1578                                <Code>Throttling</Code>
1579                                <Message>Rate exceeded</Message>
1580                            </Error>
1581                            <RequestId>test-request-id</RequestId>
1582                        </ErrorResponse>"#,
1583                    )
1584                    .unwrap()
1585            });
1586
1587            // Set up web identity token env vars + AWS_MAX_ATTEMPTS=5
1588            let env = Env::from_slice(&[
1589                ("AWS_WEB_IDENTITY_TOKEN_FILE", "/token.jwt"),
1590                ("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/test-role"),
1591                ("AWS_ROLE_SESSION_NAME", "test-session"),
1592                ("AWS_REGION", "us-east-1"),
1593                ("AWS_MAX_ATTEMPTS", "5"),
1594            ]);
1595            let fs = Fs::from_slice(&[("/token.jwt", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test")]);
1596
1597            let config = defaults(BehaviorVersion::latest())
1598                .sleep_impl(InstantSleep)
1599                .http_client(http_client)
1600                .env(env)
1601                .fs(fs)
1602                .load()
1603                .await;
1604
1605            // Attempt to load credentials — will fail because all responses are throttled
1606            let _ = config
1607                .credentials_provider()
1608                .unwrap()
1609                .provide_credentials()
1610                .await;
1611
1612            // The inner STS client should have made 5 attempts (not the default 3),
1613            // proving that AWS_MAX_ATTEMPTS propagated to the inner STS client.
1614            assert_eq!(5, request_count.load(Ordering::Relaxed));
1615        }
1616
1617        #[tokio::test]
1618        async fn pessimistic_load_timeout_allows_retries_to_complete() {
1619            let (time_source, sleep_impl) = tick_advance_time_and_sleep();
1620
1621            let request_count = Arc::new(AtomicUsize::new(0));
1622            let counter = request_count.clone();
1623
1624            // Return STS Throttling error for first 4 attempts, succeed on 5th
1625            let http_client = infallible_client_fn(move |_req| {
1626                let count = counter.fetch_add(1, Ordering::Relaxed) + 1;
1627                if count < 5 {
1628                    http::Response::builder()
1629                        .status(400)
1630                        .body(
1631                            r#"<ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
1632                                <Error>
1633                                    <Type>Sender</Type>
1634                                    <Code>Throttling</Code>
1635                                    <Message>Rate exceeded</Message>
1636                                </Error>
1637                                <RequestId>test-request-id</RequestId>
1638                            </ErrorResponse>"#,
1639                        )
1640                        .unwrap()
1641                } else {
1642                    http::Response::builder()
1643                        .status(200)
1644                        .body(
1645                            r#"<AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
1646                                <AssumeRoleWithWebIdentityResult>
1647                                    <Credentials>
1648                                        <AccessKeyId>ASIATESTACCESSKEYID</AccessKeyId>
1649                                        <SecretAccessKey>TESTSECRETKEY</SecretAccessKey>
1650                                        <SessionToken>TESTSESSIONTOKEN</SessionToken>
1651                                        <Expiration>2099-01-01T00:00:00Z</Expiration>
1652                                    </Credentials>
1653                                </AssumeRoleWithWebIdentityResult>
1654                            </AssumeRoleWithWebIdentityResponse>"#,
1655                        )
1656                        .unwrap()
1657                }
1658            });
1659
1660            let env = Env::from_slice(&[
1661                ("AWS_WEB_IDENTITY_TOKEN_FILE", "/token.jwt"),
1662                ("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/test-role"),
1663                ("AWS_ROLE_SESSION_NAME", "test-session"),
1664                ("AWS_REGION", "us-east-1"),
1665                ("AWS_MAX_ATTEMPTS", "5"),
1666            ]);
1667            let fs = Fs::from_slice(&[("/token.jwt", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test")]);
1668
1669            let config = defaults(BehaviorVersion::latest())
1670                .sleep_impl(sleep_impl.clone())
1671                .time_source(time_source.clone())
1672                .http_client(http_client)
1673                .env(env)
1674                .fs(fs)
1675                .load()
1676                .await;
1677
1678            // Exercise the LazyCache path by calling resolve_cached_identity directly,
1679            // which is what the orchestrator does during an operation.
1680            let identity_cache = config
1681                .identity_cache()
1682                .expect("identity cache should be set");
1683            let credentials_provider = config.credentials_provider().unwrap();
1684            let identity_resolver = SharedIdentityResolver::new(credentials_provider.clone());
1685
1686            let runtime_components = RuntimeComponentsBuilder::for_tests()
1687                .with_time_source(Some(time_source.clone()))
1688                .with_sleep_impl(Some(sleep_impl.clone()))
1689                .build()
1690                .unwrap();
1691
1692            let mut config_bag = aws_smithy_types::config_bag::ConfigBag::base();
1693            config_bag
1694                .interceptor_state()
1695                .store_put(aws_smithy_types::retry::RetryConfig::standard().with_max_attempts(5));
1696
1697            // Spawn identity resolution through the cache (includes timeout)
1698            let task = tokio::spawn(async move {
1699                identity_cache
1700                    .resolve_cached_identity(identity_resolver, &runtime_components, &config_bag)
1701                    .await
1702            });
1703            tokio::task::yield_now().await;
1704
1705            // Advance time enough for all retry backoffs to complete.
1706            // 5 attempts with 1s exponential backoff: 1+2+4+8 = 15s total backoff.
1707            // This exceeds the old 5s load_timeout but fits within the pessimistic
1708            // timeout (~46s for 5 attempts with 3.1s connect_timeout).
1709            time_source.tick(Duration::from_secs(60)).await;
1710
1711            let identity = task
1712                .await
1713                .unwrap()
1714                .expect("identity should resolve — pessimistic timeout gives retries room");
1715
1716            assert!(identity.expiration().is_some());
1717            assert_eq!(5, request_count.load(Ordering::Relaxed));
1718        }
1719    }
1720}