From 3f364c7ff5a0d4469712cc9ce7cbe42f6e8d3065 Mon Sep 17 00:00:00 2001 From: martintomka Date: Tue, 14 Apr 2026 08:21:55 +0200 Subject: [PATCH 1/8] relax static bounds --- crates/layered/src/dynamic.rs | 4 ++-- crates/layered/src/execute.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/layered/src/dynamic.rs b/crates/layered/src/dynamic.rs index cf88150b0..48648d08f 100644 --- a/crates/layered/src/dynamic.rs +++ b/crates/layered/src/dynamic.rs @@ -16,7 +16,7 @@ pub trait DynamicServiceExt: Sized { fn into_dynamic(self) -> DynamicService; } -impl DynamicServiceExt for T +impl DynamicServiceExt for T where T: Service + 'static, { @@ -53,7 +53,7 @@ where /// ``` pub struct DynamicService(Arc>); -impl DynamicService { +impl DynamicService { pub(crate) fn new(strategy: T) -> Self where T: Service + Send + Sync + 'static, diff --git a/crates/layered/src/execute.rs b/crates/layered/src/execute.rs index f6f94cbfc..0c9775f54 100644 --- a/crates/layered/src/execute.rs +++ b/crates/layered/src/execute.rs @@ -47,10 +47,10 @@ impl Execute { #[must_use] pub fn new(e: E) -> Self where - E: Fn(In) -> F + Send + Sync + 'static, - In: Send + 'static, - F: Future + Send + 'static, - Out: Send + 'static, + E: Fn(In) -> F + Send + Sync, + In: Send, + F: Future + Send, + Out: Send, { Self(e) } From e7d01e2c69a24085dcb649bbc94c0b852b167068 Mon Sep 17 00:00:00 2001 From: martintomka Date: Tue, 14 Apr 2026 08:45:12 +0200 Subject: [PATCH 2/8] relax more bounds --- crates/seatbelt/src/chaos/injection/service.rs | 6 +++--- crates/seatbelt/src/chaos/latency/service.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/seatbelt/src/chaos/injection/service.rs b/crates/seatbelt/src/chaos/injection/service.rs index f1ad4b091..4015cf234 100644 --- a/crates/seatbelt/src/chaos/injection/service.rs +++ b/crates/seatbelt/src/chaos/injection/service.rs @@ -106,8 +106,8 @@ impl Injection { // body MUST be mirrored in the `call` body, and vice versa. See crate-level AGENTS.md. impl Service for Injection where - In: Send + 'static, - Out: Send + 'static, + In: Send, + Out: Send, S: Service, { type Out = Out; @@ -186,7 +186,7 @@ where } } -impl InjectionShared { +impl InjectionShared { fn should_inject(&self, input: &In) -> bool { let rate = self.rate.call(input, InjectionRateArgs {}).clamp(0.0, 1.0); self.rnd.next_f64() < rate diff --git a/crates/seatbelt/src/chaos/latency/service.rs b/crates/seatbelt/src/chaos/latency/service.rs index da0402ef8..69fb4c0a9 100644 --- a/crates/seatbelt/src/chaos/latency/service.rs +++ b/crates/seatbelt/src/chaos/latency/service.rs @@ -108,8 +108,8 @@ impl Latency { // body MUST be mirrored in the `call` body, and vice versa. See crate-level AGENTS.md. impl Service for Latency where - In: Send + 'static, - Out: Send + 'static, + In: Send, + Out: Send, S: Service, { type Out = Out; @@ -199,7 +199,7 @@ where } } -impl LatencyShared { +impl LatencyShared { #[cfg_attr(test, mutants::skip)] // causes test timeouts fn should_inject(&self, input: &In) -> bool { let rate = self.rate.call(input, LatencyRateArgs {}).clamp(0.0, 1.0); From 1480390b54244277a59a42902dd41aaa7ac8d8fc Mon Sep 17 00:00:00 2001 From: martintomka Date: Tue, 14 Apr 2026 09:05:31 +0200 Subject: [PATCH 3/8] cleanup --- crates/seatbelt/src/chaos/injection/layer.rs | 12 ++++++------ crates/seatbelt/src/fallback/callbacks.rs | 20 +++++++++++--------- crates/seatbelt/src/fallback/layer.rs | 6 ++++-- crates/seatbelt/src/fallback/service.rs | 4 ++-- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/crates/seatbelt/src/chaos/injection/layer.rs b/crates/seatbelt/src/chaos/injection/layer.rs index 28529f2de..a0da8620c 100644 --- a/crates/seatbelt/src/chaos/injection/layer.rs +++ b/crates/seatbelt/src/chaos/injection/layer.rs @@ -140,7 +140,7 @@ impl InjectionLayer { } } -impl InjectionLayer { +impl InjectionLayer { /// Sets a callback-based output factory for the injected output. /// /// The `output_fn` receives the consumed input and [`InjectionOutputArgs`], @@ -164,7 +164,7 @@ impl InjectionLayer InjectionLayer where - Out: Clone + Sync, + Out: Clone + Sync + 'static, { self.output_with(move |_, _| value.clone()) } @@ -172,9 +172,9 @@ impl InjectionLayer InjectionLayer, S1, S2> where - In: Send + 'static, - Ok: Send + 'static, - Err: Send + 'static, + In: Send, + Ok: Send, + Err: Send, { /// Sets a callback-based error factory for the injected output. /// @@ -204,7 +204,7 @@ where #[must_use] pub fn output_error(self, error: Err) -> InjectionLayer, S1, Set> where - Err: Clone + Sync, + Err: Clone + Sync + 'static, { self.output_error_with(move |_, _| error.clone()) } diff --git a/crates/seatbelt/src/fallback/callbacks.rs b/crates/seatbelt/src/fallback/callbacks.rs index a00cf7b12..94aa8ca52 100644 --- a/crates/seatbelt/src/fallback/callbacks.rs +++ b/crates/seatbelt/src/fallback/callbacks.rs @@ -20,12 +20,22 @@ pub(crate) enum FallbackAction { Async(AsyncFallbackFn), } -impl FallbackAction { +impl FallbackAction { /// Create from a synchronous closure. pub(crate) fn new_sync(f: impl Fn(Out, FallbackActionArgs) -> Out + Send + Sync + 'static) -> Self { Self::Sync(SyncFallbackFn::new(f)) } + /// Invoke the fallback action. + pub(crate) async fn call(&self, out: Out, args: FallbackActionArgs) -> Out { + match self { + Self::Sync(f) => f.call(out, args), + Self::Async(f) => f.call(out, args).await, + } + } +} + +impl FallbackAction { /// Create from an asynchronous closure. pub(crate) fn new_async(f: F) -> Self where @@ -34,14 +44,6 @@ impl FallbackAction { { Self::Async(AsyncFallbackFn::new(move |out, args| Box::pin(f(out, args)))) } - - /// Invoke the fallback action. - pub(crate) async fn call(&self, out: Out, args: FallbackActionArgs) -> Out { - match self { - Self::Sync(f) => f.call(out, args), - Self::Async(f) => f.call(out, args).await, - } - } } impl Clone for FallbackAction { diff --git a/crates/seatbelt/src/fallback/layer.rs b/crates/seatbelt/src/fallback/layer.rs index 56a0be7c3..82feef520 100644 --- a/crates/seatbelt/src/fallback/layer.rs +++ b/crates/seatbelt/src/fallback/layer.rs @@ -107,7 +107,7 @@ impl FallbackLayer { } } -impl FallbackLayer { +impl FallbackLayer { /// Sets a synchronous fallback action. /// /// The `action` receives the original (invalid) output and [`FallbackActionArgs`] @@ -129,11 +129,13 @@ impl FallbackLayer { #[must_use] pub fn fallback_output(self, value: Out) -> FallbackLayer where - Out: Clone + Sync, + Out: Clone + Sync + 'static, { self.fallback(move |_, _| value.clone()) } +} +impl FallbackLayer { /// Sets an asynchronous fallback action. /// /// The `action` receives the original (invalid) output and [`FallbackActionArgs`] diff --git a/crates/seatbelt/src/fallback/service.rs b/crates/seatbelt/src/fallback/service.rs index 108b2734d..98907fd1d 100644 --- a/crates/seatbelt/src/fallback/service.rs +++ b/crates/seatbelt/src/fallback/service.rs @@ -101,7 +101,7 @@ impl Fallback { impl Service for Fallback where In: Send, - Out: Send + 'static, + Out: Send, S: Service, { type Out = Out; @@ -186,7 +186,7 @@ where } } -impl FallbackShared { +impl FallbackShared { async fn handle_fallback(&self, output: Out) -> Out { let new_output = self.fallback_action.call(output, FallbackActionArgs {}).await; From 43403ec96812ad30ebe5626f3460c2b44a9f6925 Mon Sep 17 00:00:00 2001 From: martintomka Date: Tue, 14 Apr 2026 09:51:38 +0200 Subject: [PATCH 4/8] add tests --- crates/layered/tests/execute.rs | 18 ++++++ crates/layered/tests/intercept.rs | 29 ++++++++++ crates/seatbelt/Cargo.toml | 2 +- crates/seatbelt/tests/breaker.rs | 18 ++++++ crates/seatbelt/tests/chaos_injection.rs | 18 ++++++ crates/seatbelt/tests/chaos_latency.rs | 18 ++++++ crates/seatbelt/tests/fallback.rs | 18 ++++++ crates/seatbelt/tests/hedging.rs | 18 ++++++ crates/seatbelt/tests/references.rs | 74 ++++++++++++++++++++++++ crates/seatbelt/tests/retry.rs | 18 ++++++ crates/seatbelt/tests/timeout.rs | 18 ++++++ 11 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 crates/layered/tests/execute.rs create mode 100644 crates/layered/tests/intercept.rs create mode 100644 crates/seatbelt/tests/references.rs diff --git a/crates/layered/tests/execute.rs b/crates/layered/tests/execute.rs new file mode 100644 index 000000000..b415d8e55 --- /dev/null +++ b/crates/layered/tests/execute.rs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(missing_docs, reason = "This is a test module")] +#![cfg(not(miri))] + +//! Integration tests for [`Execute`] service. + +use layered::{Execute, Service}; + +#[tokio::test] +async fn str_references() { + let service = Execute::new(|input: &str| async move { input }); + + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/layered/tests/intercept.rs b/crates/layered/tests/intercept.rs new file mode 100644 index 000000000..9cedfe404 --- /dev/null +++ b/crates/layered/tests/intercept.rs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(missing_docs, reason = "This is a test module")] +#![cfg(feature = "intercept")] +#![cfg(not(miri))] + +//! Integration tests for [`Intercept`] middleware. + +use layered::{Execute, Intercept, Service, Stack}; + +#[tokio::test] +async fn str_references() { + let stack = ( + Intercept::<&str, &str, _>::layer() + .on_input(|input: &&str| { + assert!(!input.is_empty()); + }) + .on_output(|output: &&str| { + assert!(!output.is_empty()); + }), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/seatbelt/Cargo.toml b/crates/seatbelt/Cargo.toml index 2f66462da..fd2a17c59 100644 --- a/crates/seatbelt/Cargo.toml +++ b/crates/seatbelt/Cargo.toml @@ -73,7 +73,7 @@ futures-util = { workspace = true, features = ["alloc"] } http.workspace = true insta = { workspace = true, features = ["json"] } jiff = { workspace = true, default-features = true, features = ["serde"] } -layered = { workspace = true, features = ["tower-service", "dynamic-service"] } +layered = { workspace = true, features = ["tower-service", "dynamic-service", "intercept"] } mutants.workspace = true ohno = { workspace = true, features = ["app-err"] } opentelemetry = { workspace = true, default-features = false, features = ["metrics"] } diff --git a/crates/seatbelt/tests/breaker.rs b/crates/seatbelt/tests/breaker.rs index 3293dbb14..8786d6753 100644 --- a/crates/seatbelt/tests/breaker.rs +++ b/crates/seatbelt/tests/breaker.rs @@ -243,3 +243,21 @@ async fn clone_service_shares_circuit_state(#[case] use_tower: bool) { assert_eq!(result1, Ok("circuit is open".to_string())); assert_eq!(result2, Ok("circuit is open".to_string())); } + +#[tokio::test] +async fn str_references() { + let clock = Clock::new_frozen(); + let context: ResilienceContext<&str, &str> = ResilienceContext::new(&clock); + + let stack = ( + Breaker::layer("test_breaker", &context) + .recovery_with(|_output: &&str, _| RecoveryInfo::never()) + .rejected_input(|_input: &str, _| "circuit is open"), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/seatbelt/tests/chaos_injection.rs b/crates/seatbelt/tests/chaos_injection.rs index a374af4f7..ac39c64c0 100644 --- a/crates/seatbelt/tests/chaos_injection.rs +++ b/crates/seatbelt/tests/chaos_injection.rs @@ -331,3 +331,21 @@ async fn rate_with_clamps_above_one(#[case] use_tower: bool) { assert_eq!(output, Ok("injected".to_string())); } + +#[tokio::test] +async fn str_references() { + let clock = Clock::new_frozen(); + let context: ResilienceContext<&str, &str> = ResilienceContext::new(&clock); + + let stack = ( + Injection::layer("test_injection", &context) + .rate(0.0) + .output_with(|_input: &str, _args| "injected"), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/seatbelt/tests/chaos_latency.rs b/crates/seatbelt/tests/chaos_latency.rs index 0d25036f1..96d355928 100644 --- a/crates/seatbelt/tests/chaos_latency.rs +++ b/crates/seatbelt/tests/chaos_latency.rs @@ -362,3 +362,21 @@ async fn inner_service_output_preserved(#[case] use_tower: bool) { // Unlike injection, latency preserves the inner service output. assert_eq!(output, Ok("processed:hello".to_string())); } + +#[tokio::test] +async fn str_references() { + let clock = ClockControl::default().auto_advance_timers(true).to_clock(); + let context: ResilienceContext<&str, &str> = ResilienceContext::new(&clock); + + let stack = ( + Latency::layer("test_latency", &context) + .rate(0.0) + .latency(Duration::from_millis(10)), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/seatbelt/tests/fallback.rs b/crates/seatbelt/tests/fallback.rs index f5ccf509f..515b2fd9a 100644 --- a/crates/seatbelt/tests/fallback.rs +++ b/crates/seatbelt/tests/fallback.rs @@ -220,3 +220,21 @@ async fn fallback_output_returns_fixed_value(#[case] use_tower: bool) { assert_eq!(output1, Ok("fixed_default".to_string())); assert_eq!(output2, Ok("fixed_default".to_string())); } + +#[tokio::test] +async fn str_references() { + let clock = Clock::new_frozen(); + let context: ResilienceContext<&str, &str> = ResilienceContext::new(&clock); + + let stack = ( + Fallback::layer("test_fallback", &context) + .should_fallback(|output: &&str| output.is_empty()) + .fallback(|_output, _args| "fallback_value"), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/seatbelt/tests/hedging.rs b/crates/seatbelt/tests/hedging.rs index 8f303725a..32209a84a 100644 --- a/crates/seatbelt/tests/hedging.rs +++ b/crates/seatbelt/tests/hedging.rs @@ -687,3 +687,21 @@ async fn invoke_on_execute_actually_calls_callback(#[case] use_tower: bool) { "on_execute callback must be invoked at least once" ); } + +#[tokio::test] +async fn str_references() { + let clock = Clock::new_frozen(); + let context: ResilienceContext<&str, &str> = ResilienceContext::new(&clock); + + let stack = ( + Hedging::layer("test_hedging", &context) + .clone_input() + .recovery_with(|_output: &&str, _| RecoveryInfo::never()), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/seatbelt/tests/references.rs b/crates/seatbelt/tests/references.rs new file mode 100644 index 000000000..3967373a2 --- /dev/null +++ b/crates/seatbelt/tests/references.rs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(dead_code, reason = "This is a test module")] +#![allow(missing_docs, reason = "This is a test module")] +#![cfg(all( + feature = "timeout", + feature = "retry", + feature = "breaker", + feature = "hedging", + feature = "fallback", + feature = "chaos-injection", + feature = "chaos-latency", +))] +#![cfg(not(miri))] + +//! Integration test verifying that all resilience middleware can be stacked +//! together with non-static reference input and output types (`&str`). + +use std::time::Duration; + +use layered::{Execute, Intercept, Service, Stack}; +use seatbelt::breaker::Breaker; +use seatbelt::chaos::injection::Injection; +use seatbelt::chaos::latency::Latency; +use seatbelt::fallback::Fallback; +use seatbelt::hedging::Hedging; +use seatbelt::retry::Retry; +use seatbelt::timeout::Timeout; +use seatbelt::{RecoveryInfo, ResilienceContext}; +use tick::ClockControl; + +#[tokio::test] +async fn all_middleware_stacked_with_str_references() { + let clock = ClockControl::default().auto_advance_timers(true).to_clock(); + let context: ResilienceContext<&str, &str> = ResilienceContext::new(&clock); + + let stack = ( + Fallback::layer("test_fallback", &context) + .should_fallback(|output: &&str| output.is_empty()) + .fallback(|_output, _args| "fallback_value"), + Intercept::<&str, &str, _>::layer() + .on_input(|input: &&str| { + assert!(!input.is_empty()); + }) + .on_output(|output: &&str| { + assert!(!output.is_empty()); + }), + Retry::layer("test_retry", &context) + .clone_input() + .recovery_with(|_output: &&str, _| RecoveryInfo::never()), + Hedging::layer("test_hedging", &context) + .clone_input() + .recovery_with(|_output: &&str, _| RecoveryInfo::never()), + Breaker::layer("test_breaker", &context) + .recovery_with(|_output: &&str, _| RecoveryInfo::never()) + .rejected_input(|_input: &str, _| "circuit is open"), + Timeout::layer("test_timeout", &context) + .timeout_output(|_args| "timed out") + .timeout(Duration::from_secs(5)), + Injection::layer("test_injection", &context) + .rate(0.0) + .output_with(|_input: &str, _args| "injected"), + Latency::layer("test_latency", &context) + .rate(0.0) + .latency(Duration::from_millis(10)), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/seatbelt/tests/retry.rs b/crates/seatbelt/tests/retry.rs index df4e558c0..c0cb29200 100644 --- a/crates/seatbelt/tests/retry.rs +++ b/crates/seatbelt/tests/retry.rs @@ -434,3 +434,21 @@ async fn clone_service_works_independently(#[case] use_tower: bool) { // Each service ran through retry cycle: 3 attempts each = 6 total assert_eq!(call_count.load(Ordering::SeqCst), 6); } + +#[tokio::test] +async fn str_references() { + let clock = Clock::new_frozen(); + let context: ResilienceContext<&str, &str> = ResilienceContext::new(&clock); + + let stack = ( + Retry::layer("test_retry", &context) + .clone_input() + .recovery_with(|_output: &&str, _| RecoveryInfo::never()), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} diff --git a/crates/seatbelt/tests/timeout.rs b/crates/seatbelt/tests/timeout.rs index 639ddef1a..a27562d46 100644 --- a/crates/seatbelt/tests/timeout.rs +++ b/crates/seatbelt/tests/timeout.rs @@ -189,3 +189,21 @@ async fn clone_service_works_independently(#[case] use_tower: bool) { assert_eq!(result1, Ok("processed:original".to_string())); assert_eq!(result2, Ok("processed:cloned".to_string())); } + +#[tokio::test] +async fn str_references() { + let clock = Clock::new_frozen(); + let context: ResilienceContext<&str, &str> = ResilienceContext::new(&clock); + + let stack = ( + Timeout::layer("test_timeout", &context) + .timeout_output(|_args| "timed out") + .timeout(Duration::from_secs(5)), + Execute::new(|input: &str| async move { input }), + ); + + let service = stack.into_service(); + let output = service.execute("hello").await; + + assert_eq!(output, "hello"); +} From 75872adc4af1295ef7a776167a43425789c0ebb5 Mon Sep 17 00:00:00 2001 From: martintomka Date: Tue, 14 Apr 2026 19:49:03 +0200 Subject: [PATCH 5/8] Update references.rs --- crates/seatbelt/tests/references.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/seatbelt/tests/references.rs b/crates/seatbelt/tests/references.rs index 3967373a2..850be2c5b 100644 --- a/crates/seatbelt/tests/references.rs +++ b/crates/seatbelt/tests/references.rs @@ -67,8 +67,9 @@ async fn all_middleware_stacked_with_str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(&input).await; assert_eq!(output, "hello"); } From a0f67705f739c85a301e3587993270c6644aa521 Mon Sep 17 00:00:00 2001 From: martintmk <103487740+martintmk@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:00:38 +0200 Subject: [PATCH 6/8] Update crates/seatbelt/tests/references.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/seatbelt/tests/references.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/seatbelt/tests/references.rs b/crates/seatbelt/tests/references.rs index 850be2c5b..0967d8b49 100644 --- a/crates/seatbelt/tests/references.rs +++ b/crates/seatbelt/tests/references.rs @@ -15,7 +15,7 @@ #![cfg(not(miri))] //! Integration test verifying that all resilience middleware can be stacked -//! together with non-static reference input and output types (`&str`). +//! together with non-`'static` reference input and output types (`&str`). use std::time::Duration; From 14bc097b88977e98bff3c6df08d5b701ef87b86d Mon Sep 17 00:00:00 2001 From: martintomka Date: Tue, 14 Apr 2026 21:05:48 +0200 Subject: [PATCH 7/8] cleanup --- crates/layered/tests/execute.rs | 3 ++- crates/layered/tests/intercept.rs | 3 ++- crates/seatbelt/tests/breaker.rs | 3 ++- crates/seatbelt/tests/chaos_injection.rs | 3 ++- crates/seatbelt/tests/chaos_latency.rs | 3 ++- crates/seatbelt/tests/fallback.rs | 3 ++- crates/seatbelt/tests/hedging.rs | 3 ++- crates/seatbelt/tests/retry.rs | 3 ++- crates/seatbelt/tests/timeout.rs | 3 ++- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/layered/tests/execute.rs b/crates/layered/tests/execute.rs index b415d8e55..650963060 100644 --- a/crates/layered/tests/execute.rs +++ b/crates/layered/tests/execute.rs @@ -12,7 +12,8 @@ use layered::{Execute, Service}; async fn str_references() { let service = Execute::new(|input: &str| async move { input }); - let output = service.execute("hello").await; + let input = "hello".to_string(); + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } diff --git a/crates/layered/tests/intercept.rs b/crates/layered/tests/intercept.rs index 9cedfe404..8eba155d4 100644 --- a/crates/layered/tests/intercept.rs +++ b/crates/layered/tests/intercept.rs @@ -22,8 +22,9 @@ async fn str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } diff --git a/crates/seatbelt/tests/breaker.rs b/crates/seatbelt/tests/breaker.rs index 8786d6753..4a54273bf 100644 --- a/crates/seatbelt/tests/breaker.rs +++ b/crates/seatbelt/tests/breaker.rs @@ -256,8 +256,9 @@ async fn str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } diff --git a/crates/seatbelt/tests/chaos_injection.rs b/crates/seatbelt/tests/chaos_injection.rs index ac39c64c0..72575a029 100644 --- a/crates/seatbelt/tests/chaos_injection.rs +++ b/crates/seatbelt/tests/chaos_injection.rs @@ -344,8 +344,9 @@ async fn str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } diff --git a/crates/seatbelt/tests/chaos_latency.rs b/crates/seatbelt/tests/chaos_latency.rs index 96d355928..163c94817 100644 --- a/crates/seatbelt/tests/chaos_latency.rs +++ b/crates/seatbelt/tests/chaos_latency.rs @@ -375,8 +375,9 @@ async fn str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } diff --git a/crates/seatbelt/tests/fallback.rs b/crates/seatbelt/tests/fallback.rs index 515b2fd9a..543ccdc4a 100644 --- a/crates/seatbelt/tests/fallback.rs +++ b/crates/seatbelt/tests/fallback.rs @@ -233,8 +233,9 @@ async fn str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } diff --git a/crates/seatbelt/tests/hedging.rs b/crates/seatbelt/tests/hedging.rs index 32209a84a..6bf07b203 100644 --- a/crates/seatbelt/tests/hedging.rs +++ b/crates/seatbelt/tests/hedging.rs @@ -700,8 +700,9 @@ async fn str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } diff --git a/crates/seatbelt/tests/retry.rs b/crates/seatbelt/tests/retry.rs index c0cb29200..b20d69f14 100644 --- a/crates/seatbelt/tests/retry.rs +++ b/crates/seatbelt/tests/retry.rs @@ -447,8 +447,9 @@ async fn str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } diff --git a/crates/seatbelt/tests/timeout.rs b/crates/seatbelt/tests/timeout.rs index a27562d46..e121dbcb3 100644 --- a/crates/seatbelt/tests/timeout.rs +++ b/crates/seatbelt/tests/timeout.rs @@ -202,8 +202,9 @@ async fn str_references() { Execute::new(|input: &str| async move { input }), ); + let input = "hello".to_string(); let service = stack.into_service(); - let output = service.execute("hello").await; + let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); } From a5a97cab6908d455ad56f8205b8adfbf2a8acc98 Mon Sep 17 00:00:00 2001 From: martintomka Date: Wed, 15 Apr 2026 09:56:01 +0200 Subject: [PATCH 8/8] cleanup --- crates/layered/src/intercept.rs | 4 ++-- crates/layered/tests/intercept.rs | 4 ++-- crates/seatbelt/src/utils/mod.rs | 2 +- crates/seatbelt/tests/breaker.rs | 2 +- crates/seatbelt/tests/chaos_injection.rs | 2 +- crates/seatbelt/tests/chaos_latency.rs | 2 +- crates/seatbelt/tests/fallback.rs | 2 +- crates/seatbelt/tests/hedging.rs | 2 +- crates/seatbelt/tests/references.rs | 2 +- crates/seatbelt/tests/retry.rs | 2 +- crates/seatbelt/tests/timeout.rs | 4 ++-- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/layered/src/intercept.rs b/crates/layered/src/intercept.rs index ea019d949..fefc9c2fa 100644 --- a/crates/layered/src/intercept.rs +++ b/crates/layered/src/intercept.rs @@ -374,7 +374,7 @@ impl crate::Layer for InterceptLayer { } } -struct OnInput(Arc); +struct OnInput(Arc Fn(&'a In) + Send + Sync>); impl Clone for OnInput { fn clone(&self) -> Self { @@ -382,7 +382,7 @@ impl Clone for OnInput { } } -struct OnOutput(Arc); +struct OnOutput(Arc Fn(&'a Out) + Send + Sync>); impl Clone for OnOutput { fn clone(&self) -> Self { diff --git a/crates/layered/tests/intercept.rs b/crates/layered/tests/intercept.rs index 8eba155d4..7ee32ebf6 100644 --- a/crates/layered/tests/intercept.rs +++ b/crates/layered/tests/intercept.rs @@ -3,12 +3,12 @@ #![allow(missing_docs, reason = "This is a test module")] #![cfg(feature = "intercept")] -#![cfg(not(miri))] //! Integration tests for [`Intercept`] middleware. use layered::{Execute, Intercept, Service, Stack}; +#[cfg_attr(miri, ignore)] #[tokio::test] async fn str_references() { let stack = ( @@ -21,9 +21,9 @@ async fn str_references() { }), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); diff --git a/crates/seatbelt/src/utils/mod.rs b/crates/seatbelt/src/utils/mod.rs index 8ccbad10a..4d41990cc 100644 --- a/crates/seatbelt/src/utils/mod.rs +++ b/crates/seatbelt/src/utils/mod.rs @@ -28,7 +28,7 @@ pub(crate) enum EnableIf { /// The middleware is always disabled. Disabled, /// The middleware is conditionally enabled based on a predicate. - Custom(Arc bool + Send + Sync>), + Custom(Arc Fn(&'a In) -> bool + Send + Sync>), } impl EnableIf { diff --git a/crates/seatbelt/tests/breaker.rs b/crates/seatbelt/tests/breaker.rs index 4a54273bf..e0fc12757 100644 --- a/crates/seatbelt/tests/breaker.rs +++ b/crates/seatbelt/tests/breaker.rs @@ -255,9 +255,9 @@ async fn str_references() { .rejected_input(|_input: &str, _| "circuit is open"), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); diff --git a/crates/seatbelt/tests/chaos_injection.rs b/crates/seatbelt/tests/chaos_injection.rs index 72575a029..26c0738f8 100644 --- a/crates/seatbelt/tests/chaos_injection.rs +++ b/crates/seatbelt/tests/chaos_injection.rs @@ -343,9 +343,9 @@ async fn str_references() { .output_with(|_input: &str, _args| "injected"), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); diff --git a/crates/seatbelt/tests/chaos_latency.rs b/crates/seatbelt/tests/chaos_latency.rs index 163c94817..7fb0f233a 100644 --- a/crates/seatbelt/tests/chaos_latency.rs +++ b/crates/seatbelt/tests/chaos_latency.rs @@ -374,9 +374,9 @@ async fn str_references() { .latency(Duration::from_millis(10)), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); diff --git a/crates/seatbelt/tests/fallback.rs b/crates/seatbelt/tests/fallback.rs index 543ccdc4a..18e4c0470 100644 --- a/crates/seatbelt/tests/fallback.rs +++ b/crates/seatbelt/tests/fallback.rs @@ -232,9 +232,9 @@ async fn str_references() { .fallback(|_output, _args| "fallback_value"), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); diff --git a/crates/seatbelt/tests/hedging.rs b/crates/seatbelt/tests/hedging.rs index 6bf07b203..06cfe57b8 100644 --- a/crates/seatbelt/tests/hedging.rs +++ b/crates/seatbelt/tests/hedging.rs @@ -699,9 +699,9 @@ async fn str_references() { .recovery_with(|_output: &&str, _| RecoveryInfo::never()), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); diff --git a/crates/seatbelt/tests/references.rs b/crates/seatbelt/tests/references.rs index 0967d8b49..1d8bc4101 100644 --- a/crates/seatbelt/tests/references.rs +++ b/crates/seatbelt/tests/references.rs @@ -66,9 +66,9 @@ async fn all_middleware_stacked_with_str_references() { .latency(Duration::from_millis(10)), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(&input).await; assert_eq!(output, "hello"); diff --git a/crates/seatbelt/tests/retry.rs b/crates/seatbelt/tests/retry.rs index b20d69f14..6815462f0 100644 --- a/crates/seatbelt/tests/retry.rs +++ b/crates/seatbelt/tests/retry.rs @@ -446,9 +446,9 @@ async fn str_references() { .recovery_with(|_output: &&str, _| RecoveryInfo::never()), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(input.as_str()).await; assert_eq!(output, "hello"); diff --git a/crates/seatbelt/tests/timeout.rs b/crates/seatbelt/tests/timeout.rs index e121dbcb3..e3428208a 100644 --- a/crates/seatbelt/tests/timeout.rs +++ b/crates/seatbelt/tests/timeout.rs @@ -4,7 +4,6 @@ #![allow(dead_code, reason = "This is a test module")] #![allow(missing_docs, reason = "This is a test module")] #![cfg(feature = "timeout")] -#![cfg(not(miri))] //! Integration tests for timeout middleware using only public API. @@ -198,12 +197,13 @@ async fn str_references() { let stack = ( Timeout::layer("test_timeout", &context) .timeout_output(|_args| "timed out") + .enable_if(|input| true) .timeout(Duration::from_secs(5)), Execute::new(|input: &str| async move { input }), ); + let service = stack.into_service(); let input = "hello".to_string(); - let service = stack.into_service(); let output = service.execute(input.as_str()).await; assert_eq!(output, "hello");