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