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