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    SdkConfig,
108};
109/// Load default sources for all configuration with override support
110pub use loader::ConfigLoader;
111
112/// Types for configuring identity caching.
113pub mod identity {
114    pub use aws_smithy_runtime::client::identity::IdentityCache;
115    pub use aws_smithy_runtime::client::identity::LazyCacheBuilder;
116}
117
118#[allow(dead_code)]
119const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
120
121mod http_credential_provider;
122mod json_credentials;
123#[cfg(test)]
124mod test_case;
125
126pub mod credential_process;
127pub mod default_provider;
128pub mod ecs;
129mod env_service_config;
130pub mod environment;
131pub mod imds;
132#[cfg(feature = "credentials-login")]
133pub mod login;
134pub mod meta;
135pub mod profile;
136pub mod provider_config;
137pub mod retry;
138mod sensitive_command;
139#[cfg(feature = "sso")]
140pub mod sso;
141pub mod stalled_stream_protection;
142pub mod sts;
143pub mod timeout;
144pub mod web_identity_token;
145
146/// Create a config loader with the _latest_ defaults.
147///
148/// This loader will always set [`BehaviorVersion::latest`].
149///
150/// 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).
151///
152/// # Examples
153/// ```no_run
154/// # async fn create_config() {
155/// let config = aws_config::from_env().region("us-east-1").load().await;
156/// # }
157/// ```
158#[cfg(feature = "behavior-version-latest")]
159pub fn from_env() -> ConfigLoader {
160    ConfigLoader::default().behavior_version(BehaviorVersion::latest())
161}
162
163/// Load default configuration with the _latest_ defaults.
164///
165/// Convenience wrapper equivalent to `aws_config::load_defaults(BehaviorVersion::latest()).await`
166///
167/// 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).
168#[cfg(feature = "behavior-version-latest")]
169pub async fn load_from_env() -> SdkConfig {
170    from_env().load().await
171}
172
173/// Create a config loader with the _latest_ defaults.
174#[cfg(not(feature = "behavior-version-latest"))]
175#[deprecated(
176    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."
177)]
178pub fn from_env() -> ConfigLoader {
179    ConfigLoader::default().behavior_version(BehaviorVersion::latest())
180}
181
182/// Load default configuration with the _latest_ defaults.
183#[cfg(not(feature = "behavior-version-latest"))]
184#[deprecated(
185    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."
186)]
187pub async fn load_from_env() -> SdkConfig {
188    load_defaults(BehaviorVersion::latest()).await
189}
190
191/// Create a config loader with the defaults for the given behavior version.
192///
193/// 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).
194///
195/// # Examples
196/// ```no_run
197/// # async fn create_config() {
198/// use aws_config::BehaviorVersion;
199/// let config = aws_config::defaults(BehaviorVersion::v2023_11_09())
200///     .region("us-east-1")
201///     .load()
202///     .await;
203/// # }
204/// ```
205pub fn defaults(version: BehaviorVersion) -> ConfigLoader {
206    ConfigLoader::default().behavior_version(version)
207}
208
209/// Load default configuration with the given behavior version.
210///
211/// Convenience wrapper equivalent to `aws_config::defaults(behavior_version).load().await`
212///
213/// 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).
214pub async fn load_defaults(version: BehaviorVersion) -> SdkConfig {
215    defaults(version).load().await
216}
217
218mod loader {
219    use crate::env_service_config::EnvServiceConfig;
220    use aws_credential_types::provider::{
221        token::{ProvideToken, SharedTokenProvider},
222        ProvideCredentials, SharedCredentialsProvider,
223    };
224    use aws_credential_types::Credentials;
225    use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
226    use aws_smithy_async::time::{SharedTimeSource, TimeSource};
227    use aws_smithy_runtime::client::identity::IdentityCache;
228    use aws_smithy_runtime_api::client::auth::AuthSchemePreference;
229    use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
230    use aws_smithy_runtime_api::client::http::HttpClient;
231    use aws_smithy_runtime_api::client::identity::{ResolveCachedIdentity, SharedIdentityCache};
232    use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
233    use aws_smithy_runtime_api::shared::IntoShared;
234    use aws_smithy_schema::protocol::{ClientProtocol, SharedClientProtocol};
235    use aws_smithy_types::checksum_config::{
236        RequestChecksumCalculation, ResponseChecksumValidation,
237    };
238    use aws_smithy_types::retry::RetryConfig;
239    use aws_smithy_types::timeout::TimeoutConfig;
240    use aws_types::app_name::AppName;
241    use aws_types::docs_for;
242    use aws_types::endpoint_config::AccountIdEndpointMode;
243    use aws_types::origin::Origin;
244    use aws_types::os_shim_internal::{Env, Fs};
245    use aws_types::region::SigningRegionSet;
246    use aws_types::sdk_config::SharedHttpClient;
247    use aws_types::SdkConfig;
248
249    use crate::default_provider::{
250        account_id_endpoint_mode, app_name, auth_scheme_preference, checksums, credentials,
251        disable_request_compression, endpoint_url, ignore_configured_endpoint_urls as ignore_ep,
252        region, request_min_compression_size_bytes, retry_config, sigv4a_signing_region_set,
253        timeout_config, use_dual_stack, use_fips,
254    };
255    use crate::meta::region::ProvideRegion;
256    #[allow(deprecated)]
257    use crate::profile::profile_file::ProfileFiles;
258    use crate::provider_config::ProviderConfig;
259
260    #[derive(Default, Debug)]
261    enum TriStateOption<T> {
262        /// No option was set by the user. We can set up the default.
263        #[default]
264        NotSet,
265        /// The option was explicitly unset. Do not set up a default.
266        ExplicitlyUnset,
267        /// Use the given user provided option.
268        Set(T),
269    }
270
271    /// Load a cross-service [`SdkConfig`] from the environment
272    ///
273    /// This builder supports overriding individual components of the generated config. Overriding a component
274    /// will skip the standard resolution chain from **for that component**. For example,
275    /// if you override the region provider, _even if that provider returns None_, the default region provider
276    /// chain will not be used.
277    #[derive(Default, Debug)]
278    pub struct ConfigLoader {
279        app_name: Option<AppName>,
280        auth_scheme_preference: Option<AuthSchemePreference>,
281        sigv4a_signing_region_set: Option<SigningRegionSet>,
282        identity_cache: Option<SharedIdentityCache>,
283        credentials_provider: TriStateOption<SharedCredentialsProvider>,
284        token_provider: Option<SharedTokenProvider>,
285        account_id_endpoint_mode: Option<AccountIdEndpointMode>,
286        endpoint_url: Option<String>,
287        region: Option<Box<dyn ProvideRegion>>,
288        retry_config: Option<RetryConfig>,
289        sleep: Option<SharedAsyncSleep>,
290        timeout_config: Option<TimeoutConfig>,
291        provider_config: Option<ProviderConfig>,
292        http_client: Option<SharedHttpClient>,
293        profile_name_override: Option<String>,
294        #[allow(deprecated)]
295        profile_files_override: Option<ProfileFiles>,
296        use_fips: Option<bool>,
297        use_dual_stack: Option<bool>,
298        time_source: Option<SharedTimeSource>,
299        disable_request_compression: Option<bool>,
300        request_min_compression_size_bytes: Option<u32>,
301        stalled_stream_protection_config: Option<StalledStreamProtectionConfig>,
302        env: Option<Env>,
303        fs: Option<Fs>,
304        behavior_version: Option<BehaviorVersion>,
305        request_checksum_calculation: Option<RequestChecksumCalculation>,
306        response_checksum_validation: Option<ResponseChecksumValidation>,
307        protocol: Option<SharedClientProtocol>,
308    }
309
310    impl ConfigLoader {
311        /// Sets the [`BehaviorVersion`] used to build [`SdkConfig`].
312        pub fn behavior_version(mut self, behavior_version: BehaviorVersion) -> Self {
313            self.behavior_version = Some(behavior_version);
314            self
315        }
316
317        /// Override the region used to build [`SdkConfig`].
318        ///
319        /// # Examples
320        /// ```no_run
321        /// # async fn create_config() {
322        /// use aws_types::region::Region;
323        /// let config = aws_config::from_env()
324        ///     .region(Region::new("us-east-1"))
325        ///     .load().await;
326        /// # }
327        /// ```
328        pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
329            self.region = Some(Box::new(region));
330            self
331        }
332
333        /// Override the retry_config used to build [`SdkConfig`].
334        ///
335        /// # Examples
336        /// ```no_run
337        /// # async fn create_config() {
338        /// use aws_config::retry::RetryConfig;
339        ///
340        /// let config = aws_config::from_env()
341        ///     .retry_config(RetryConfig::standard().with_max_attempts(2))
342        ///     .load()
343        ///     .await;
344        /// # }
345        /// ```
346        pub fn retry_config(mut self, retry_config: RetryConfig) -> Self {
347            self.retry_config = Some(retry_config);
348            self
349        }
350
351        /// Override the timeout config used to build [`SdkConfig`].
352        ///
353        /// This will be merged with timeouts coming from the timeout information provider, which
354        /// currently includes a default `CONNECT` timeout of `3.1s`.
355        ///
356        /// If you want to disable timeouts, use [`TimeoutConfig::disabled`]. If you want to disable
357        /// a specific timeout, use `TimeoutConfig::set_<type>(None)`.
358        ///
359        /// **Note: This only sets timeouts for calls to AWS services.** Timeouts for the credentials
360        /// provider chain are configured separately.
361        ///
362        /// # Examples
363        /// ```no_run
364        /// # use std::time::Duration;
365        /// # async fn create_config() {
366        /// use aws_config::timeout::TimeoutConfig;
367        ///
368        /// let config = aws_config::from_env()
369        ///    .timeout_config(
370        ///        TimeoutConfig::builder()
371        ///            .operation_timeout(Duration::from_secs(5))
372        ///            .build()
373        ///    )
374        ///    .load()
375        ///    .await;
376        /// # }
377        /// ```
378        pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self {
379            self.timeout_config = Some(timeout_config);
380            self
381        }
382
383        /// Override the sleep implementation for this [`ConfigLoader`].
384        ///
385        /// The sleep implementation is used to create timeout futures.
386        /// You generally won't need to change this unless you're using an async runtime other
387        /// than Tokio.
388        pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self {
389            // it's possible that we could wrapping an `Arc in an `Arc` and that's OK
390            self.sleep = Some(sleep.into_shared());
391            self
392        }
393
394        /// Set the time source used for tasks like signing requests.
395        ///
396        /// You generally won't need to change this unless you're compiling for a target
397        /// that can't provide a default, such as WASM, or unless you're writing a test against
398        /// the client that needs a fixed time.
399        pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self {
400            self.time_source = Some(time_source.into_shared());
401            self
402        }
403
404        /// Override the [`HttpClient`] for this [`ConfigLoader`].
405        ///
406        /// The HTTP client will be used for both AWS services and credentials providers.
407        ///
408        /// If you wish to use a separate HTTP client for credentials providers when creating clients,
409        /// then override the HTTP client set with this function on the client-specific `Config`s.
410        pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self {
411            self.http_client = Some(http_client.into_shared());
412            self
413        }
414
415        /// Sets the client protocol to use for serialization and deserialization.
416        ///
417        /// This overrides the default protocol determined by the service model.
418        ///
419        /// # Transport
420        ///
421        /// This setter is HTTP-specific. `self.protocol` is typed
422        /// `Option<SharedClientProtocol>` which elides to the HTTP specialization,
423        /// and only `SharedClientProtocol<http::Request, http::Response>` has a
424        /// `Storable` impl. The `impl ClientProtocol + 'static` bound here
425        /// elides to `impl ClientProtocol<http::Request, http::Response>` to
426        /// match. A non-HTTP transport would add its own setter paired with its
427        /// own `Storable` newtype rather than generalizing this one — the
428        /// underlying `ClientProtocol<Req, Res>` trait is already
429        /// transport-generic.
430        pub fn protocol(mut self, protocol: impl ClientProtocol + 'static) -> Self {
431            self.protocol = Some(SharedClientProtocol::new(protocol));
432            self
433        }
434
435        #[doc = docs_for!(auth_scheme_preference)]
436        ///
437        /// # Examples
438        /// ```no_run
439        /// # use aws_smithy_runtime_api::client::auth::AuthSchemeId;
440        /// # async fn create_config() {
441        /// let config = aws_config::from_env()
442        ///     // Favors a custom auth scheme over the SigV4 auth scheme.
443        ///     // Note: This will not result in an error, even if the custom scheme is missing from the resolved auth schemes.
444        ///     .auth_scheme_preference([AuthSchemeId::from("custom"), aws_runtime::auth::sigv4::SCHEME_ID])
445        ///     .load()
446        ///     .await;
447        /// # }
448        /// ```
449        pub fn auth_scheme_preference(
450            mut self,
451            auth_scheme_preference: impl Into<AuthSchemePreference>,
452        ) -> Self {
453            self.auth_scheme_preference = Some(auth_scheme_preference.into());
454            self
455        }
456
457        #[doc = docs_for!(sigv4a_signing_region_set)]
458        pub fn sigv4a_signing_region_set(
459            mut self,
460            sigv4a_signing_region_set: impl Into<SigningRegionSet>,
461        ) -> Self {
462            self.sigv4a_signing_region_set = Some(sigv4a_signing_region_set.into());
463            self
464        }
465
466        /// Override the identity cache used to build [`SdkConfig`].
467        ///
468        /// The identity cache caches AWS credentials and SSO tokens. By default, a lazy cache is used
469        /// that will load credentials upon first request, cache them, and then reload them during
470        /// another request when they are close to expiring.
471        ///
472        /// # Examples
473        ///
474        /// Change a setting on the default lazy caching implementation:
475        /// ```no_run
476        /// use aws_config::identity::IdentityCache;
477        /// use std::time::Duration;
478        ///
479        /// # async fn create_config() {
480        /// let config = aws_config::from_env()
481        ///     .identity_cache(
482        ///         IdentityCache::lazy()
483        ///             // Change the load timeout to 10 seconds.
484        ///             // Note: there are other timeouts that could trigger if the load timeout is too long.
485        ///             .load_timeout(Duration::from_secs(10))
486        ///             .build()
487        ///     )
488        ///     .load()
489        ///     .await;
490        /// # }
491        /// ```
492        pub fn identity_cache(
493            mut self,
494            identity_cache: impl ResolveCachedIdentity + 'static,
495        ) -> Self {
496            self.identity_cache = Some(identity_cache.into_shared());
497            self
498        }
499
500        /// Override the credentials provider used to build [`SdkConfig`].
501        ///
502        /// # Examples
503        ///
504        /// Override the credentials provider but load the default value for region:
505        /// ```no_run
506        /// # use aws_credential_types::Credentials;
507        /// # fn create_my_credential_provider() -> Credentials {
508        /// #     Credentials::new("example", "example", None, None, "example")
509        /// # }
510        /// # async fn create_config() {
511        /// let config = aws_config::from_env()
512        ///     .credentials_provider(create_my_credential_provider())
513        ///     .load()
514        ///     .await;
515        /// # }
516        /// ```
517        pub fn credentials_provider(
518            mut self,
519            credentials_provider: impl ProvideCredentials + 'static,
520        ) -> Self {
521            self.credentials_provider =
522                TriStateOption::Set(SharedCredentialsProvider::new(credentials_provider));
523            self
524        }
525
526        /// Don't use credentials to sign requests.
527        ///
528        /// Turning off signing with credentials is necessary in some cases, such as using
529        /// anonymous auth for S3, calling operations in STS that don't require a signature,
530        /// or using token-based auth.
531        ///
532        /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you
533        /// want. If credentials are disabled, requests cannot be signed. For these use cases, use
534        /// [`test_credentials`](Self::test_credentials).
535        ///
536        /// # Examples
537        ///
538        /// Turn off credentials in order to call a service without signing:
539        /// ```no_run
540        /// # async fn create_config() {
541        /// let config = aws_config::from_env()
542        ///     .no_credentials()
543        ///     .load()
544        ///     .await;
545        /// # }
546        /// ```
547        pub fn no_credentials(mut self) -> Self {
548            self.credentials_provider = TriStateOption::ExplicitlyUnset;
549            self
550        }
551
552        /// Set test credentials for use when signing requests
553        pub fn test_credentials(self) -> Self {
554            #[allow(unused_mut)]
555            let mut ret = self.credentials_provider(Credentials::for_tests());
556            #[cfg(feature = "sso")]
557            {
558                use aws_smithy_runtime_api::client::identity::http::Token;
559                ret = ret.token_provider(Token::for_tests());
560            }
561            ret
562        }
563
564        /// Ignore any environment variables on the host during config resolution
565        ///
566        /// This allows for testing in a reproducible environment that ensures any
567        /// environment variables from the host do not influence environment variable
568        /// resolution.
569        pub fn empty_test_environment(mut self) -> Self {
570            self.env = Some(Env::from_slice(&[]));
571            self
572        }
573
574        /// Override the access token provider used to build [`SdkConfig`].
575        ///
576        /// # Examples
577        ///
578        /// Override the token provider but load the default value for region:
579        /// ```no_run
580        /// # use aws_credential_types::Token;
581        /// # fn create_my_token_provider() -> Token {
582        /// #     Token::new("example", None)
583        /// # }
584        /// # async fn create_config() {
585        /// let config = aws_config::from_env()
586        ///     .token_provider(create_my_token_provider())
587        ///     .load()
588        ///     .await;
589        /// # }
590        /// ```
591        pub fn token_provider(mut self, token_provider: impl ProvideToken + 'static) -> Self {
592            self.token_provider = Some(SharedTokenProvider::new(token_provider));
593            self
594        }
595
596        /// Override the name of the app used to build [`SdkConfig`].
597        ///
598        /// This _optional_ name is used to identify the application in the user agent header that
599        /// gets sent along with requests.
600        ///
601        /// The app name is selected from an ordered list of sources:
602        /// 1. This override.
603        /// 2. The value of the `AWS_SDK_UA_APP_ID` environment variable.
604        /// 3. Profile files from the key `sdk_ua_app_id`
605        ///
606        /// If none of those sources are set the value is `None` and it is not added to the user agent header.
607        ///
608        /// # Examples
609        /// ```no_run
610        /// # async fn create_config() {
611        /// use aws_config::AppName;
612        /// let config = aws_config::from_env()
613        ///     .app_name(AppName::new("my-app-name").expect("valid app name"))
614        ///     .load().await;
615        /// # }
616        /// ```
617        pub fn app_name(mut self, app_name: AppName) -> Self {
618            self.app_name = Some(app_name);
619            self
620        }
621
622        /// Provides the ability to programmatically override the profile files that get loaded by the SDK.
623        ///
624        /// The [`Default`] for `ProfileFiles` includes the default SDK config and credential files located in
625        /// `~/.aws/config` and `~/.aws/credentials` respectively.
626        ///
627        /// Any number of config and credential files may be added to the `ProfileFiles` file set, with the
628        /// only requirement being that there is at least one of each. Profile file locations will produce an
629        /// error if they don't exist, but the default config/credentials files paths are exempt from this validation.
630        ///
631        /// # Example: Using a custom profile file path
632        ///
633        /// ```no_run
634        /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
635        /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind};
636        ///
637        /// # async fn example() {
638        /// let profile_files = ProfileFiles::builder()
639        ///     .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file")
640        ///     .build();
641        /// let sdk_config = aws_config::from_env()
642        ///     .profile_files(profile_files)
643        ///     .load()
644        ///     .await;
645        /// # }
646        #[allow(deprecated)]
647        pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
648            self.profile_files_override = Some(profile_files);
649            self
650        }
651
652        /// Override the profile name used by configuration providers
653        ///
654        /// Profile name is selected from an ordered list of sources:
655        /// 1. This override.
656        /// 2. The value of the `AWS_PROFILE` environment variable.
657        /// 3. `default`
658        ///
659        /// Each AWS profile has a name. For example, in the file below, the profiles are named
660        /// `dev`, `prod` and `staging`:
661        /// ```ini
662        /// [dev]
663        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
664        ///
665        /// [staging]
666        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
667        ///
668        /// [prod]
669        /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
670        /// ```
671        ///
672        /// See [Named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
673        /// for more information about naming profiles.
674        ///
675        /// # Example: Using a custom profile name
676        ///
677        /// ```no_run
678        /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
679        ///
680        /// # async fn example() {
681        /// let sdk_config = aws_config::from_env()
682        ///     .profile_name("prod")
683        ///     .load()
684        ///     .await;
685        /// # }
686        pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
687            self.profile_name_override = Some(profile_name.into());
688            self
689        }
690
691        #[doc = docs_for!(account_id_endpoint_mode)]
692        pub fn account_id_endpoint_mode(
693            mut self,
694            account_id_endpoint_mode: AccountIdEndpointMode,
695        ) -> Self {
696            self.account_id_endpoint_mode = Some(account_id_endpoint_mode);
697            self
698        }
699
700        /// Override the endpoint URL used for **all** AWS services.
701        ///
702        /// This method will override the endpoint URL used for **all** AWS services. This primarily
703        /// exists to set a static endpoint for tools like `LocalStack`. When sending requests to
704        /// production AWS services, this method should only be used for service-specific behavior.
705        ///
706        /// When this method is used, the [`Region`](aws_types::region::Region) is only used for signing;
707        /// It is **not** used to route the request.
708        ///
709        /// # Examples
710        ///
711        /// Use a static endpoint for all services
712        /// ```no_run
713        /// # async fn create_config() {
714        /// let sdk_config = aws_config::from_env()
715        ///     .endpoint_url("http://localhost:1234")
716        ///     .load()
717        ///     .await;
718        /// # }
719        pub fn endpoint_url(mut self, endpoint_url: impl Into<String>) -> Self {
720            self.endpoint_url = Some(endpoint_url.into());
721            self
722        }
723
724        #[doc = docs_for!(use_fips)]
725        pub fn use_fips(mut self, use_fips: bool) -> Self {
726            self.use_fips = Some(use_fips);
727            self
728        }
729
730        #[doc = docs_for!(use_dual_stack)]
731        pub fn use_dual_stack(mut self, use_dual_stack: bool) -> Self {
732            self.use_dual_stack = Some(use_dual_stack);
733            self
734        }
735
736        #[doc = docs_for!(disable_request_compression)]
737        pub fn disable_request_compression(mut self, disable_request_compression: bool) -> Self {
738            self.disable_request_compression = Some(disable_request_compression);
739            self
740        }
741
742        #[doc = docs_for!(request_min_compression_size_bytes)]
743        pub fn request_min_compression_size_bytes(mut self, size: u32) -> Self {
744            self.request_min_compression_size_bytes = Some(size);
745            self
746        }
747
748        /// Override the [`StalledStreamProtectionConfig`] used to build [`SdkConfig`].
749        ///
750        /// This configures stalled stream protection. When enabled, download streams
751        /// that stop (stream no data) for longer than a configured grace period will return an error.
752        ///
753        /// By default, streams that transmit less than one byte per-second for five seconds will
754        /// be cancelled.
755        ///
756        /// _Note_: When an override is provided, the default implementation is replaced.
757        ///
758        /// # Examples
759        /// ```no_run
760        /// # async fn create_config() {
761        /// use aws_config::stalled_stream_protection::StalledStreamProtectionConfig;
762        /// use std::time::Duration;
763        /// let config = aws_config::from_env()
764        ///     .stalled_stream_protection(
765        ///         StalledStreamProtectionConfig::enabled()
766        ///             .grace_period(Duration::from_secs(1))
767        ///             .build()
768        ///     )
769        ///     .load()
770        ///     .await;
771        /// # }
772        /// ```
773        pub fn stalled_stream_protection(
774            mut self,
775            stalled_stream_protection_config: StalledStreamProtectionConfig,
776        ) -> Self {
777            self.stalled_stream_protection_config = Some(stalled_stream_protection_config);
778            self
779        }
780
781        /// Set the checksum calculation strategy to use when making requests.
782        /// # Examples
783        /// ```
784        /// use aws_types::SdkConfig;
785        /// use aws_smithy_types::checksum_config::RequestChecksumCalculation;
786        /// let config = SdkConfig::builder().request_checksum_calculation(RequestChecksumCalculation::WhenSupported).build();
787        /// ```
788        pub fn request_checksum_calculation(
789            mut self,
790            request_checksum_calculation: RequestChecksumCalculation,
791        ) -> Self {
792            self.request_checksum_calculation = Some(request_checksum_calculation);
793            self
794        }
795
796        /// Set the checksum calculation strategy to use for responses.
797        /// # Examples
798        /// ```
799        /// use aws_types::SdkConfig;
800        /// use aws_smithy_types::checksum_config::ResponseChecksumValidation;
801        /// let config = SdkConfig::builder().response_checksum_validation(ResponseChecksumValidation::WhenSupported).build();
802        /// ```
803        pub fn response_checksum_validation(
804            mut self,
805            response_checksum_validation: ResponseChecksumValidation,
806        ) -> Self {
807            self.response_checksum_validation = Some(response_checksum_validation);
808            self
809        }
810
811        /// Load the default configuration chain
812        ///
813        /// If fields have been overridden during builder construction, the override values will be used.
814        ///
815        /// Otherwise, the default values for each field will be provided.
816        ///
817        /// NOTE: When an override is provided, the default implementation is **not** used as a fallback.
818        /// This means that if you provide a region provider that does not return a region, no region will
819        /// be set in the resulting [`SdkConfig`].
820        pub async fn load(self) -> SdkConfig {
821            let time_source = self.time_source.unwrap_or_default();
822
823            let sleep_impl = if self.sleep.is_some() {
824                self.sleep
825            } else {
826                if default_async_sleep().is_none() {
827                    tracing::warn!(
828                        "An implementation of AsyncSleep was requested by calling default_async_sleep \
829                         but no default was set.
830                         This happened when ConfigLoader::load was called during Config construction. \
831                         You can fix this by setting a sleep_impl on the ConfigLoader before calling \
832                         load or by enabling the rt-tokio feature"
833                    );
834                }
835                default_async_sleep()
836            };
837
838            let conf = self
839                .provider_config
840                .unwrap_or_else(|| {
841                    let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone())
842                        .with_fs(self.fs.unwrap_or_default())
843                        .with_env(self.env.unwrap_or_default());
844                    if let Some(http_client) = self.http_client.clone() {
845                        config = config.with_http_client(http_client);
846                    }
847                    config
848                })
849                .with_behavior_version(self.behavior_version)
850                .with_profile_config(self.profile_files_override, self.profile_name_override);
851
852            let use_fips = if let Some(use_fips) = self.use_fips {
853                Some(use_fips)
854            } else {
855                use_fips::use_fips_provider(&conf).await
856            };
857
858            let use_dual_stack = if let Some(use_dual_stack) = self.use_dual_stack {
859                Some(use_dual_stack)
860            } else {
861                use_dual_stack::use_dual_stack_provider(&conf).await
862            };
863
864            let conf = conf
865                .with_use_fips(use_fips)
866                .with_use_dual_stack(use_dual_stack);
867
868            let region = if let Some(provider) = self.region {
869                provider.region().await
870            } else {
871                region::Builder::default()
872                    .configure(&conf)
873                    .build()
874                    .region()
875                    .await
876            };
877            let conf = conf.with_region(region.clone());
878
879            let app_name = if self.app_name.is_some() {
880                self.app_name
881            } else {
882                app_name::default_provider()
883                    .configure(&conf)
884                    .app_name()
885                    .await
886            };
887
888            let disable_request_compression = if self.disable_request_compression.is_some() {
889                self.disable_request_compression
890            } else {
891                disable_request_compression::disable_request_compression_provider(&conf).await
892            };
893
894            let request_min_compression_size_bytes =
895                if self.request_min_compression_size_bytes.is_some() {
896                    self.request_min_compression_size_bytes
897                } else {
898                    request_min_compression_size_bytes::request_min_compression_size_bytes_provider(
899                        &conf,
900                    )
901                    .await
902                };
903
904            let base_config = timeout_config::default_provider()
905                .configure(&conf)
906                .timeout_config()
907                .await;
908            let mut timeout_config = self
909                .timeout_config
910                .unwrap_or_else(|| TimeoutConfig::builder().build());
911            timeout_config.take_defaults_from(&base_config);
912
913            let credentials_provider = match self.credentials_provider {
914                TriStateOption::Set(provider) => Some(provider),
915                TriStateOption::NotSet => {
916                    let mut builder =
917                        credentials::DefaultCredentialsChain::builder().configure(conf.clone());
918                    builder.set_region(region.clone());
919                    Some(SharedCredentialsProvider::new(builder.build().await))
920                }
921                TriStateOption::ExplicitlyUnset => None,
922            };
923
924            let profiles = conf.profile().await;
925            let ignore_configured_endpoint_urls = if self.endpoint_url.is_some() {
926                // If an endpoint URL is set programmatically, the ignore flag is irrelevant
927                // because programmatic config always takes precedence.
928                false
929            } else {
930                ignore_ep::ignore_configured_endpoint_urls_provider(&conf)
931                    .await
932                    .unwrap_or_default()
933            };
934            let service_config = EnvServiceConfig {
935                env: conf.env(),
936                env_config_sections: profiles.cloned().unwrap_or_default(),
937                ignore_configured_endpoint_urls,
938            };
939            let mut builder = SdkConfig::builder()
940                .region(region.clone())
941                .timeout_config(timeout_config)
942                .time_source(time_source)
943                .service_config(service_config);
944
945            let retry_config = if let Some(retry_config) = self.retry_config {
946                builder.insert_origin("retry_config", Origin::shared_config());
947                retry_config
948            } else {
949                retry_config::default_provider()
950                    .configure(&conf)
951                    .retry_config()
952                    .await
953            };
954            builder = builder.retry_config(retry_config);
955
956            // If an endpoint URL is set programmatically, then our work is done.
957            let endpoint_url = if self.endpoint_url.is_some() {
958                builder.insert_origin("endpoint_url", Origin::shared_config());
959                self.endpoint_url
960            } else if ignore_configured_endpoint_urls {
961                // If yes, log a trace and return `None`.
962                tracing::trace!(
963                    "`ignore_configured_endpoint_urls` is set, any endpoint URLs configured in the environment will be ignored. \
964                    NOTE: Endpoint URLs set programmatically WILL still be respected"
965                );
966                None
967            } else {
968                // Otherwise, attempt to resolve one.
969                let (v, origin) = endpoint_url::endpoint_url_provider_with_origin(&conf).await;
970                builder.insert_origin("endpoint_url", origin);
971                v
972            };
973
974            let token_provider = match self.token_provider {
975                Some(provider) => {
976                    builder.insert_origin("token_provider", Origin::shared_config());
977                    Some(provider)
978                }
979                None => {
980                    #[cfg(feature = "sso")]
981                    {
982                        let mut builder =
983                            crate::default_provider::token::DefaultTokenChain::builder()
984                                .configure(conf.clone());
985                        builder.set_region(region);
986                        Some(SharedTokenProvider::new(builder.build().await))
987                    }
988                    #[cfg(not(feature = "sso"))]
989                    {
990                        None
991                    }
992                    // Not setting `Origin` in this arm, and that's good for now as long as we know
993                    // it's not programmatically set in the shared config.
994                    // We can consider adding `Origin::Default` if needed.
995                }
996            };
997
998            builder.set_endpoint_url(endpoint_url);
999            builder.set_behavior_version(self.behavior_version);
1000            builder.set_http_client(self.http_client);
1001            builder.set_protocol(self.protocol);
1002            builder.set_app_name(app_name);
1003
1004            let identity_cache = match self.identity_cache {
1005                None => match self.behavior_version {
1006                    #[allow(deprecated)]
1007                    Some(bv) if bv.is_at_least(BehaviorVersion::v2024_03_28()) => {
1008                        Some(IdentityCache::lazy().build())
1009                    }
1010                    _ => None,
1011                },
1012                Some(user_cache) => Some(user_cache),
1013            };
1014
1015            let request_checksum_calculation =
1016                if let Some(request_checksum_calculation) = self.request_checksum_calculation {
1017                    Some(request_checksum_calculation)
1018                } else {
1019                    checksums::request_checksum_calculation_provider(&conf).await
1020                };
1021
1022            let response_checksum_validation =
1023                if let Some(response_checksum_validation) = self.response_checksum_validation {
1024                    Some(response_checksum_validation)
1025                } else {
1026                    checksums::response_checksum_validation_provider(&conf).await
1027                };
1028
1029            let account_id_endpoint_mode =
1030                if let Some(acccount_id_endpoint_mode) = self.account_id_endpoint_mode {
1031                    Some(acccount_id_endpoint_mode)
1032                } else {
1033                    account_id_endpoint_mode::account_id_endpoint_mode_provider(&conf).await
1034                };
1035
1036            let auth_scheme_preference =
1037                if let Some(auth_scheme_preference) = self.auth_scheme_preference {
1038                    builder.insert_origin("auth_scheme_preference", Origin::shared_config());
1039                    Some(auth_scheme_preference)
1040                } else {
1041                    auth_scheme_preference::auth_scheme_preference_provider(&conf).await
1042                    // Not setting `Origin` in this arm, and that's good for now as long as we know
1043                    // it's not programmatically set in the shared config.
1044                };
1045
1046            let sigv4a_signing_region_set =
1047                if let Some(sigv4a_signing_region_set) = self.sigv4a_signing_region_set {
1048                    Some(sigv4a_signing_region_set)
1049                } else {
1050                    sigv4a_signing_region_set::sigv4a_signing_region_set_provider(&conf).await
1051                };
1052
1053            builder.set_request_checksum_calculation(request_checksum_calculation);
1054            builder.set_response_checksum_validation(response_checksum_validation);
1055            builder.set_identity_cache(identity_cache);
1056            builder.set_credentials_provider(credentials_provider);
1057            builder.set_token_provider(token_provider);
1058            builder.set_sleep_impl(sleep_impl);
1059            builder.set_use_fips(use_fips);
1060            builder.set_use_dual_stack(use_dual_stack);
1061            builder.set_disable_request_compression(disable_request_compression);
1062            builder.set_request_min_compression_size_bytes(request_min_compression_size_bytes);
1063            builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
1064            builder.set_account_id_endpoint_mode(account_id_endpoint_mode);
1065            builder.set_auth_scheme_preference(auth_scheme_preference);
1066            builder.set_sigv4a_signing_region_set(sigv4a_signing_region_set);
1067            builder.build()
1068        }
1069    }
1070
1071    #[cfg(any(test, feature = "test-util"))]
1072    impl ConfigLoader {
1073        /// Override the environment variables used during config resolution.
1074        ///
1075        /// This is intended for testing only.
1076        pub fn env(mut self, env: Env) -> Self {
1077            self.env = Some(env);
1078            self
1079        }
1080
1081        /// Override the filesystem used during config resolution.
1082        ///
1083        /// This is intended for testing only.
1084        pub fn fs(mut self, fs: Fs) -> Self {
1085            self.fs = Some(fs);
1086            self
1087        }
1088    }
1089
1090    #[cfg(test)]
1091    mod test {
1092        #[allow(deprecated)]
1093        use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
1094        use crate::test_case::{no_traffic_client, InstantSleep};
1095        use crate::BehaviorVersion;
1096        use crate::{defaults, ConfigLoader};
1097        use aws_credential_types::provider::ProvideCredentials;
1098        use aws_smithy_async::rt::sleep::TokioSleep;
1099        use aws_smithy_http_client::test_util::{infallible_client_fn, NeverClient};
1100        use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
1101        use aws_types::app_name::AppName;
1102        use aws_types::origin::Origin;
1103        use aws_types::os_shim_internal::{Env, Fs};
1104        use aws_types::sdk_config::{RequestChecksumCalculation, ResponseChecksumValidation};
1105        use std::sync::atomic::{AtomicUsize, Ordering};
1106        use std::sync::Arc;
1107
1108        #[tokio::test]
1109        async fn provider_config_used() {
1110            let (_guard, logs_rx) = capture_test_logs();
1111            let env = Env::from_slice(&[
1112                ("AWS_MAX_ATTEMPTS", "10"),
1113                ("AWS_REGION", "us-west-4"),
1114                ("AWS_ACCESS_KEY_ID", "akid"),
1115                ("AWS_SECRET_ACCESS_KEY", "secret"),
1116            ]);
1117            let fs =
1118                Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]);
1119            let loader = defaults(BehaviorVersion::latest())
1120                .sleep_impl(TokioSleep::new())
1121                .env(env)
1122                .fs(fs)
1123                .http_client(NeverClient::new())
1124                .profile_name("custom")
1125                .profile_files(
1126                    #[allow(deprecated)]
1127                    ProfileFiles::builder()
1128                        .with_file(
1129                            #[allow(deprecated)]
1130                            ProfileFileKind::Config,
1131                            "test_config",
1132                        )
1133                        .build(),
1134                )
1135                .load()
1136                .await;
1137            assert_eq!(10, loader.retry_config().unwrap().max_attempts());
1138            assert_eq!("us-west-4", loader.region().unwrap().as_ref());
1139            assert_eq!(
1140                "akid",
1141                loader
1142                    .credentials_provider()
1143                    .unwrap()
1144                    .provide_credentials()
1145                    .await
1146                    .unwrap()
1147                    .access_key_id(),
1148            );
1149            assert_eq!(Some(&AppName::new("correct").unwrap()), loader.app_name());
1150
1151            let num_config_loader_logs = logs_rx.contents()
1152                .lines()
1153                // The logger uses fancy formatting, so we have to account for that.
1154                .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}"))
1155                .count();
1156
1157            match num_config_loader_logs {
1158                0 => panic!("no config file logs found!"),
1159                1 => (),
1160                more => panic!("the config file was parsed more than once! (parsed {more})",),
1161            };
1162        }
1163
1164        fn base_conf() -> ConfigLoader {
1165            defaults(BehaviorVersion::latest())
1166                .sleep_impl(InstantSleep)
1167                .http_client(no_traffic_client())
1168        }
1169
1170        #[tokio::test]
1171        async fn test_origin_programmatic() {
1172            let _ = tracing_subscriber::fmt::try_init();
1173            let loader = base_conf()
1174                .test_credentials()
1175                .profile_name("custom")
1176                .profile_files(
1177                    #[allow(deprecated)]
1178                    ProfileFiles::builder()
1179                        .with_contents(
1180                            #[allow(deprecated)]
1181                            ProfileFileKind::Config,
1182                            "[profile custom]\nendpoint_url = http://localhost:8989",
1183                        )
1184                        .build(),
1185                )
1186                .endpoint_url("http://localhost:1111")
1187                .load()
1188                .await;
1189            assert_eq!(Origin::shared_config(), loader.get_origin("endpoint_url"));
1190        }
1191
1192        #[tokio::test]
1193        async fn test_origin_env() {
1194            let _ = tracing_subscriber::fmt::try_init();
1195            let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://localhost:7878")]);
1196            let loader = base_conf()
1197                .test_credentials()
1198                .env(env)
1199                .profile_name("custom")
1200                .profile_files(
1201                    #[allow(deprecated)]
1202                    ProfileFiles::builder()
1203                        .with_contents(
1204                            #[allow(deprecated)]
1205                            ProfileFileKind::Config,
1206                            "[profile custom]\nendpoint_url = http://localhost:8989",
1207                        )
1208                        .build(),
1209                )
1210                .load()
1211                .await;
1212            assert_eq!(
1213                Origin::shared_environment_variable(),
1214                loader.get_origin("endpoint_url")
1215            );
1216        }
1217
1218        #[tokio::test]
1219        async fn test_origin_fs() {
1220            let _ = tracing_subscriber::fmt::try_init();
1221            let loader = base_conf()
1222                .test_credentials()
1223                .profile_name("custom")
1224                .profile_files(
1225                    #[allow(deprecated)]
1226                    ProfileFiles::builder()
1227                        .with_contents(
1228                            #[allow(deprecated)]
1229                            ProfileFileKind::Config,
1230                            "[profile custom]\nendpoint_url = http://localhost:8989",
1231                        )
1232                        .build(),
1233                )
1234                .load()
1235                .await;
1236            assert_eq!(
1237                Origin::shared_profile_file(),
1238                loader.get_origin("endpoint_url")
1239            );
1240        }
1241
1242        #[tokio::test]
1243        async fn load_use_fips() {
1244            let conf = base_conf().use_fips(true).load().await;
1245            assert_eq!(Some(true), conf.use_fips());
1246        }
1247
1248        #[tokio::test]
1249        async fn load_dual_stack() {
1250            let conf = base_conf().use_dual_stack(false).load().await;
1251            assert_eq!(Some(false), conf.use_dual_stack());
1252
1253            let conf = base_conf().load().await;
1254            assert_eq!(None, conf.use_dual_stack());
1255        }
1256
1257        #[tokio::test]
1258        async fn load_disable_request_compression() {
1259            let conf = base_conf().disable_request_compression(true).load().await;
1260            assert_eq!(Some(true), conf.disable_request_compression());
1261
1262            let conf = base_conf().load().await;
1263            assert_eq!(None, conf.disable_request_compression());
1264        }
1265
1266        #[tokio::test]
1267        async fn load_request_min_compression_size_bytes() {
1268            let conf = base_conf()
1269                .request_min_compression_size_bytes(99)
1270                .load()
1271                .await;
1272            assert_eq!(Some(99), conf.request_min_compression_size_bytes());
1273
1274            let conf = base_conf().load().await;
1275            assert_eq!(None, conf.request_min_compression_size_bytes());
1276        }
1277
1278        #[tokio::test]
1279        async fn app_name() {
1280            let app_name = AppName::new("my-app-name").unwrap();
1281            let conf = base_conf().app_name(app_name.clone()).load().await;
1282            assert_eq!(Some(&app_name), conf.app_name());
1283        }
1284
1285        #[tokio::test]
1286        async fn request_checksum_calculation() {
1287            let conf = base_conf()
1288                .request_checksum_calculation(RequestChecksumCalculation::WhenRequired)
1289                .load()
1290                .await;
1291            assert_eq!(
1292                Some(RequestChecksumCalculation::WhenRequired),
1293                conf.request_checksum_calculation()
1294            );
1295        }
1296
1297        #[tokio::test]
1298        async fn response_checksum_validation() {
1299            let conf = base_conf()
1300                .response_checksum_validation(ResponseChecksumValidation::WhenRequired)
1301                .load()
1302                .await;
1303            assert_eq!(
1304                Some(ResponseChecksumValidation::WhenRequired),
1305                conf.response_checksum_validation()
1306            );
1307        }
1308
1309        #[cfg(feature = "default-https-client")]
1310        #[tokio::test]
1311        async fn disable_default_credentials() {
1312            let config = defaults(BehaviorVersion::latest())
1313                .no_credentials()
1314                .load()
1315                .await;
1316            assert!(config.credentials_provider().is_none());
1317        }
1318
1319        #[cfg(feature = "default-https-client")]
1320        #[tokio::test]
1321        async fn identity_cache_defaulted() {
1322            let config = defaults(BehaviorVersion::latest()).load().await;
1323
1324            assert!(config.identity_cache().is_some());
1325        }
1326
1327        #[cfg(feature = "default-https-client")]
1328        #[allow(deprecated)]
1329        #[tokio::test]
1330        async fn identity_cache_old_behavior_version() {
1331            // Previously, `load()` did not need an explicit HTTP client because
1332            // internal providers (e.g. IMDS) built their Operation without a
1333            // BehaviorVersion, so default_plugins defaulted to latest() and the
1334            // `default-https-client` code path provided one automatically.
1335            //
1336            // Now that BehaviorVersion is threaded through to Operation::builder(),
1337            // the old BV here (v2023_11_09) causes default_plugins to use the
1338            // legacy hyper 0.14 code path, which returns None without
1339            // `legacy-rustls-ring`. NeverClient satisfies the debug_assertions
1340            // check in Operation::build() without additional TLS dependencies.
1341            let config = defaults(BehaviorVersion::v2023_11_09())
1342                .http_client(NeverClient::new())
1343                .sleep_impl(InstantSleep)
1344                .load()
1345                .await;
1346
1347            assert!(config.identity_cache().is_none());
1348        }
1349
1350        #[tokio::test]
1351        async fn connector_is_shared() {
1352            let num_requests = Arc::new(AtomicUsize::new(0));
1353            let movable = num_requests.clone();
1354            let http_client = infallible_client_fn(move |_req| {
1355                movable.fetch_add(1, Ordering::Relaxed);
1356                http::Response::new("ok!")
1357            });
1358            let config = defaults(BehaviorVersion::latest())
1359                .fs(Fs::from_slice(&[]))
1360                .env(Env::from_slice(&[]))
1361                .http_client(http_client.clone())
1362                .load()
1363                .await;
1364            config
1365                .credentials_provider()
1366                .unwrap()
1367                .provide_credentials()
1368                .await
1369                .expect_err("did not expect credentials to be loaded—no traffic is allowed");
1370            let num_requests = num_requests.load(Ordering::Relaxed);
1371            assert!(num_requests > 0, "{}", num_requests);
1372        }
1373
1374        #[tokio::test]
1375        async fn endpoint_urls_may_be_ignored_from_env() {
1376            let fs = Fs::from_slice(&[(
1377                "test_config",
1378                "[profile custom]\nendpoint_url = http://profile",
1379            )]);
1380            let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1381
1382            let conf = base_conf().use_dual_stack(false).load().await;
1383            assert_eq!(Some(false), conf.use_dual_stack());
1384
1385            let conf = base_conf().load().await;
1386            assert_eq!(None, conf.use_dual_stack());
1387
1388            // Check that we get nothing back because the env said we should ignore endpoints
1389            let config = base_conf()
1390                .fs(fs.clone())
1391                .env(env)
1392                .profile_name("custom")
1393                .profile_files(
1394                    #[allow(deprecated)]
1395                    ProfileFiles::builder()
1396                        .with_file(
1397                            #[allow(deprecated)]
1398                            ProfileFileKind::Config,
1399                            "test_config",
1400                        )
1401                        .build(),
1402                )
1403                .load()
1404                .await;
1405            assert_eq!(None, config.endpoint_url());
1406
1407            // Check that without the env, we DO get something back
1408            let config = base_conf()
1409                .fs(fs)
1410                .profile_name("custom")
1411                .profile_files(
1412                    #[allow(deprecated)]
1413                    ProfileFiles::builder()
1414                        .with_file(
1415                            #[allow(deprecated)]
1416                            ProfileFileKind::Config,
1417                            "test_config",
1418                        )
1419                        .build(),
1420                )
1421                .load()
1422                .await;
1423            assert_eq!(Some("http://profile"), config.endpoint_url());
1424        }
1425
1426        #[tokio::test]
1427        async fn endpoint_urls_may_be_ignored_from_profile() {
1428            let fs = Fs::from_slice(&[(
1429                "test_config",
1430                "[profile custom]\nignore_configured_endpoint_urls = true",
1431            )]);
1432            let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://environment")]);
1433
1434            // Check that we get nothing back because the profile said we should ignore endpoints
1435            let config = base_conf()
1436                .fs(fs)
1437                .env(env.clone())
1438                .profile_name("custom")
1439                .profile_files(
1440                    #[allow(deprecated)]
1441                    ProfileFiles::builder()
1442                        .with_file(
1443                            #[allow(deprecated)]
1444                            ProfileFileKind::Config,
1445                            "test_config",
1446                        )
1447                        .build(),
1448                )
1449                .load()
1450                .await;
1451            assert_eq!(None, config.endpoint_url());
1452
1453            // Check that without the profile, we DO get something back
1454            let config = base_conf().env(env).load().await;
1455            assert_eq!(Some("http://environment"), config.endpoint_url());
1456        }
1457
1458        #[tokio::test]
1459        async fn programmatic_endpoint_urls_may_not_be_ignored() {
1460            let fs = Fs::from_slice(&[(
1461                "test_config",
1462                "[profile custom]\nignore_configured_endpoint_urls = true",
1463            )]);
1464            let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1465
1466            // Check that we get something back because we explicitly set the loader's endpoint URL
1467            let config = base_conf()
1468                .fs(fs)
1469                .env(env)
1470                .endpoint_url("http://localhost")
1471                .profile_name("custom")
1472                .profile_files(
1473                    #[allow(deprecated)]
1474                    ProfileFiles::builder()
1475                        .with_file(
1476                            #[allow(deprecated)]
1477                            ProfileFileKind::Config,
1478                            "test_config",
1479                        )
1480                        .build(),
1481                )
1482                .load()
1483                .await;
1484            assert_eq!(Some("http://localhost"), config.endpoint_url());
1485        }
1486    }
1487}