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