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