1use crate::env_service_config::EnvServiceConfig;
9use crate::profile;
10#[allow(deprecated)]
11use crate::profile::profile_file::ProfileFiles;
12use crate::profile::{ProfileFileLoadError, ProfileSet};
13use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
14use aws_smithy_async::time::{SharedTimeSource, TimeSource};
15use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
16use aws_smithy_runtime_api::client::http::HttpClient;
17use aws_smithy_runtime_api::shared::IntoShared;
18use aws_smithy_types::error::display::DisplayErrorContext;
19use aws_smithy_types::retry::RetryConfig;
20use aws_smithy_types::timeout::TimeoutConfig;
21use aws_types::os_shim_internal::{Env, Fs};
22use aws_types::region::Region;
23use aws_types::sdk_config::SharedHttpClient;
24use aws_types::SdkConfig;
25use std::borrow::Cow;
26use std::fmt::{Debug, Formatter};
27use std::sync::Arc;
28use tokio::sync::OnceCell;
29
30#[derive(Clone)]
39pub struct ProviderConfig {
40 env: Env,
41 fs: Fs,
42 time_source: SharedTimeSource,
43 http_client: Option<SharedHttpClient>,
44 retry_config: Option<RetryConfig>,
45 timeout_config: Option<TimeoutConfig>,
46 sleep_impl: Option<SharedAsyncSleep>,
47 region: Option<Region>,
48 use_fips: Option<bool>,
49 use_dual_stack: Option<bool>,
50 behavior_version: Option<BehaviorVersion>,
51 parsed_profile: Arc<OnceCell<Result<ProfileSet, ProfileFileLoadError>>>,
53 #[allow(deprecated)]
55 profile_files: ProfileFiles,
56 profile_name_override: Option<Cow<'static, str>>,
58}
59
60impl Debug for ProviderConfig {
61 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("ProviderConfig")
63 .field("env", &self.env)
64 .field("fs", &self.fs)
65 .field("time_source", &self.time_source)
66 .field("http_client", &self.http_client)
67 .field("retry_config", &self.retry_config)
68 .field("timeout_config", &self.timeout_config)
69 .field("sleep_impl", &self.sleep_impl)
70 .field("region", &self.region)
71 .field("use_fips", &self.use_fips)
72 .field("use_dual_stack", &self.use_dual_stack)
73 .field("profile_name_override", &self.profile_name_override)
74 .finish()
75 }
76}
77
78impl Default for ProviderConfig {
79 fn default() -> Self {
80 Self {
81 env: Env::default(),
82 fs: Fs::default(),
83 time_source: SharedTimeSource::default(),
84 http_client: None,
85 retry_config: None,
86 timeout_config: None,
87 sleep_impl: default_async_sleep(),
88 region: None,
89 use_fips: None,
90 use_dual_stack: None,
91 behavior_version: None,
92 parsed_profile: Default::default(),
93 #[allow(deprecated)]
94 profile_files: ProfileFiles::default(),
95 profile_name_override: None,
96 }
97 }
98}
99
100#[cfg(test)]
101impl ProviderConfig {
102 pub fn no_configuration() -> Self {
107 use aws_smithy_async::time::StaticTimeSource;
108 use std::collections::HashMap;
109 use std::time::UNIX_EPOCH;
110 let fs = Fs::from_raw_map(HashMap::new());
111 let env = Env::from_slice(&[]);
112 Self {
113 parsed_profile: Default::default(),
114 #[allow(deprecated)]
115 profile_files: ProfileFiles::default(),
116 env,
117 fs,
118 time_source: SharedTimeSource::new(StaticTimeSource::new(UNIX_EPOCH)),
119 http_client: None,
120 retry_config: None,
121 timeout_config: None,
122 sleep_impl: None,
123 region: None,
124 use_fips: None,
125 use_dual_stack: None,
126 behavior_version: None,
127 profile_name_override: None,
128 }
129 }
130}
131
132impl ProviderConfig {
133 pub fn without_region() -> Self {
155 Self::default()
156 }
157
158 pub fn empty() -> Self {
160 ProviderConfig {
161 env: Env::default(),
162 fs: Fs::default(),
163 time_source: SharedTimeSource::default(),
164 http_client: None,
165 retry_config: None,
166 timeout_config: None,
167 sleep_impl: None,
168 region: None,
169 use_fips: None,
170 use_dual_stack: None,
171 behavior_version: None,
172 parsed_profile: Default::default(),
173 #[allow(deprecated)]
174 profile_files: ProfileFiles::default(),
175 profile_name_override: None,
176 }
177 }
178
179 pub(crate) fn init(
181 time_source: SharedTimeSource,
182 sleep_impl: Option<SharedAsyncSleep>,
183 ) -> Self {
184 Self {
185 parsed_profile: Default::default(),
186 #[allow(deprecated)]
187 profile_files: ProfileFiles::default(),
188 env: Env::default(),
189 fs: Fs::default(),
190 time_source,
191 http_client: None,
192 retry_config: None,
193 timeout_config: None,
194 sleep_impl,
195 region: None,
196 use_fips: None,
197 use_dual_stack: None,
198 behavior_version: None,
199 profile_name_override: None,
200 }
201 }
202
203 pub async fn with_default_region() -> Self {
216 Self::without_region().load_default_region().await
217 }
218
219 pub(crate) fn client_config(&self) -> SdkConfig {
226 let profiles = self.parsed_profile.get().and_then(|v| v.as_ref().ok());
227 let service_config = EnvServiceConfig {
228 env: self.env(),
229 env_config_sections: profiles.cloned().unwrap_or_default(),
230 ignore_configured_endpoint_urls: false,
231 };
232
233 let mut builder = SdkConfig::builder()
234 .retry_config(
235 self.retry_config
236 .as_ref()
237 .map_or(RetryConfig::standard(), |config| config.clone()),
238 )
239 .region(self.region())
240 .time_source(self.time_source())
241 .use_fips(self.use_fips().unwrap_or_default())
242 .use_dual_stack(self.use_dual_stack().unwrap_or_default())
243 .service_config(service_config)
244 .behavior_version(crate::BehaviorVersion::latest());
245 if let Some(timeout_config) = self.timeout_config.as_ref() {
246 builder.set_timeout_config(Some(timeout_config.clone()));
247 }
248 builder.set_http_client(self.http_client.clone());
249 builder.set_sleep_impl(self.sleep_impl.clone());
250 builder.build()
251 }
252
253 #[allow(dead_code)]
256 pub(crate) fn env(&self) -> Env {
257 self.env.clone()
258 }
259
260 #[allow(dead_code)]
261 pub(crate) fn fs(&self) -> Fs {
262 self.fs.clone()
263 }
264
265 #[allow(dead_code)]
266 pub(crate) fn time_source(&self) -> SharedTimeSource {
267 self.time_source.clone()
268 }
269
270 #[allow(dead_code)]
271 pub(crate) fn http_client(&self) -> Option<SharedHttpClient> {
272 self.http_client.clone()
273 }
274
275 #[allow(dead_code)]
276 pub(crate) fn retry_config(&self) -> Option<RetryConfig> {
277 self.retry_config.clone()
278 }
279
280 #[allow(dead_code)]
281 pub(crate) fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
282 self.sleep_impl.clone()
283 }
284
285 #[allow(dead_code)]
286 pub(crate) fn region(&self) -> Option<Region> {
287 self.region.clone()
288 }
289
290 #[allow(dead_code)]
291 pub(crate) fn use_fips(&self) -> Option<bool> {
292 self.use_fips
293 }
294
295 #[allow(dead_code)]
296 pub(crate) fn use_dual_stack(&self) -> Option<bool> {
297 self.use_dual_stack
298 }
299
300 pub(crate) async fn try_profile(&self) -> Result<&ProfileSet, &ProfileFileLoadError> {
301 let parsed_profile = self
302 .parsed_profile
303 .get_or_init(|| async {
304 let profile = profile::load(
305 &self.fs,
306 &self.env,
307 &self.profile_files,
308 self.profile_name_override.clone(),
309 )
310 .await;
311 if let Err(err) = profile.as_ref() {
312 tracing::warn!(err = %DisplayErrorContext(&err), "failed to parse profile")
313 }
314 profile
315 })
316 .await;
317 parsed_profile.as_ref()
318 }
319
320 pub(crate) async fn profile(&self) -> Option<&ProfileSet> {
321 self.try_profile().await.ok()
322 }
323
324 pub fn with_region(mut self, region: Option<Region>) -> Self {
326 self.region = region;
327 self
328 }
329
330 pub fn with_use_fips(mut self, use_fips: Option<bool>) -> Self {
343 self.use_fips = use_fips;
344 self
345 }
346
347 pub fn with_use_dual_stack(mut self, use_dual_stack: Option<bool>) -> Self {
355 self.use_dual_stack = use_dual_stack;
356 self
357 }
358
359 pub(crate) fn behavior_version(&self) -> Option<BehaviorVersion> {
360 self.behavior_version
361 }
362
363 pub fn with_behavior_version(mut self, behavior_version: Option<BehaviorVersion>) -> Self {
365 self.behavior_version = behavior_version;
366 self
367 }
368
369 pub(crate) fn with_profile_name(self, profile_name: String) -> Self {
370 let profile_files = self.profile_files.clone();
371 self.with_profile_config(Some(profile_files), Some(profile_name))
372 }
373
374 #[allow(deprecated)]
376 pub(crate) fn with_profile_config(
377 self,
378 profile_files: Option<ProfileFiles>,
379 profile_name_override: Option<String>,
380 ) -> Self {
381 if profile_files.is_none() && profile_name_override.is_none() {
383 return self;
384 }
385 ProviderConfig {
386 parsed_profile: Default::default(),
388 profile_files: profile_files.unwrap_or(self.profile_files),
389 profile_name_override: profile_name_override
390 .map(Cow::Owned)
391 .or(self.profile_name_override),
392 ..self
393 }
394 }
395
396 pub async fn load_default_region(self) -> Self {
401 use crate::default_provider::region::DefaultRegionChain;
402 let provider_chain = DefaultRegionChain::builder().configure(&self).build();
403 self.with_region(provider_chain.region().await)
404 }
405
406 pub(crate) fn with_fs(self, fs: Fs) -> Self {
407 ProviderConfig {
408 parsed_profile: Default::default(),
409 fs,
410 ..self
411 }
412 }
413
414 pub(crate) fn with_env(self, env: Env) -> Self {
415 ProviderConfig {
416 parsed_profile: Default::default(),
417 env,
418 ..self
419 }
420 }
421
422 pub fn with_time_source(self, time_source: impl TimeSource + 'static) -> Self {
424 ProviderConfig {
425 time_source: time_source.into_shared(),
426 ..self
427 }
428 }
429
430 pub fn with_http_client(self, http_client: impl HttpClient + 'static) -> Self {
432 ProviderConfig {
433 http_client: Some(http_client.into_shared()),
434 ..self
435 }
436 }
437
438 pub fn with_sleep_impl(self, sleep_impl: impl AsyncSleep + 'static) -> Self {
440 ProviderConfig {
441 sleep_impl: Some(sleep_impl.into_shared()),
442 ..self
443 }
444 }
445
446 pub fn with_retry_config(self, retry_config: RetryConfig) -> Self {
458 ProviderConfig {
459 retry_config: Some(retry_config),
460 ..self
461 }
462 }
463
464 pub fn with_timeout_config(self, timeout_config: TimeoutConfig) -> Self {
478 ProviderConfig {
479 timeout_config: Some(timeout_config),
480 ..self
481 }
482 }
483}
484
485#[cfg(test)]
486mod test {
487 use super::ProviderConfig;
488 use aws_smithy_types::retry::RetryConfig;
489 use aws_smithy_types::timeout::TimeoutConfig;
490 use std::time::Duration;
491
492 #[test]
496 fn client_config_threads_retry_and_timeout() {
497 let timeout = TimeoutConfig::builder()
498 .operation_timeout(Duration::from_secs(3))
499 .connect_timeout(Duration::from_secs(1))
500 .build();
501 let conf = ProviderConfig::empty()
502 .with_retry_config(RetryConfig::standard().with_max_attempts(7))
503 .with_timeout_config(timeout.clone());
504
505 let sdk_config = conf.client_config();
506
507 assert_eq!(
508 7,
509 sdk_config
510 .retry_config()
511 .expect("retry config threaded to inner client config")
512 .max_attempts()
513 );
514 assert_eq!(
515 Some(&timeout),
516 sdk_config.timeout_config(),
517 "timeout config threaded to inner client config"
518 );
519 }
520
521 #[test]
524 fn client_config_without_timeout_leaves_it_unset() {
525 let sdk_config = ProviderConfig::empty().client_config();
526 assert!(sdk_config.timeout_config().is_none());
527 assert_eq!(3, sdk_config.retry_config().unwrap().max_attempts());
528 }
529}