1use 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::interceptors::SharedInterceptor;
23use aws_smithy_runtime_api::client::runtime_components::{
24 RuntimeComponentsBuilder, SharedConfigValidator,
25};
26use aws_smithy_runtime_api::client::runtime_plugin::{
27 Order, SharedRuntimePlugin, StaticRuntimePlugin,
28};
29use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
30use aws_smithy_runtime_api::shared::IntoShared;
31use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Layer};
32use aws_smithy_types::retry::RetryConfig;
33use aws_smithy_types::timeout::TimeoutConfig;
34use std::borrow::Cow;
35use std::time::Duration;
36
37pub(crate) const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_millis(3100);
39
40fn default_plugin<CompFn>(name: &'static str, components_fn: CompFn) -> StaticRuntimePlugin
41where
42 CompFn: FnOnce(RuntimeComponentsBuilder) -> RuntimeComponentsBuilder,
43{
44 StaticRuntimePlugin::new()
45 .with_order(Order::Defaults)
46 .with_runtime_components((components_fn)(RuntimeComponentsBuilder::new(name)))
47}
48
49fn layer<LayerFn>(name: &'static str, layer_fn: LayerFn) -> FrozenLayer
50where
51 LayerFn: FnOnce(&mut Layer),
52{
53 let mut layer = Layer::new(name);
54 (layer_fn)(&mut layer);
55 layer.freeze()
56}
57
58#[deprecated(
60 since = "1.8.0",
61 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."
62)]
63pub fn default_http_client_plugin() -> Option<SharedRuntimePlugin> {
64 #[expect(deprecated)]
65 default_http_client_plugin_v2(BehaviorVersion::v2024_03_28())
66}
67
68#[cfg(feature = "connector-hyper-0-14-x")]
79fn warn_legacy_client_default_is_changing(behavior_version: BehaviorVersion) {
80 let emit = || {
81 tracing::warn!(
82 behavior_version = ?behavior_version,
83 "this build resolves to the legacy hyper 0.14.x / http 0.2.x HTTP client. In the 2.x \
84 release, currently expected November 2026, the default becomes the hyper 1.x client: \
85 a different TLS implementation, with different connection-pooling and timeout \
86 behavior. To keep the legacy client, add `features = [\"legacy-https-client\"]` to \
87 your AWS SDK crate now — that spelling is stable across the change, whereas `rustls` \
88 will become a synonym for the hyper 1.x client. To move early instead, use \
89 `BehaviorVersion::v2026_01_12()` or later. \
90 See https://github.com/smithy-lang/smithy-rs/issues/4489",
91 );
92 };
93
94 #[cfg(not(test))]
98 {
99 static WARNED: std::sync::Once = std::sync::Once::new();
100 WARNED.call_once(emit);
101 }
102 #[cfg(test)]
103 emit();
104}
105
106pub fn default_http_client_plugin_v2(
108 behavior_version: BehaviorVersion,
109) -> Option<SharedRuntimePlugin> {
110 let mut _default: Option<SharedHttpClient> = None;
111
112 #[allow(deprecated)]
113 if behavior_version.is_at_least(BehaviorVersion::v2026_01_12()) {
114 #[cfg(all(
118 feature = "connector-hyper-0-14-x",
119 not(feature = "default-https-client")
120 ))]
121 #[allow(deprecated)]
122 {
123 _default = crate::client::http::hyper_014::default_client();
124
125 if _default.is_some() {
128 warn_legacy_client_default_is_changing(behavior_version);
129 }
130 }
131
132 #[cfg(feature = "default-https-client")]
134 {
135 let opts = crate::client::http::DefaultClientOptions::default()
136 .with_behavior_version(behavior_version);
137 _default = crate::client::http::default_https_client(opts);
138 }
139 } else {
140 #[cfg(feature = "connector-hyper-0-14-x")]
142 #[allow(deprecated)]
143 {
144 _default = crate::client::http::hyper_014::default_client();
145
146 if _default.is_some() {
149 warn_legacy_client_default_is_changing(behavior_version);
150 }
151 }
152
153 #[cfg(feature = "default-https-client")]
162 if _default.is_none() {
163 let opts = crate::client::http::DefaultClientOptions::default()
164 .with_behavior_version(behavior_version);
165 _default = crate::client::http::default_https_client(opts);
166
167 if _default.is_some() {
171 tracing::warn!(
172 behavior_version = ?behavior_version,
173 "this behavior version selects the legacy hyper 0.14.x HTTP client, which is \
174 not available in this build, so the default hyper 1.x HTTPS client is being \
175 used instead. Enable the `legacy-https-client` feature on your AWS SDK crate \
176 (or `aws-smithy-runtime/tls-rustls`) to get the legacy stack, or move to \
177 `BehaviorVersion::v2026_01_12()` or later to stop seeing this warning.",
178 );
179 }
180 }
181 }
182
183 _default.map(|default| {
184 default_plugin("default_http_client_plugin", |components| {
185 components.with_http_client(Some(default))
186 })
187 .into_shared()
188 })
189}
190
191pub fn default_sleep_impl_plugin() -> Option<SharedRuntimePlugin> {
193 default_async_sleep().map(|default| {
194 default_plugin("default_sleep_impl_plugin", |components| {
195 components.with_sleep_impl(Some(default))
196 })
197 .into_shared()
198 })
199}
200
201pub fn default_time_source_plugin() -> Option<SharedRuntimePlugin> {
203 Some(
204 default_plugin("default_time_source_plugin", |components| {
205 components.with_time_source(Some(SystemTimeSource::new()))
206 })
207 .into_shared(),
208 )
209}
210
211pub fn default_retry_config_plugin(
213 default_partition_name: impl Into<Cow<'static, str>>,
214) -> Option<SharedRuntimePlugin> {
215 let retry_partition = RetryPartition::new(default_partition_name);
216 Some(
217 default_plugin("default_retry_config_plugin", |components| {
218 components
219 .with_retry_strategy(Some(StandardRetryStrategy::new()))
220 .with_config_validator(SharedConfigValidator::base_client_config_fn(
221 validate_retry_config,
222 ))
223 .with_interceptor(SharedInterceptor::permanent(TokenBucketProvider::new(
228 retry_partition.clone(),
229 )))
230 })
231 .with_config(layer("default_retry_config", |layer| {
232 layer.store_put(RetryConfig::disabled());
233 layer.store_put(retry_partition);
234 }))
235 .into_shared(),
236 )
237}
238
239pub fn default_retry_config_plugin_v2(params: &DefaultPluginParams) -> Option<SharedRuntimePlugin> {
244 let retry_partition = RetryPartition::new(
245 params
246 .retry_partition_name
247 .as_ref()
248 .expect("retry partition name is required")
249 .clone(),
250 );
251 let is_aws_sdk = params.is_aws_sdk;
252 let behavior_version = params
253 .behavior_version
254 .unwrap_or_else(BehaviorVersion::latest);
255 Some(
256 default_plugin("default_retry_config_plugin", |components| {
257 components
258 .with_retry_strategy(Some(StandardRetryStrategy::new()))
259 .with_config_validator(SharedConfigValidator::base_client_config_fn(
260 validate_retry_config,
261 ))
262 .with_interceptor(SharedInterceptor::permanent(TokenBucketProvider::new(
263 retry_partition.clone(),
264 )))
265 })
266 .with_config(layer("default_retry_config", |layer| {
267 #[allow(deprecated)]
268 let retry_config =
269 if is_aws_sdk && behavior_version.is_at_least(BehaviorVersion::v2026_01_12()) {
270 RetryConfig::standard()
271 } else {
272 RetryConfig::disabled()
273 };
274 layer.store_put(retry_config);
275 layer.store_put(retry_partition);
276 }))
277 .into_shared(),
278 )
279}
280
281fn validate_retry_config(
282 components: &RuntimeComponentsBuilder,
283 cfg: &ConfigBag,
284) -> Result<(), BoxError> {
285 if let Some(retry_config) = cfg.load::<RetryConfig>() {
286 if retry_config.has_retry() && components.sleep_impl().is_none() {
287 Err("An async sleep implementation is required for retry to work. Please provide a `sleep_impl` on \
288 the config, or disable timeouts.".into())
289 } else {
290 Ok(())
291 }
292 } else {
293 Err(
294 "The default retry config was removed, and no other config was put in its place."
295 .into(),
296 )
297 }
298}
299
300pub fn default_timeout_config_plugin() -> Option<SharedRuntimePlugin> {
302 Some(
303 default_plugin("default_timeout_config_plugin", |components| {
304 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
305 validate_timeout_config,
306 ))
307 })
308 .with_config(layer("default_timeout_config", |layer| {
309 layer.store_put(TimeoutConfig::disabled());
310 }))
311 .into_shared(),
312 )
313}
314
315pub fn default_timeout_config_plugin_v2(
320 params: &DefaultPluginParams,
321) -> Option<SharedRuntimePlugin> {
322 let behavior_version = params
323 .behavior_version
324 .unwrap_or_else(BehaviorVersion::latest);
325 Some(
326 default_plugin("default_timeout_config_plugin", |components| {
327 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
328 validate_timeout_config,
329 ))
330 })
331 .with_config(layer("default_timeout_config", |layer| {
332 #[allow(deprecated)]
333 let timeout_config = if behavior_version.is_at_least(BehaviorVersion::v2026_01_12()) {
334 TimeoutConfig::builder()
336 .connect_timeout(DEFAULT_CONNECT_TIMEOUT)
337 .build()
338 } else {
339 TimeoutConfig::disabled()
341 };
342 layer.store_put(timeout_config);
343 }))
344 .into_shared(),
345 )
346}
347
348fn validate_timeout_config(
349 components: &RuntimeComponentsBuilder,
350 cfg: &ConfigBag,
351) -> Result<(), BoxError> {
352 if let Some(timeout_config) = cfg.load::<TimeoutConfig>() {
353 if timeout_config.has_timeouts() && components.sleep_impl().is_none() {
354 Err("An async sleep implementation is required for timeouts to work. Please provide a `sleep_impl` on \
355 the config, or disable timeouts.".into())
356 } else {
357 Ok(())
358 }
359 } else {
360 Err(
361 "The default timeout config was removed, and no other config was put in its place."
362 .into(),
363 )
364 }
365}
366
367pub fn default_identity_cache_plugin() -> Option<SharedRuntimePlugin> {
369 Some(
370 default_plugin("default_identity_cache_plugin", |components| {
371 components.with_identity_cache(Some(IdentityCache::lazy().build()))
372 })
373 .into_shared(),
374 )
375}
376
377#[deprecated(
382 since = "1.2.0",
383 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."
384)]
385pub fn default_stalled_stream_protection_config_plugin() -> Option<SharedRuntimePlugin> {
386 #[expect(deprecated)]
387 default_stalled_stream_protection_config_plugin_v2(BehaviorVersion::v2023_11_09())
388}
389fn default_stalled_stream_protection_config_plugin_v2(
390 behavior_version: BehaviorVersion,
391) -> Option<SharedRuntimePlugin> {
392 Some(
393 default_plugin(
394 "default_stalled_stream_protection_config_plugin",
395 |components| {
396 components.with_config_validator(SharedConfigValidator::base_client_config_fn(
397 validate_stalled_stream_protection_config,
398 ))
399 },
400 )
401 .with_config(layer("default_stalled_stream_protection_config", |layer| {
402 let mut config =
403 StalledStreamProtectionConfig::enabled().grace_period(Duration::from_secs(5));
404 #[expect(deprecated)]
406 if !behavior_version.is_at_least(BehaviorVersion::v2024_03_28()) {
407 config = config.upload_enabled(false);
408 }
409 layer.store_put(config.build());
410 }))
411 .into_shared(),
412 )
413}
414
415fn enforce_content_length_runtime_plugin() -> Option<SharedRuntimePlugin> {
416 Some(EnforceContentLengthRuntimePlugin::new().into_shared())
417}
418
419fn validate_stalled_stream_protection_config(
420 components: &RuntimeComponentsBuilder,
421 cfg: &ConfigBag,
422) -> Result<(), BoxError> {
423 if let Some(stalled_stream_protection_config) = cfg.load::<StalledStreamProtectionConfig>() {
424 if stalled_stream_protection_config.is_enabled() {
425 if components.sleep_impl().is_none() {
426 return Err(
427 "An async sleep implementation is required for stalled stream protection to work. \
428 Please provide a `sleep_impl` on the config, or disable stalled stream protection.".into());
429 }
430
431 if components.time_source().is_none() {
432 return Err(
433 "A time source is required for stalled stream protection to work.\
434 Please provide a `time_source` on the config, or disable stalled stream protection.".into());
435 }
436 }
437
438 Ok(())
439 } else {
440 Err(
441 "The default stalled stream protection config was removed, and no other config was put in its place."
442 .into(),
443 )
444 }
445}
446
447#[non_exhaustive]
451#[derive(Debug, Default)]
452pub struct DefaultPluginParams {
453 retry_partition_name: Option<Cow<'static, str>>,
454 behavior_version: Option<BehaviorVersion>,
455 is_aws_sdk: bool,
456}
457
458impl DefaultPluginParams {
459 pub fn new() -> Self {
461 Default::default()
462 }
463
464 pub fn with_retry_partition_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
466 self.retry_partition_name = Some(name.into());
467 self
468 }
469
470 pub fn with_behavior_version(mut self, version: BehaviorVersion) -> Self {
472 self.behavior_version = Some(version);
473 self
474 }
475
476 pub fn with_is_aws_sdk(mut self, is_aws_sdk: bool) -> Self {
478 self.is_aws_sdk = is_aws_sdk;
479 self
480 }
481}
482
483pub fn default_plugins(
485 params: DefaultPluginParams,
486) -> impl IntoIterator<Item = SharedRuntimePlugin> {
487 let behavior_version = params
488 .behavior_version
489 .unwrap_or_else(BehaviorVersion::latest);
490
491 [
492 default_http_client_plugin_v2(behavior_version),
493 default_identity_cache_plugin(),
494 default_retry_config_plugin_v2(¶ms),
495 default_sleep_impl_plugin(),
496 default_time_source_plugin(),
497 default_timeout_config_plugin_v2(¶ms),
498 enforce_content_length_runtime_plugin(),
499 default_stalled_stream_protection_config_plugin_v2(behavior_version),
500 ]
501 .into_iter()
502 .flatten()
503 .collect::<Vec<SharedRuntimePlugin>>()
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509 use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, RuntimePlugins};
510 #[cfg(any(feature = "default-https-client", feature = "tls-rustls"))]
511 use tracing_test::traced_test;
512
513 fn test_plugin_params(version: BehaviorVersion) -> DefaultPluginParams {
514 DefaultPluginParams::new()
515 .with_behavior_version(version)
516 .with_retry_partition_name("dontcare")
517 .with_is_aws_sdk(false) }
519 fn config_for(plugins: impl IntoIterator<Item = SharedRuntimePlugin>) -> ConfigBag {
520 let mut config = ConfigBag::base();
521 let plugins = RuntimePlugins::new().with_client_plugins(plugins);
522 plugins.apply_client_configuration(&mut config).unwrap();
523 config
524 }
525
526 #[test]
527 #[expect(deprecated)]
528 fn v2024_03_28_stalled_stream_protection_difference() {
529 let latest = config_for(default_plugins(test_plugin_params(
530 BehaviorVersion::latest(),
531 )));
532 let v2023 = config_for(default_plugins(test_plugin_params(
533 BehaviorVersion::v2023_11_09(),
534 )));
535
536 assert!(
537 latest
538 .load::<StalledStreamProtectionConfig>()
539 .unwrap()
540 .upload_enabled(),
541 "stalled stream protection on uploads MUST be enabled after v2024_03_28"
542 );
543 assert!(
544 !v2023
545 .load::<StalledStreamProtectionConfig>()
546 .unwrap()
547 .upload_enabled(),
548 "stalled stream protection on uploads MUST NOT be enabled before v2024_03_28"
549 );
550 }
551
552 #[test]
553 fn test_retry_enabled_for_aws_sdk() {
554 let params = DefaultPluginParams::new()
555 .with_retry_partition_name("test-partition")
556 .with_behavior_version(BehaviorVersion::latest())
557 .with_is_aws_sdk(true);
558 let plugin = default_retry_config_plugin_v2(¶ms).expect("plugin should be created");
559
560 let config = plugin.config().expect("config should exist");
561 let retry_config = config
562 .load::<RetryConfig>()
563 .expect("retry config should exist");
564
565 assert_eq!(
566 retry_config.max_attempts(),
567 3,
568 "retries should be enabled with max_attempts=3 for AWS SDK with latest behavior version"
569 );
570 }
571
572 #[test]
573 #[expect(deprecated)]
574 fn test_retry_disabled_for_aws_sdk_old_behavior_version() {
575 let params = DefaultPluginParams::new()
577 .with_retry_partition_name("test-partition")
578 .with_behavior_version(BehaviorVersion::v2024_03_28())
579 .with_is_aws_sdk(true);
580 let plugin = default_retry_config_plugin_v2(¶ms).expect("plugin should be created");
581
582 let config = plugin.config().expect("config should exist");
583 let retry_config = config
584 .load::<RetryConfig>()
585 .expect("retry config should exist");
586
587 assert_eq!(
588 retry_config.max_attempts(),
589 1,
590 "retries should be disabled for AWS SDK with behavior version < v2026_01_12"
591 );
592 }
593
594 #[test]
595 #[allow(deprecated)]
596 fn test_retry_enabled_at_cutoff_version() {
597 let params = DefaultPluginParams::new()
599 .with_retry_partition_name("test-partition")
600 .with_behavior_version(BehaviorVersion::v2026_01_12())
601 .with_is_aws_sdk(true);
602 let plugin = default_retry_config_plugin_v2(¶ms).expect("plugin should be created");
603
604 let config = plugin.config().expect("config should exist");
605 let retry_config = config
606 .load::<RetryConfig>()
607 .expect("retry config should exist");
608
609 assert_eq!(
610 retry_config.max_attempts(),
611 3,
612 "retries should be enabled for AWS SDK starting from v2026_01_12"
613 );
614 }
615
616 #[test]
617 fn test_retry_disabled_for_non_aws_sdk() {
618 let params = DefaultPluginParams::new()
619 .with_retry_partition_name("test-partition")
620 .with_behavior_version(BehaviorVersion::latest())
621 .with_is_aws_sdk(false);
622 let plugin = default_retry_config_plugin_v2(¶ms).expect("plugin should be created");
623
624 let config = plugin.config().expect("config should exist");
625 let retry_config = config
626 .load::<RetryConfig>()
627 .expect("retry config should exist");
628
629 assert_eq!(
630 retry_config.max_attempts(),
631 1,
632 "retries should be disabled for non-AWS SDK clients"
633 );
634 }
635
636 #[test]
637 #[expect(deprecated)]
638 fn test_behavior_version_gates_retry_for_aws_sdk() {
639 let test_cases = vec![
644 (BehaviorVersion::v2023_11_09(), 1, "v2023_11_09 (old)"),
645 (BehaviorVersion::v2024_03_28(), 1, "v2024_03_28 (old)"),
646 (BehaviorVersion::v2025_01_17(), 1, "v2025_01_17 (old)"),
647 (BehaviorVersion::v2025_08_07(), 1, "v2025_08_07 (old)"),
648 (BehaviorVersion::v2026_01_12(), 3, "v2026_01_12 (cutoff)"),
649 (BehaviorVersion::latest(), 3, "latest"),
650 ];
651
652 for (version, expected_attempts, version_name) in test_cases {
653 let params = DefaultPluginParams::new()
654 .with_retry_partition_name("test-partition")
655 .with_behavior_version(version)
656 .with_is_aws_sdk(true);
657
658 let plugin = default_retry_config_plugin_v2(¶ms).expect("plugin should be created");
659 let config = plugin.config().expect("config should exist");
660 let retry_config = config
661 .load::<RetryConfig>()
662 .expect("retry config should exist");
663
664 assert_eq!(
665 retry_config.max_attempts(),
666 expected_attempts,
667 "AWS SDK with {} should have {} max attempts",
668 version_name,
669 expected_attempts
670 );
671 }
672 }
673
674 #[test]
675 #[expect(deprecated)]
676 fn test_complete_default_plugins_integration() {
677 let params_aws_latest = DefaultPluginParams::new()
683 .with_retry_partition_name("aws-s3")
684 .with_behavior_version(BehaviorVersion::latest())
685 .with_is_aws_sdk(true);
686
687 let config_aws_latest = config_for(default_plugins(params_aws_latest));
688 let retry_aws_latest = config_aws_latest
689 .load::<RetryConfig>()
690 .expect("retry config should exist");
691 assert_eq!(
692 retry_aws_latest.max_attempts(),
693 3,
694 "AWS SDK with latest behavior version should have retries enabled (3 attempts)"
695 );
696
697 let params_aws_old = DefaultPluginParams::new()
699 .with_retry_partition_name("aws-s3")
700 .with_behavior_version(BehaviorVersion::v2024_03_28())
701 .with_is_aws_sdk(true);
702
703 let config_aws_old = config_for(default_plugins(params_aws_old));
704 let retry_aws_old = config_aws_old
705 .load::<RetryConfig>()
706 .expect("retry config should exist");
707 assert_eq!(
708 retry_aws_old.max_attempts(),
709 1,
710 "AWS SDK with old behavior version should have retries disabled (1 attempt)"
711 );
712
713 let params_generic = DefaultPluginParams::new()
715 .with_retry_partition_name("my-service")
716 .with_behavior_version(BehaviorVersion::latest())
717 .with_is_aws_sdk(false);
718
719 let config_generic = config_for(default_plugins(params_generic));
720 let retry_generic = config_generic
721 .load::<RetryConfig>()
722 .expect("retry config should exist");
723 assert_eq!(
724 retry_generic.max_attempts(),
725 1,
726 "Non-AWS SDK clients should always have retries disabled (1 attempt)"
727 );
728
729 let params_cutoff = DefaultPluginParams::new()
731 .with_retry_partition_name("aws-s3")
732 .with_behavior_version(BehaviorVersion::v2026_01_12())
733 .with_is_aws_sdk(true);
734
735 let config_cutoff = config_for(default_plugins(params_cutoff));
736 let retry_cutoff = config_cutoff
737 .load::<RetryConfig>()
738 .expect("retry config should exist");
739 assert_eq!(
740 retry_cutoff.max_attempts(),
741 3,
742 "AWS SDK with v2026_01_12 (the cutoff version) should have retries enabled (3 attempts)"
743 );
744 }
745
746 #[test]
757 #[expect(deprecated)]
758 fn old_behavior_version_still_gets_an_http_client() {
759 let old = default_http_client_plugin_v2(BehaviorVersion::v2024_03_28());
760 let latest = default_http_client_plugin_v2(BehaviorVersion::latest());
761
762 #[cfg(feature = "default-https-client")]
765 {
766 assert!(
767 old.is_some(),
768 "a pre-v2026_01_12 behavior version must fall back to the hyper 1.x client \
769 instead of getting no HTTP client"
770 );
771 assert!(
772 latest.is_some(),
773 "the latest behavior version must get the hyper 1.x client"
774 );
775 }
776
777 #[cfg(all(not(feature = "default-https-client"), feature = "tls-rustls"))]
780 assert!(
781 old.is_some(),
782 "a pre-v2026_01_12 behavior version must still get the legacy client when that is \
783 the only stack compiled in"
784 );
785
786 #[cfg(all(
788 not(feature = "default-https-client"),
789 not(feature = "connector-hyper-0-14-x")
790 ))]
791 {
792 assert!(
793 old.is_none(),
794 "no HTTP client stack is compiled in, so there is nothing to install"
795 );
796 assert!(
797 latest.is_none(),
798 "no HTTP client stack is compiled in, so there is nothing to install"
799 );
800 }
801
802 let _ = (old, latest);
803 }
804
805 #[test]
812 #[traced_test]
813 #[expect(deprecated)]
814 #[cfg(all(feature = "default-https-client", not(feature = "tls-rustls")))]
815 fn falling_back_to_the_hyper_1x_client_warns() {
816 let old = default_http_client_plugin_v2(BehaviorVersion::v2024_03_28());
817 assert!(old.is_some(), "the fallback should have supplied a client");
818 assert!(
819 logs_contain("selects the legacy hyper 0.14.x HTTP client"),
820 "falling back to the hyper 1.x client must be logged"
821 );
822 }
823
824 #[test]
830 #[traced_test]
831 #[expect(deprecated)]
832 #[cfg(feature = "tls-rustls")]
833 fn resolving_to_the_legacy_client_warns_that_the_default_is_changing() {
834 let old = default_http_client_plugin_v2(BehaviorVersion::v2024_03_28());
835 assert!(old.is_some(), "the legacy client should have been selected");
836 assert!(
837 logs_contain("the default becomes the hyper 1.x client"),
838 "resolving to the legacy client must announce the upcoming default change"
839 );
840 assert!(
841 logs_contain("legacy-https-client"),
842 "the warning must name the feature that pins the legacy client"
843 );
844 }
845
846 #[test]
849 #[traced_test]
850 #[expect(deprecated)]
851 #[cfg(all(
852 feature = "default-https-client",
853 not(feature = "connector-hyper-0-14-x")
854 ))]
855 fn a_build_without_the_legacy_client_does_not_warn_about_it() {
856 let _ = default_http_client_plugin_v2(BehaviorVersion::v2024_03_28());
857 assert!(
858 !logs_contain("the default becomes the hyper 1.x client"),
859 "a build with no legacy client must not warn about the legacy default changing"
860 );
861 }
862
863 #[test]
865 #[traced_test]
866 #[cfg(feature = "default-https-client")]
867 fn the_latest_behavior_version_does_not_warn() {
868 let latest = default_http_client_plugin_v2(BehaviorVersion::latest());
869 assert!(latest.is_some(), "the latest version should get a client");
870 assert!(
871 !logs_contain("selects the legacy hyper 0.14.x HTTP client"),
872 "the latest behavior version must not warn about the legacy stack"
873 );
874 }
875}