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