aws_smithy_runtime/client/
defaults.rs1use crate::client::http::body::content_length_enforcement::EnforceContentLengthRuntimePlugin;
13use crate::client::identity::IdentityCache;
14use crate::client::retries::strategy::standard::TokenBucketProvider;
15use crate::client::retries::strategy::StandardRetryStrategy;
16use crate::client::retries::RetryPartition;
17use aws_smithy_async::rt::sleep::default_async_sleep;
18use aws_smithy_async::time::SystemTimeSource;
19use aws_smithy_runtime_api::box_error::BoxError;
20use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
21use aws_smithy_runtime_api::client::http::SharedHttpClient;
22use aws_smithy_runtime_api::client::runtime_components::{
23 RuntimeComponentsBuilder, SharedConfigValidator,
24};
25use aws_smithy_runtime_api::client::runtime_plugin::{
26 Order, SharedRuntimePlugin, StaticRuntimePlugin,
27};
28use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
29use aws_smithy_runtime_api::shared::IntoShared;
30use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Layer};
31use aws_smithy_types::retry::RetryConfig;
32use aws_smithy_types::timeout::TimeoutConfig;
33use std::borrow::Cow;
34use std::time::Duration;
35
36fn default_plugin<CompFn>(name: &'static str, components_fn: CompFn) -> StaticRuntimePlugin
37where
38 CompFn: FnOnce(RuntimeComponentsBuilder) -> RuntimeComponentsBuilder,
39{
40 StaticRuntimePlugin::new()
41 .with_order(Order::Defaults)
42 .with_runtime_components((components_fn)(RuntimeComponentsBuilder::new(name)))
43}
44
45fn layer<LayerFn>(name: &'static str, layer_fn: LayerFn) -> FrozenLayer
46where
47 LayerFn: FnOnce(&mut Layer),
48{
49 let mut layer = Layer::new(name);
50 (layer_fn)(&mut layer);
51 layer.freeze()
52}
53
54#[deprecated(
56 since = "1.8.0",
57 note = "This function wasn't intended to be public, and didn't take the behavior major version as an argument, so it couldn't be evolved over time."
58)]
59pub fn default_http_client_plugin() -> Option<SharedRuntimePlugin> {
60 #[allow(deprecated)]
61 default_http_client_plugin_v2(BehaviorVersion::v2024_03_28())
62}
63
64pub fn default_http_client_plugin_v2(
66 behavior_version: BehaviorVersion,
67) -> Option<SharedRuntimePlugin> {
68 let mut _default: Option<SharedHttpClient> = None;
69
70 #[allow(deprecated)]
71 if behavior_version.is_at_least(BehaviorVersion::v2025_01_17()) {
72 #[cfg(all(
76 feature = "connector-hyper-0-14-x",
77 not(feature = "default-https-client")
78 ))]
79 #[allow(deprecated)]
80 {
81 _default = crate::client::http::hyper_014::default_client();
82 }
83
84 #[cfg(feature = "default-https-client")]
86 {
87 let opts = crate::client::http::DefaultClientOptions::default()
88 .with_behavior_version(behavior_version);
89 _default = crate::client::http::default_https_client(opts);
90 }
91 } else {
92 #[cfg(feature = "connector-hyper-0-14-x")]
94 #[allow(deprecated)]
95 {
96 _default = crate::client::http::hyper_014::default_client();
97 }
98 }
99
100 _default.map(|default| {
101 default_plugin("default_http_client_plugin", |components| {
102 components.with_http_client(Some(default))
103 })
104 .into_shared()
105 })
106}
107
108pub fn default_sleep_impl_plugin() -> Option<SharedRuntimePlugin> {
110 default_async_sleep().map(|default| {
111 default_plugin("default_sleep_impl_plugin", |components| {
112 components.with_sleep_impl(Some(default))
113 })
114 .into_shared()
115 })
116}
117
118pub fn default_time_source_plugin() -> Option<SharedRuntimePlugin> {
120 Some(
121 default_plugin("default_time_source_plugin", |components| {
122 components.with_time_source(Some(SystemTimeSource::new()))
123 })
124 .into_shared(),
125 )
126}
127
128pub fn default_retry_config_plugin(
130 default_partition_name: impl Into<Cow<'static, str>>,
131) -> Option<SharedRuntimePlugin> {
132 let retry_partition = RetryPartition::new(default_partition_name);
133 Some(
134 default_plugin("default_retry_config_plugin", |components| {
135 components
136 .with_retry_strategy(Some(StandardRetryStrategy::new()))
137 .with_config_validator(SharedConfigValidator::base_client_config_fn(
138 validate_retry_config,
139 ))
140 .with_interceptor(TokenBucketProvider::new(retry_partition.clone()))
141 })
142 .with_config(layer("default_retry_config", |layer| {
143 layer.store_put(RetryConfig::disabled());
144 layer.store_put(retry_partition);
145 }))
146 .into_shared(),
147 )
148}
149
150pub fn default_retry_config_plugin_v2(params: &DefaultPluginParams) -> Option<SharedRuntimePlugin> {
155 let default_partition_name = params.retry_partition_name.as_ref()?.clone();
156 let is_aws_sdk = params.is_aws_sdk;
157 let retry_partition = RetryPartition::new(default_partition_name);
158 Some(
159 default_plugin("default_retry_config_plugin", |components| {
160 components
161 .with_retry_strategy(Some(StandardRetryStrategy::new()))
162 .with_config_validator(SharedConfigValidator::base_client_config_fn(
163 validate_retry_config,
164 ))
165 .with_interceptor(TokenBucketProvider::new(retry_partition.clone()))
166 })
167 .with_config(layer("default_retry_config", |layer| {
168 let retry_config = if is_aws_sdk {
169 RetryConfig::standard()
170 } else {
171 RetryConfig::disabled()
172 };
173 layer.store_put(retry_config);
174 layer.store_put(retry_partition);
175 }))
176 .into_shared(),
177 )
178}
179
180fn validate_retry_config(
181 components: &RuntimeComponentsBuilder,
182 cfg: &ConfigBag,
183) -> Result<(), BoxError> {
184 if let Some(retry_config) = cfg.load::<RetryConfig>() {
185 if retry_config.has_retry() && components.sleep_impl().is_none() {
186 Err("An async sleep implementation is required for retry to work. Please provide a `sleep_impl` on \
187 the config, or disable timeouts.".into())
188 } else {
189 Ok(())
190 }
191 } else {
192 Err(
193 "The default retry config was removed, and no other config was put in its place."
194 .into(),
195 )
196 }
197}
198
199pub fn default_timeout_config_plugin() -> Option<SharedRuntimePlugin> {
201 Some(
202 default_plugin("default_timeout_config_plugin", |components| {
203 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
204 validate_timeout_config,
205 ))
206 })
207 .with_config(layer("default_timeout_config", |layer| {
208 layer.store_put(TimeoutConfig::disabled());
209 }))
210 .into_shared(),
211 )
212}
213
214pub fn default_timeout_config_plugin_v2(
219 params: &DefaultPluginParams,
220) -> Option<SharedRuntimePlugin> {
221 let behavior_version = params
222 .behavior_version
223 .unwrap_or_else(BehaviorVersion::latest);
224 let is_aws_sdk = params.is_aws_sdk;
225 Some(
226 default_plugin("default_timeout_config_plugin", |components| {
227 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
228 validate_timeout_config,
229 ))
230 })
231 .with_config(layer("default_timeout_config", |layer| {
232 #[allow(deprecated)]
233 let timeout_config =
234 if is_aws_sdk && behavior_version.is_at_least(BehaviorVersion::v2025_01_17()) {
235 TimeoutConfig::builder()
237 .connect_timeout(Duration::from_millis(3100))
238 .disable_operation_timeout()
239 .disable_operation_attempt_timeout()
240 .build()
241 } else {
242 TimeoutConfig::disabled()
244 };
245 layer.store_put(timeout_config);
246 }))
247 .into_shared(),
248 )
249}
250
251fn validate_timeout_config(
252 components: &RuntimeComponentsBuilder,
253 cfg: &ConfigBag,
254) -> Result<(), BoxError> {
255 if let Some(timeout_config) = cfg.load::<TimeoutConfig>() {
256 if timeout_config.has_timeouts() && components.sleep_impl().is_none() {
257 Err("An async sleep implementation is required for timeouts to work. Please provide a `sleep_impl` on \
258 the config, or disable timeouts.".into())
259 } else {
260 Ok(())
261 }
262 } else {
263 Err(
264 "The default timeout config was removed, and no other config was put in its place."
265 .into(),
266 )
267 }
268}
269
270pub fn default_identity_cache_plugin() -> Option<SharedRuntimePlugin> {
272 Some(
273 default_plugin("default_identity_cache_plugin", |components| {
274 components.with_identity_cache(Some(IdentityCache::lazy().build()))
275 })
276 .into_shared(),
277 )
278}
279
280#[deprecated(
285 since = "1.2.0",
286 note = "This function wasn't intended to be public, and didn't take the behavior major version as an argument, so it couldn't be evolved over time."
287)]
288pub fn default_stalled_stream_protection_config_plugin() -> Option<SharedRuntimePlugin> {
289 #[allow(deprecated)]
290 default_stalled_stream_protection_config_plugin_v2(BehaviorVersion::v2023_11_09())
291}
292fn default_stalled_stream_protection_config_plugin_v2(
293 behavior_version: BehaviorVersion,
294) -> Option<SharedRuntimePlugin> {
295 Some(
296 default_plugin(
297 "default_stalled_stream_protection_config_plugin",
298 |components| {
299 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
300 validate_stalled_stream_protection_config,
301 ))
302 },
303 )
304 .with_config(layer("default_stalled_stream_protection_config", |layer| {
305 let mut config =
306 StalledStreamProtectionConfig::enabled().grace_period(Duration::from_secs(5));
307 #[allow(deprecated)]
309 if !behavior_version.is_at_least(BehaviorVersion::v2024_03_28()) {
310 config = config.upload_enabled(false);
311 }
312 layer.store_put(config.build());
313 }))
314 .into_shared(),
315 )
316}
317
318fn enforce_content_length_runtime_plugin() -> Option<SharedRuntimePlugin> {
319 Some(EnforceContentLengthRuntimePlugin::new().into_shared())
320}
321
322fn validate_stalled_stream_protection_config(
323 components: &RuntimeComponentsBuilder,
324 cfg: &ConfigBag,
325) -> Result<(), BoxError> {
326 if let Some(stalled_stream_protection_config) = cfg.load::<StalledStreamProtectionConfig>() {
327 if stalled_stream_protection_config.is_enabled() {
328 if components.sleep_impl().is_none() {
329 return Err(
330 "An async sleep implementation is required for stalled stream protection to work. \
331 Please provide a `sleep_impl` on the config, or disable stalled stream protection.".into());
332 }
333
334 if components.time_source().is_none() {
335 return Err(
336 "A time source is required for stalled stream protection to work.\
337 Please provide a `time_source` on the config, or disable stalled stream protection.".into());
338 }
339 }
340
341 Ok(())
342 } else {
343 Err(
344 "The default stalled stream protection config was removed, and no other config was put in its place."
345 .into(),
346 )
347 }
348}
349
350#[non_exhaustive]
354#[derive(Debug, Default)]
355pub struct DefaultPluginParams {
356 retry_partition_name: Option<Cow<'static, str>>,
357 behavior_version: Option<BehaviorVersion>,
358 is_aws_sdk: bool,
359}
360
361impl DefaultPluginParams {
362 pub fn new() -> Self {
364 Default::default()
365 }
366
367 pub fn with_retry_partition_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
369 self.retry_partition_name = Some(name.into());
370 self
371 }
372
373 pub fn with_behavior_version(mut self, version: BehaviorVersion) -> Self {
375 self.behavior_version = Some(version);
376 self
377 }
378
379 pub fn with_is_aws_sdk(mut self, is_aws_sdk: bool) -> Self {
381 self.is_aws_sdk = is_aws_sdk;
382 self
383 }
384}
385
386pub fn default_plugins(
388 params: DefaultPluginParams,
389) -> impl IntoIterator<Item = SharedRuntimePlugin> {
390 let behavior_version = params
391 .behavior_version
392 .unwrap_or_else(BehaviorVersion::latest);
393
394 [
395 default_http_client_plugin_v2(behavior_version),
396 default_identity_cache_plugin(),
397 default_retry_config_plugin_v2(¶ms),
398 default_sleep_impl_plugin(),
399 default_time_source_plugin(),
400 default_timeout_config_plugin_v2(¶ms),
401 enforce_content_length_runtime_plugin(),
402 default_stalled_stream_protection_config_plugin_v2(behavior_version),
403 ]
404 .into_iter()
405 .flatten()
406 .collect::<Vec<SharedRuntimePlugin>>()
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, RuntimePlugins};
413
414 fn test_plugin_params(version: BehaviorVersion) -> DefaultPluginParams {
415 DefaultPluginParams::new()
416 .with_behavior_version(version)
417 .with_retry_partition_name("dontcare")
418 .with_is_aws_sdk(false) }
420 fn config_for(plugins: impl IntoIterator<Item = SharedRuntimePlugin>) -> ConfigBag {
421 let mut config = ConfigBag::base();
422 let plugins = RuntimePlugins::new().with_client_plugins(plugins);
423 plugins.apply_client_configuration(&mut config).unwrap();
424 config
425 }
426
427 #[test]
428 #[allow(deprecated)]
429 fn v2024_03_28_stalled_stream_protection_difference() {
430 let latest = config_for(default_plugins(test_plugin_params(
431 BehaviorVersion::latest(),
432 )));
433 let v2023 = config_for(default_plugins(test_plugin_params(
434 BehaviorVersion::v2023_11_09(),
435 )));
436
437 assert!(
438 latest
439 .load::<StalledStreamProtectionConfig>()
440 .unwrap()
441 .upload_enabled(),
442 "stalled stream protection on uploads MUST be enabled after v2024_03_28"
443 );
444 assert!(
445 !v2023
446 .load::<StalledStreamProtectionConfig>()
447 .unwrap()
448 .upload_enabled(),
449 "stalled stream protection on uploads MUST NOT be enabled before v2024_03_28"
450 );
451 }
452
453 #[test]
454 fn test_retry_enabled_for_aws_sdk() {
455 let params = DefaultPluginParams::new()
456 .with_retry_partition_name("test-partition")
457 .with_is_aws_sdk(true);
458 let plugin = default_retry_config_plugin_v2(¶ms).expect("plugin should be created");
459
460 let config = plugin.config().expect("config should exist");
461 let retry_config = config
462 .load::<RetryConfig>()
463 .expect("retry config should exist");
464
465 assert_eq!(
466 retry_config.max_attempts(),
467 3,
468 "retries should be enabled with max_attempts=3 for AWS SDK"
469 );
470 }
471
472 #[test]
473 fn test_retry_disabled_for_non_aws_sdk() {
474 let params = DefaultPluginParams::new()
475 .with_retry_partition_name("test-partition")
476 .with_is_aws_sdk(false);
477 let plugin = default_retry_config_plugin_v2(¶ms).expect("plugin should be created");
478
479 let config = plugin.config().expect("config should exist");
480 let retry_config = config
481 .load::<RetryConfig>()
482 .expect("retry config should exist");
483
484 assert_eq!(
485 retry_config.max_attempts(),
486 1,
487 "retries should be disabled for non-AWS SDK clients"
488 );
489 }
490}