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}