1use aws_config::meta::region::{ProvideRegion, RegionProviderChain};
2use aws_config::retry::RetryConfig;
3use aws_config::{BehaviorVersion, ConfigLoader};
4use aws_runtime::env_config::file::{EnvConfigFileKind, EnvConfigFiles};
5use aws_sdk_s3::Client;
6use aws_sdk_s3::config::Builder;
7use std::path::PathBuf;
8use std::time::Duration;
9
10use crate::config::ClientConfig;
11use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
12use aws_smithy_types::timeout::TimeoutConfig;
13use aws_types::SdkConfig;
14use aws_types::region::Region;
15
16fn build_profile_files(
21 aws_config_file: Option<&PathBuf>,
22 aws_shared_credentials_file: Option<&PathBuf>,
23) -> Option<EnvConfigFiles> {
24 if aws_config_file.is_none() && aws_shared_credentials_file.is_none() {
25 return None;
26 }
27 let mut builder = EnvConfigFiles::builder();
28 match aws_config_file {
29 Some(p) => builder = builder.with_file(EnvConfigFileKind::Config, p),
30 None => builder = builder.include_default_config_file(true),
31 }
32 match aws_shared_credentials_file {
33 Some(p) => builder = builder.with_file(EnvConfigFileKind::Credentials, p),
34 None => builder = builder.include_default_credentials_file(true),
35 }
36 Some(builder.build())
37}
38
39impl ClientConfig {
40 pub async fn create_client(&self) -> Client {
41 let mut config_builder = Builder::from(&self.load_sdk_config().await)
42 .force_path_style(self.force_path_style)
43 .request_checksum_calculation(self.request_checksum_calculation)
44 .accelerate(self.accelerate);
45
46 if let Some(timeout_config) = self.build_timeout_config() {
47 config_builder = config_builder.timeout_config(timeout_config);
48 }
49
50 Client::from_conf(config_builder.build())
51 }
52
53 async fn load_sdk_config(&self) -> SdkConfig {
54 let config_loader = if self.disable_stalled_stream_protection {
55 aws_config::defaults(BehaviorVersion::latest())
56 .stalled_stream_protection(StalledStreamProtectionConfig::disabled())
57 } else {
58 aws_config::defaults(BehaviorVersion::latest())
59 .stalled_stream_protection(StalledStreamProtectionConfig::enabled().build())
60 };
61 let mut config_loader = self
62 .load_config_credential(config_loader)
63 .region(self.build_region_provider())
64 .retry_config(self.build_retry_config());
65
66 if let Some(endpoint_url) = &self.endpoint_url {
67 config_loader = config_loader.endpoint_url(endpoint_url);
68 };
69
70 config_loader.load().await
71 }
72
73 fn load_config_credential(&self, mut config_loader: ConfigLoader) -> ConfigLoader {
74 match &self.credential {
75 crate::types::S3Credentials::Credentials { access_keys } => {
76 let credentials = aws_sdk_s3::config::Credentials::new(
77 access_keys.access_key.to_string(),
78 access_keys.secret_access_key.to_string(),
79 access_keys.session_token.clone(),
80 None,
81 "",
82 );
83 config_loader = config_loader.credentials_provider(credentials);
84 }
85 crate::types::S3Credentials::Profile(profile_name) => {
86 let mut builder = aws_config::profile::ProfileFileCredentialsProvider::builder();
87
88 if let Some(profile_files) = build_profile_files(
89 self.client_config_location.aws_config_file.as_ref(),
90 self.client_config_location
91 .aws_shared_credentials_file
92 .as_ref(),
93 ) {
94 builder = builder.profile_files(profile_files);
95 }
96
97 config_loader =
98 config_loader.credentials_provider(builder.profile_name(profile_name).build());
99 }
100 crate::types::S3Credentials::FromEnvironment => {}
101 crate::types::S3Credentials::NoSignRequest => {
102 config_loader = config_loader.no_credentials();
103 }
104 }
105 config_loader
106 }
107
108 fn build_region_provider(&self) -> Box<dyn ProvideRegion> {
109 let mut builder = aws_config::profile::ProfileFileRegionProvider::builder();
110
111 if let crate::types::S3Credentials::Profile(profile_name) = &self.credential {
112 if let Some(profile_files) = build_profile_files(
113 self.client_config_location.aws_config_file.as_ref(),
114 self.client_config_location
115 .aws_shared_credentials_file
116 .as_ref(),
117 ) {
118 builder = builder.profile_files(profile_files);
119 }
120 builder = builder.profile_name(profile_name)
121 }
122
123 let provider_region = if matches!(
124 &self.credential,
125 crate::types::S3Credentials::FromEnvironment
126 | crate::types::S3Credentials::NoSignRequest,
127 ) {
128 RegionProviderChain::first_try(self.region.clone().map(Region::new))
129 .or_default_provider()
130 } else {
131 RegionProviderChain::first_try(self.region.clone().map(Region::new))
132 .or_else(builder.build())
133 };
134
135 Box::new(provider_region)
136 }
137
138 fn build_retry_config(&self) -> RetryConfig {
139 RetryConfig::standard()
140 .with_max_attempts(self.retry_config.aws_max_attempts)
141 .with_initial_backoff(std::time::Duration::from_millis(
142 self.retry_config.initial_backoff_milliseconds,
143 ))
144 }
145
146 fn build_timeout_config(&self) -> Option<TimeoutConfig> {
147 let operation_timeout = self
149 .cli_timeout_config
150 .operation_timeout_milliseconds
151 .map(Duration::from_millis);
152 let operation_attempt_timeout = self
153 .cli_timeout_config
154 .operation_attempt_timeout_milliseconds
155 .map(Duration::from_millis);
156 let connect_timeout = self
157 .cli_timeout_config
158 .connect_timeout_milliseconds
159 .map(Duration::from_millis);
160 let read_timeout = self
161 .cli_timeout_config
162 .read_timeout_milliseconds
163 .map(Duration::from_millis);
164
165 if operation_timeout.is_none()
166 && operation_attempt_timeout.is_none()
167 && connect_timeout.is_none()
168 && read_timeout.is_none()
169 {
170 return None;
171 }
172
173 let mut builder = TimeoutConfig::builder();
174
175 builder = if let Some(operation_timeout) = operation_timeout {
176 builder.operation_timeout(operation_timeout)
177 } else {
178 builder
179 };
180
181 builder = if let Some(operation_attempt_timeout) = operation_attempt_timeout {
182 builder.operation_attempt_timeout(operation_attempt_timeout)
183 } else {
184 builder
185 };
186
187 builder = if let Some(connect_timeout) = connect_timeout {
188 builder.connect_timeout(connect_timeout)
189 } else {
190 builder
191 };
192
193 builder = if let Some(read_timeout) = read_timeout {
194 builder.read_timeout(read_timeout)
195 } else {
196 builder
197 };
198
199 Some(builder.build())
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::types::{AccessKeys, ClientConfigLocation};
207 use aws_smithy_types::checksum_config::RequestChecksumCalculation;
208 use std::sync::Arc;
209 use tokio::sync::Semaphore;
210 use tracing_subscriber::EnvFilter;
211
212 struct EnvGuard {
220 key: &'static str,
221 previous: Option<String>,
222 }
223
224 impl EnvGuard {
225 fn set(key: &'static str, value: &str) -> Self {
226 let previous = std::env::var(key).ok();
227 unsafe { std::env::set_var(key, value) };
230 Self { key, previous }
231 }
232 }
233
234 impl Drop for EnvGuard {
235 fn drop(&mut self) {
236 match &self.previous {
238 Some(v) => unsafe { std::env::set_var(self.key, v) },
239 None => unsafe { std::env::remove_var(self.key) },
240 }
241 }
242 }
243
244 #[tokio::test]
245 async fn create_client_from_credentials() {
246 init_dummy_tracing_subscriber();
247
248 let client_config = ClientConfig {
249 client_config_location: ClientConfigLocation {
250 aws_config_file: None,
251 aws_shared_credentials_file: None,
252 },
253 credential: crate::types::S3Credentials::Credentials {
254 access_keys: AccessKeys {
255 access_key: "my_access_key".to_string(),
256 secret_access_key: "my_secret_access_key".to_string(),
257 session_token: Some("my_session_token".to_string()),
258 },
259 },
260 region: Some("my-region".to_string()),
261 endpoint_url: Some("https://my.endpoint.local".to_string()),
262 force_path_style: false,
263 retry_config: crate::config::RetryConfig {
264 aws_max_attempts: 10,
265 initial_backoff_milliseconds: 100,
266 },
267 cli_timeout_config: crate::config::CLITimeoutConfig {
268 operation_timeout_milliseconds: None,
269 operation_attempt_timeout_milliseconds: None,
270 connect_timeout_milliseconds: None,
271 read_timeout_milliseconds: None,
272 },
273 disable_stalled_stream_protection: false,
274 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
275 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
276 accelerate: false,
277 request_payer: None,
278 };
279
280 let client = client_config.create_client().await;
281
282 let retry_config = client.config().retry_config().unwrap();
283 assert_eq!(retry_config.max_attempts(), 10);
284 assert_eq!(
285 retry_config.initial_backoff(),
286 std::time::Duration::from_millis(100)
287 );
288
289 let timeout_config = client.config().timeout_config().unwrap();
290 assert!(timeout_config.operation_timeout().is_none());
291 assert!(timeout_config.operation_attempt_timeout().is_none());
292 assert!(timeout_config.connect_timeout().is_some());
293 assert!(timeout_config.read_timeout().is_none());
294 assert!(timeout_config.has_timeouts());
295
296 assert_eq!(
298 timeout_config.connect_timeout(),
299 Some(Duration::from_millis(3100))
300 );
301
302 assert_eq!(
303 client.config().region().unwrap().to_string(),
304 "my-region".to_string()
305 );
306 }
307
308 #[tokio::test]
309 async fn create_client_from_credentials_with_default_region() {
310 init_dummy_tracing_subscriber();
311
312 let client_config = ClientConfig {
313 client_config_location: ClientConfigLocation {
314 aws_config_file: None,
315 aws_shared_credentials_file: None,
316 },
317 credential: crate::types::S3Credentials::Credentials {
318 access_keys: AccessKeys {
319 access_key: "my_access_key".to_string(),
320 secret_access_key: "my_secret_access_key".to_string(),
321 session_token: Some("my_session_token".to_string()),
322 },
323 },
324 region: None,
325 endpoint_url: Some("https://my.endpoint.local".to_string()),
326 force_path_style: false,
327 retry_config: crate::config::RetryConfig {
328 aws_max_attempts: 10,
329 initial_backoff_milliseconds: 100,
330 },
331 cli_timeout_config: crate::config::CLITimeoutConfig {
332 operation_timeout_milliseconds: Some(1000),
333 operation_attempt_timeout_milliseconds: Some(2000),
334 connect_timeout_milliseconds: Some(3000),
335 read_timeout_milliseconds: Some(4000),
336 },
337 disable_stalled_stream_protection: false,
338 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
339 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
340 accelerate: false,
341 request_payer: None,
342 };
343
344 let client = client_config.create_client().await;
345
346 let retry_config = client.config().retry_config().unwrap();
347 assert_eq!(retry_config.max_attempts(), 10);
348 assert_eq!(
349 retry_config.initial_backoff(),
350 std::time::Duration::from_millis(100)
351 );
352
353 let timeout_config = client.config().timeout_config().unwrap();
354 assert_eq!(
355 timeout_config.operation_timeout(),
356 Some(Duration::from_millis(1000))
357 );
358 assert_eq!(
359 timeout_config.operation_attempt_timeout(),
360 Some(Duration::from_millis(2000))
361 );
362 assert_eq!(
363 timeout_config.connect_timeout(),
364 Some(Duration::from_millis(3000))
365 );
366 assert_eq!(
367 timeout_config.read_timeout(),
368 Some(Duration::from_millis(4000))
369 );
370 assert!(timeout_config.has_timeouts());
371 }
372
373 #[tokio::test]
374 async fn create_client_from_custom_profile() {
375 init_dummy_tracing_subscriber();
376
377 let client_config = ClientConfig {
378 client_config_location: ClientConfigLocation {
379 aws_config_file: Some("./test_data/test_config/config".into()),
380 aws_shared_credentials_file: Some("./test_data/test_config/credentials".into()),
381 },
382 credential: crate::types::S3Credentials::Profile("aws".to_string()),
383 region: Some("my-region".to_string()),
384 endpoint_url: Some("https://my.endpoint.local".to_string()),
385 force_path_style: false,
386 retry_config: crate::config::RetryConfig {
387 aws_max_attempts: 10,
388 initial_backoff_milliseconds: 100,
389 },
390 cli_timeout_config: crate::config::CLITimeoutConfig {
391 operation_timeout_milliseconds: None,
392 operation_attempt_timeout_milliseconds: None,
393 connect_timeout_milliseconds: None,
394 read_timeout_milliseconds: None,
395 },
396 disable_stalled_stream_protection: false,
397 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
398 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
399 accelerate: false,
400 request_payer: None,
401 };
402
403 let client = client_config.create_client().await;
404
405 let retry_config = client.config().retry_config().unwrap();
406 assert_eq!(retry_config.max_attempts(), 10);
407 assert_eq!(
408 retry_config.initial_backoff(),
409 std::time::Duration::from_millis(100)
410 );
411
412 assert_eq!(
413 client.config().region().unwrap().to_string(),
414 "my-region".to_string()
415 );
416 }
417
418 #[tokio::test]
419 async fn create_client_from_custom_timeout() {
420 init_dummy_tracing_subscriber();
421
422 let client_config = ClientConfig {
423 client_config_location: ClientConfigLocation {
424 aws_config_file: Some("./test_data/test_config/config".into()),
425 aws_shared_credentials_file: Some("./test_data/test_config/credentials".into()),
426 },
427 credential: crate::types::S3Credentials::Profile("aws".to_string()),
428 region: Some("my-region".to_string()),
429 endpoint_url: Some("https://my.endpoint.local".to_string()),
430 force_path_style: false,
431 retry_config: crate::config::RetryConfig {
432 aws_max_attempts: 10,
433 initial_backoff_milliseconds: 100,
434 },
435 cli_timeout_config: crate::config::CLITimeoutConfig {
436 operation_timeout_milliseconds: Some(1000),
437 operation_attempt_timeout_milliseconds: None,
438 connect_timeout_milliseconds: None,
439 read_timeout_milliseconds: None,
440 },
441 disable_stalled_stream_protection: false,
442 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
443 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
444 accelerate: false,
445 request_payer: None,
446 };
447
448 let client = client_config.create_client().await;
449
450 let retry_config = client.config().retry_config().unwrap();
451 assert_eq!(retry_config.max_attempts(), 10);
452 assert_eq!(
453 retry_config.initial_backoff(),
454 std::time::Duration::from_millis(100)
455 );
456
457 let timeout_config = client.config().timeout_config().unwrap();
458 assert_eq!(
459 timeout_config.operation_timeout(),
460 Some(Duration::from_millis(1000))
461 );
462 assert!(timeout_config.operation_attempt_timeout().is_none());
463 assert!(timeout_config.connect_timeout().is_some());
464 assert!(timeout_config.read_timeout().is_none());
465 assert!(timeout_config.has_timeouts());
466
467 assert_eq!(
468 client.config().region().unwrap().to_string(),
469 "my-region".to_string()
470 );
471 }
472
473 #[tokio::test]
474 async fn create_client_from_custom_timeout_case2() {
475 init_dummy_tracing_subscriber();
476
477 let client_config = ClientConfig {
478 client_config_location: ClientConfigLocation {
479 aws_config_file: Some("./test_data/test_config/config".into()),
480 aws_shared_credentials_file: Some("./test_data/test_config/credentials".into()),
481 },
482 credential: crate::types::S3Credentials::Profile("aws".to_string()),
483 region: Some("my-region".to_string()),
484 endpoint_url: Some("https://my.endpoint.local".to_string()),
485 force_path_style: false,
486 retry_config: crate::config::RetryConfig {
487 aws_max_attempts: 10,
488 initial_backoff_milliseconds: 100,
489 },
490 cli_timeout_config: crate::config::CLITimeoutConfig {
491 operation_timeout_milliseconds: None,
492 operation_attempt_timeout_milliseconds: None,
493 connect_timeout_milliseconds: Some(1000),
494 read_timeout_milliseconds: None,
495 },
496 disable_stalled_stream_protection: false,
497 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
498 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
499 accelerate: false,
500 request_payer: None,
501 };
502
503 let client = client_config.create_client().await;
504
505 let retry_config = client.config().retry_config().unwrap();
506 assert_eq!(retry_config.max_attempts(), 10);
507 assert_eq!(
508 retry_config.initial_backoff(),
509 std::time::Duration::from_millis(100)
510 );
511
512 let timeout_config = client.config().timeout_config().unwrap();
513 assert!(timeout_config.connect_timeout().is_some());
514 assert!(timeout_config.operation_attempt_timeout().is_none());
515 assert!(timeout_config.connect_timeout().is_some());
516 assert!(timeout_config.read_timeout().is_none());
517 assert!(timeout_config.has_timeouts());
518
519 assert_eq!(
520 client.config().region().unwrap().to_string(),
521 "my-region".to_string()
522 );
523 }
524
525 #[tokio::test]
526 async fn create_client_from_default_profile() {
527 init_dummy_tracing_subscriber();
528
529 let client_config = ClientConfig {
530 client_config_location: ClientConfigLocation {
531 aws_config_file: Some("./test_data/test_config/config".into()),
532 aws_shared_credentials_file: Some("./test_data/test_config/credentials".into()),
533 },
534 credential: crate::types::S3Credentials::Profile("default".to_string()),
535 region: None,
536 endpoint_url: Some("https://my.endpoint.local".to_string()),
537 force_path_style: false,
538 retry_config: crate::config::RetryConfig {
539 aws_max_attempts: 10,
540 initial_backoff_milliseconds: 100,
541 },
542 cli_timeout_config: crate::config::CLITimeoutConfig {
543 operation_timeout_milliseconds: None,
544 operation_attempt_timeout_milliseconds: None,
545 connect_timeout_milliseconds: None,
546 read_timeout_milliseconds: None,
547 },
548 disable_stalled_stream_protection: false,
549 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
550 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
551 accelerate: false,
552 request_payer: None,
553 };
554
555 let client = client_config.create_client().await;
556
557 let retry_config = client.config().retry_config().unwrap();
558 assert_eq!(retry_config.max_attempts(), 10);
559 assert_eq!(
560 retry_config.initial_backoff(),
561 std::time::Duration::from_millis(100)
562 );
563
564 assert_eq!(
565 client.config().region().unwrap().to_string(),
566 "us-west-1".to_string()
567 );
568 }
569
570 #[cfg(e2e_test)]
572 #[tokio::test]
573 async fn create_client_from_env() {
574 init_dummy_tracing_subscriber();
575
576 let client_config = ClientConfig {
577 client_config_location: ClientConfigLocation {
578 aws_config_file: Some("./test_data/test_config/config".into()),
579 aws_shared_credentials_file: Some("./test_data/test_config/credentials".into()),
580 },
581 credential: crate::types::S3Credentials::FromEnvironment,
582 region: None,
583 endpoint_url: Some("https://my.endpoint.local".to_string()),
584 force_path_style: false,
585 retry_config: crate::config::RetryConfig {
586 aws_max_attempts: 10,
587 initial_backoff_milliseconds: 100,
588 },
589 cli_timeout_config: crate::config::CLITimeoutConfig {
590 operation_timeout_milliseconds: None,
591 operation_attempt_timeout_milliseconds: None,
592 connect_timeout_milliseconds: None,
593 read_timeout_milliseconds: None,
594 },
595 disable_stalled_stream_protection: false,
596 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
597 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
598 accelerate: false,
599 request_payer: None,
600 };
601
602 let _ = client_config.create_client().await;
603 }
604
605 #[tokio::test]
606 async fn create_client_from_custom_profile_overriding_region() {
607 init_dummy_tracing_subscriber();
608
609 let client_config = ClientConfig {
610 client_config_location: ClientConfigLocation {
611 aws_config_file: Some("./test_data/test_config/config".into()),
612 aws_shared_credentials_file: Some("./test_data/test_config/credentials".into()),
613 },
614 credential: crate::types::S3Credentials::Profile("aws".to_string()),
615 region: Some("my-region2".to_string()),
616 endpoint_url: Some("https://my.endpoint.local".to_string()),
617 force_path_style: false,
618 retry_config: crate::config::RetryConfig {
619 aws_max_attempts: 10,
620 initial_backoff_milliseconds: 100,
621 },
622 cli_timeout_config: crate::config::CLITimeoutConfig {
623 operation_timeout_milliseconds: None,
624 operation_attempt_timeout_milliseconds: None,
625 connect_timeout_milliseconds: None,
626 read_timeout_milliseconds: None,
627 },
628 disable_stalled_stream_protection: false,
629 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
630 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
631 accelerate: false,
632 request_payer: None,
633 };
634
635 let client = client_config.create_client().await;
636
637 let retry_config = client.config().retry_config().unwrap();
638 assert_eq!(retry_config.max_attempts(), 10);
639 assert_eq!(
640 retry_config.initial_backoff(),
641 std::time::Duration::from_millis(100)
642 );
643
644 assert_eq!(
645 client.config().region().unwrap().to_string(),
646 "my-region2".to_string()
647 );
648 }
649
650 #[tokio::test]
651 async fn create_client_with_no_sign_request_credential() {
652 init_dummy_tracing_subscriber();
653
654 let client_config = ClientConfig {
655 client_config_location: ClientConfigLocation {
656 aws_config_file: None,
657 aws_shared_credentials_file: None,
658 },
659 credential: crate::types::S3Credentials::NoSignRequest,
660 region: Some("my-region".to_string()),
661 endpoint_url: Some("https://my.endpoint.local".to_string()),
662 force_path_style: false,
663 retry_config: crate::config::RetryConfig {
664 aws_max_attempts: 10,
665 initial_backoff_milliseconds: 100,
666 },
667 cli_timeout_config: crate::config::CLITimeoutConfig {
668 operation_timeout_milliseconds: None,
669 operation_attempt_timeout_milliseconds: None,
670 connect_timeout_milliseconds: None,
671 read_timeout_milliseconds: None,
672 },
673 disable_stalled_stream_protection: false,
674 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
675 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
676 accelerate: false,
677 request_payer: None,
678 };
679
680 let client = client_config.create_client().await;
681 assert_eq!(
682 client.config().region().unwrap().to_string(),
683 "my-region".to_string()
684 );
685 let config_debug = format!("{:?}", client.config());
686 assert!(
687 config_debug.contains("identity_resolvers: None"),
688 "NoSignRequest must not install a sigv4 credentials identity resolver, got: {config_debug}"
689 );
690 }
691
692 #[tokio::test]
693 async fn no_sign_request_uses_default_region_chain_not_profile_files() {
694 init_dummy_tracing_subscriber();
699
700 let client_config = ClientConfig {
701 client_config_location: ClientConfigLocation {
702 aws_config_file: Some("/definitely/does/not/exist/config".into()),
703 aws_shared_credentials_file: Some("/definitely/does/not/exist/creds".into()),
704 },
705 credential: crate::types::S3Credentials::NoSignRequest,
706 region: Some("us-east-1".to_string()),
707 endpoint_url: Some("https://my.endpoint.local".to_string()),
708 force_path_style: false,
709 retry_config: crate::config::RetryConfig {
710 aws_max_attempts: 10,
711 initial_backoff_milliseconds: 100,
712 },
713 cli_timeout_config: crate::config::CLITimeoutConfig {
714 operation_timeout_milliseconds: None,
715 operation_attempt_timeout_milliseconds: None,
716 connect_timeout_milliseconds: None,
717 read_timeout_milliseconds: None,
718 },
719 disable_stalled_stream_protection: false,
720 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
721 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
722 accelerate: false,
723 request_payer: None,
724 };
725
726 let client = client_config.create_client().await;
727 assert_eq!(
728 client.config().region().unwrap().to_string(),
729 "us-east-1".to_string(),
730 );
731 }
732
733 #[tokio::test]
734 async fn no_sign_request_no_region_falls_through_to_env_default_chain() {
735 init_dummy_tracing_subscriber();
750
751 let _guard = EnvGuard::set("AWS_REGION", "eu-west-3");
752
753 let client_config = ClientConfig {
754 client_config_location: ClientConfigLocation {
755 aws_config_file: Some("/definitely/does/not/exist/config".into()),
756 aws_shared_credentials_file: Some("/definitely/does/not/exist/creds".into()),
757 },
758 credential: crate::types::S3Credentials::NoSignRequest,
759 region: None,
760 endpoint_url: Some("https://my.endpoint.local".to_string()),
761 force_path_style: false,
762 retry_config: crate::config::RetryConfig {
763 aws_max_attempts: 10,
764 initial_backoff_milliseconds: 100,
765 },
766 cli_timeout_config: crate::config::CLITimeoutConfig {
767 operation_timeout_milliseconds: None,
768 operation_attempt_timeout_milliseconds: None,
769 connect_timeout_milliseconds: None,
770 read_timeout_milliseconds: None,
771 },
772 disable_stalled_stream_protection: false,
773 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
774 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
775 accelerate: false,
776 request_payer: None,
777 };
778
779 let client = client_config.create_client().await;
780 assert_eq!(
781 client.config().region().unwrap().to_string(),
782 "eu-west-3".to_string(),
783 );
784 }
785
786 #[test]
787 fn build_profile_files_returns_none_when_no_overrides() {
788 assert!(build_profile_files(None, None).is_none());
790 }
791
792 #[test]
793 fn build_profile_files_with_only_config_file_falls_back_to_default_credentials() {
794 let config_path = PathBuf::from("/tmp/fake-aws-config");
797 assert!(build_profile_files(Some(&config_path), None).is_some());
798 }
799
800 #[test]
801 fn build_profile_files_with_only_credentials_file_falls_back_to_default_config() {
802 let creds_path = PathBuf::from("/tmp/fake-aws-creds");
805 assert!(build_profile_files(None, Some(&creds_path)).is_some());
806 }
807
808 #[test]
809 fn build_profile_files_with_both_overrides() {
810 let config_path = PathBuf::from("/tmp/fake-aws-config");
811 let creds_path = PathBuf::from("/tmp/fake-aws-creds");
812 assert!(build_profile_files(Some(&config_path), Some(&creds_path)).is_some());
813 }
814
815 #[tokio::test]
816 async fn disable_stalled_stream_protection_branch_builds_client() {
817 init_dummy_tracing_subscriber();
822
823 let client_config = ClientConfig {
824 client_config_location: ClientConfigLocation {
825 aws_config_file: None,
826 aws_shared_credentials_file: None,
827 },
828 credential: crate::types::S3Credentials::NoSignRequest,
829 region: Some("us-east-1".to_string()),
830 endpoint_url: None,
831 force_path_style: false,
832 retry_config: crate::config::RetryConfig {
833 aws_max_attempts: 1,
834 initial_backoff_milliseconds: 0,
835 },
836 cli_timeout_config: crate::config::CLITimeoutConfig {
837 operation_timeout_milliseconds: None,
838 operation_attempt_timeout_milliseconds: None,
839 connect_timeout_milliseconds: None,
840 read_timeout_milliseconds: None,
841 },
842 disable_stalled_stream_protection: true,
843 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
844 parallel_upload_semaphore: Arc::new(Semaphore::new(1)),
845 accelerate: false,
846 request_payer: None,
847 };
848
849 let client = client_config.create_client().await;
850 assert_eq!(
851 client.config().region().unwrap().to_string(),
852 "us-east-1".to_string()
853 );
854 }
855
856 fn init_dummy_tracing_subscriber() {
857 let _ = tracing_subscriber::fmt()
858 .with_env_filter(
859 EnvFilter::try_from_default_env()
860 .or_else(|_| EnvFilter::try_new("dummy=trace"))
861 .unwrap(),
862 )
863 .try_init();
864 }
865}