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(
154 default_partition_name: impl Into<Cow<'static, str>>,
155 behavior_version: BehaviorVersion,
156) -> Option<SharedRuntimePlugin> {
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 #[allow(deprecated)]
169 let retry_config = if behavior_version.is_at_least(BehaviorVersion::v2025_01_17()) {
170 RetryConfig::standard()
171 } else {
172 RetryConfig::disabled()
173 };
174 layer.store_put(retry_config);
175 layer.store_put(retry_partition);
176 }))
177 .into_shared(),
178 )
179}
180
181fn validate_retry_config(
182 components: &RuntimeComponentsBuilder,
183 cfg: &ConfigBag,
184) -> Result<(), BoxError> {
185 if let Some(retry_config) = cfg.load::<RetryConfig>() {
186 if retry_config.has_retry() && components.sleep_impl().is_none() {
187 Err("An async sleep implementation is required for retry to work. Please provide a `sleep_impl` on \
188 the config, or disable timeouts.".into())
189 } else {
190 Ok(())
191 }
192 } else {
193 Err(
194 "The default retry config was removed, and no other config was put in its place."
195 .into(),
196 )
197 }
198}
199
200pub fn default_timeout_config_plugin() -> Option<SharedRuntimePlugin> {
202 Some(
203 default_plugin("default_timeout_config_plugin", |components| {
204 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
205 validate_timeout_config,
206 ))
207 })
208 .with_config(layer("default_timeout_config", |layer| {
209 layer.store_put(TimeoutConfig::disabled());
210 }))
211 .into_shared(),
212 )
213}
214
215pub fn default_timeout_config_plugin_v2(
219 behavior_version: BehaviorVersion,
220) -> Option<SharedRuntimePlugin> {
221 Some(
222 default_plugin("default_timeout_config_plugin", |components| {
223 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
224 validate_timeout_config,
225 ))
226 })
227 .with_config(layer("default_timeout_config", |layer| {
228 #[allow(deprecated)]
229 let timeout_config = if behavior_version.is_at_least(BehaviorVersion::v2025_01_17()) {
230 TimeoutConfig::builder()
231 .connect_timeout(Duration::from_millis(3100))
232 .build()
233 } else {
234 TimeoutConfig::disabled()
235 };
236 layer.store_put(timeout_config);
237 }))
238 .into_shared(),
239 )
240}
241
242fn validate_timeout_config(
243 components: &RuntimeComponentsBuilder,
244 cfg: &ConfigBag,
245) -> Result<(), BoxError> {
246 if let Some(timeout_config) = cfg.load::<TimeoutConfig>() {
247 if timeout_config.has_timeouts() && components.sleep_impl().is_none() {
248 Err("An async sleep implementation is required for timeouts to work. Please provide a `sleep_impl` on \
249 the config, or disable timeouts.".into())
250 } else {
251 Ok(())
252 }
253 } else {
254 Err(
255 "The default timeout config was removed, and no other config was put in its place."
256 .into(),
257 )
258 }
259}
260
261pub fn default_identity_cache_plugin() -> Option<SharedRuntimePlugin> {
263 Some(
264 default_plugin("default_identity_cache_plugin", |components| {
265 components.with_identity_cache(Some(IdentityCache::lazy().build()))
266 })
267 .into_shared(),
268 )
269}
270
271#[deprecated(
276 since = "1.2.0",
277 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."
278)]
279pub fn default_stalled_stream_protection_config_plugin() -> Option<SharedRuntimePlugin> {
280 #[allow(deprecated)]
281 default_stalled_stream_protection_config_plugin_v2(BehaviorVersion::v2023_11_09())
282}
283fn default_stalled_stream_protection_config_plugin_v2(
284 behavior_version: BehaviorVersion,
285) -> Option<SharedRuntimePlugin> {
286 Some(
287 default_plugin(
288 "default_stalled_stream_protection_config_plugin",
289 |components| {
290 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
291 validate_stalled_stream_protection_config,
292 ))
293 },
294 )
295 .with_config(layer("default_stalled_stream_protection_config", |layer| {
296 let mut config =
297 StalledStreamProtectionConfig::enabled().grace_period(Duration::from_secs(5));
298 #[allow(deprecated)]
300 if !behavior_version.is_at_least(BehaviorVersion::v2024_03_28()) {
301 config = config.upload_enabled(false);
302 }
303 layer.store_put(config.build());
304 }))
305 .into_shared(),
306 )
307}
308
309fn enforce_content_length_runtime_plugin() -> Option<SharedRuntimePlugin> {
310 Some(EnforceContentLengthRuntimePlugin::new().into_shared())
311}
312
313fn validate_stalled_stream_protection_config(
314 components: &RuntimeComponentsBuilder,
315 cfg: &ConfigBag,
316) -> Result<(), BoxError> {
317 if let Some(stalled_stream_protection_config) = cfg.load::<StalledStreamProtectionConfig>() {
318 if stalled_stream_protection_config.is_enabled() {
319 if components.sleep_impl().is_none() {
320 return Err(
321 "An async sleep implementation is required for stalled stream protection to work. \
322 Please provide a `sleep_impl` on the config, or disable stalled stream protection.".into());
323 }
324
325 if components.time_source().is_none() {
326 return Err(
327 "A time source is required for stalled stream protection to work.\
328 Please provide a `time_source` on the config, or disable stalled stream protection.".into());
329 }
330 }
331
332 Ok(())
333 } else {
334 Err(
335 "The default stalled stream protection config was removed, and no other config was put in its place."
336 .into(),
337 )
338 }
339}
340
341#[non_exhaustive]
345#[derive(Debug, Default)]
346pub struct DefaultPluginParams {
347 retry_partition_name: Option<Cow<'static, str>>,
348 behavior_version: Option<BehaviorVersion>,
349}
350
351impl DefaultPluginParams {
352 pub fn new() -> Self {
354 Default::default()
355 }
356
357 pub fn with_retry_partition_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
359 self.retry_partition_name = Some(name.into());
360 self
361 }
362
363 pub fn with_behavior_version(mut self, version: BehaviorVersion) -> Self {
365 self.behavior_version = Some(version);
366 self
367 }
368}
369
370pub fn default_plugins(
372 params: DefaultPluginParams,
373) -> impl IntoIterator<Item = SharedRuntimePlugin> {
374 let behavior_version = params
375 .behavior_version
376 .unwrap_or_else(BehaviorVersion::latest);
377
378 [
379 default_http_client_plugin_v2(behavior_version),
380 default_identity_cache_plugin(),
381 default_retry_config_plugin_v2(
382 params
383 .retry_partition_name
384 .expect("retry_partition_name is required"),
385 behavior_version,
386 ),
387 default_sleep_impl_plugin(),
388 default_time_source_plugin(),
389 default_timeout_config_plugin_v2(behavior_version),
390 enforce_content_length_runtime_plugin(),
391 default_stalled_stream_protection_config_plugin_v2(behavior_version),
392 ]
393 .into_iter()
394 .flatten()
395 .collect::<Vec<SharedRuntimePlugin>>()
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401 use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins;
402
403 fn test_plugin_params(version: BehaviorVersion) -> DefaultPluginParams {
404 DefaultPluginParams::new()
405 .with_behavior_version(version)
406 .with_retry_partition_name("dontcare")
407 }
408 fn config_for(plugins: impl IntoIterator<Item = SharedRuntimePlugin>) -> ConfigBag {
409 let mut config = ConfigBag::base();
410 let plugins = RuntimePlugins::new().with_client_plugins(plugins);
411 plugins.apply_client_configuration(&mut config).unwrap();
412 config
413 }
414
415 #[test]
416 #[allow(deprecated)]
417 fn v2024_03_28_stalled_stream_protection_difference() {
418 let latest = config_for(default_plugins(test_plugin_params(
419 BehaviorVersion::latest(),
420 )));
421 let v2023 = config_for(default_plugins(test_plugin_params(
422 BehaviorVersion::v2023_11_09(),
423 )));
424
425 assert!(
426 latest
427 .load::<StalledStreamProtectionConfig>()
428 .unwrap()
429 .upload_enabled(),
430 "stalled stream protection on uploads MUST be enabled after v2024_03_28"
431 );
432 assert!(
433 !v2023
434 .load::<StalledStreamProtectionConfig>()
435 .unwrap()
436 .upload_enabled(),
437 "stalled stream protection on uploads MUST NOT be enabled before v2024_03_28"
438 );
439 }
440
441 #[test]
442 #[allow(deprecated)]
443 fn v2025_01_17_retry_config_enabled_by_default() {
444 let latest = config_for(default_plugins(test_plugin_params(
445 BehaviorVersion::latest(),
446 )));
447 let v2024 = config_for(default_plugins(test_plugin_params(
448 BehaviorVersion::v2024_03_28(),
449 )));
450
451 assert!(
452 latest.load::<RetryConfig>().unwrap().has_retry(),
453 "retries MUST be enabled by default for v2025_01_17 and later"
454 );
455 assert!(
456 !v2024.load::<RetryConfig>().unwrap().has_retry(),
457 "retries MUST be disabled by default before v2025_01_17"
458 );
459 }
460
461 #[test]
462 #[allow(deprecated)]
463 fn v2025_01_17_connection_timeout_enabled_by_default() {
464 let latest = config_for(default_plugins(test_plugin_params(
465 BehaviorVersion::latest(),
466 )));
467 let v2024 = config_for(default_plugins(test_plugin_params(
468 BehaviorVersion::v2024_03_28(),
469 )));
470
471 let latest_timeout = latest.load::<TimeoutConfig>().unwrap();
472 assert_eq!(
473 latest_timeout.connect_timeout(),
474 Some(Duration::from_millis(3100)),
475 "connection timeout MUST be 3.1s by default for v2025_01_17 and later"
476 );
477
478 let v2024_timeout = v2024.load::<TimeoutConfig>().unwrap();
479 assert_eq!(
480 v2024_timeout.connect_timeout(),
481 None,
482 "connection timeout MUST be disabled by default before v2025_01_17"
483 );
484 }
485}