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