From 9a6a1af4ab35d1b7e670528322fb58780a6aa128 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:42:56 +0000 Subject: [PATCH 1/3] Initial plan From 5a6d2b6827d3ccbe3fbfc242d5e7cdf698b99dd2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:52:22 +0000 Subject: [PATCH 2/3] Fix cargo doc warnings and improve documentation - Fix unresolved link warnings by using conditional doc attributes - Improve Key Concepts section with clearer explanations - Enhance Layers and Middleware example with better comments - Move public API tests to tests/ directory - Keep internal tests in source modules Co-authored-by: martintmk <103487740+martintmk@users.noreply.github.com> --- crates/layered/README.md | 34 ++++-- crates/layered/src/dynamic.rs | 32 +---- crates/layered/src/intercept.rs | 75 +----------- crates/layered/src/layer/tuples.rs | 151 +---------------------- crates/layered/src/lib.rs | 26 ++-- crates/layered/src/service.rs | 37 +----- crates/layered/src/tower.rs | 68 ++--------- crates/layered/tests/dynamic_service.rs | 32 +++++ crates/layered/tests/intercept.rs | 84 +++++++++++++ crates/layered/tests/service.rs | 39 ++++++ crates/layered/tests/stack.rs | 154 ++++++++++++++++++++++++ crates/layered/tests/tower.rs | 66 ++++++++++ 12 files changed, 429 insertions(+), 369 deletions(-) create mode 100644 crates/layered/tests/dynamic_service.rs create mode 100644 crates/layered/tests/intercept.rs create mode 100644 crates/layered/tests/service.rs create mode 100644 crates/layered/tests/stack.rs create mode 100644 crates/layered/tests/tower.rs diff --git a/crates/layered/README.md b/crates/layered/README.md index 710399a8..60881aec 100644 --- a/crates/layered/README.md +++ b/crates/layered/README.md @@ -59,19 +59,23 @@ assert_eq!(greeter.execute("World".into()).await, "Hello, World!"); ### Key Concepts -* **Service**: An async function `In → Out` that processes inputs. -* **Middleware**: A service that wraps another service to add behavior (logging, timeouts, retries). -* **Layer**: A factory that wraps any service with middleware. Stack layers using tuples - like `(layer1, layer2, service)`. +* **Service**: A type implementing the [`Service`][__link4] trait that transforms inputs into outputs + asynchronously. Think of it as `async fn(&self, In) -> Out`. +* **Middleware**: A service that wraps another service to add cross-cutting behavior such as + logging, timeouts, or retries. Middleware receives requests before the inner service and can + process responses after. +* **Layer**: A type implementing the [`Layer`][__link5] trait that constructs middleware around a + service. Layers are composable and can be stacked using tuples like `(layer1, layer2, service)`. ### Layers and Middleware -A [`Layer`][__link4] wraps a service with additional behavior: +A [`Layer`][__link6] wraps a service with additional behavior. In this example, we create a logging +middleware that prints inputs before passing them to the inner service: ```rust use layered::{Execute, Layer, Service, Stack}; -// A simple logging layer +// A layer that creates logging middleware struct LogLayer; impl Layer for LogLayer { @@ -82,6 +86,7 @@ impl Layer for LogLayer { } } +// The middleware service that wraps another service struct LogService(S); impl Service for LogService @@ -107,13 +112,13 @@ let result = service.execute(21).await; ### Thread Safety -All services must implement [`Send`][__link5] and [`Sync`][__link6], and returned futures must be [`Send`][__link7]. +All services must implement [`Send`][__link11] and [`Sync`][__link12], and returned futures must be [`Send`][__link13]. This ensures compatibility with multi-threaded async runtimes like Tokio. ### Features -* **`intercept`**: Enables [`Intercept`][__link8] middleware -* **`dynamic-service`**: Enables [`DynamicService`][__link9] for type erasure +* **`intercept`**: Enables [`Intercept`][__link14] middleware +* **`dynamic-service`**: Enables [`DynamicService`][__link15] for type erasure * **`tower-service`**: Enables Tower interoperability via the [`tower`][__link10] module @@ -126,11 +131,16 @@ This crate was developed as part of The Oxidizer Project. Br [__link0]: https://docs.rs/layered/0.1.0/layered/?search=Service [__link1]: https://docs.rs/tower [__link10]: https://docs.rs/layered/0.1.0/layered/tower/index.html + [__link11]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html + [__link12]: https://doc.rust-lang.org/stable/std/marker/trait.Sync.html + [__link13]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html + [__link14]: https://docs.rs/layered/0.1.0/layered/?search=Intercept + [__link15]: https://docs.rs/layered/0.1.0/layered/?search=DynamicService [__link2]: https://docs.rs/layered/0.1.0/layered/?search=Service [__link3]: https://docs.rs/layered/0.1.0/layered/?search=Execute - [__link4]: https://docs.rs/layered/0.1.0/layered/?search=Layer - [__link5]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html - [__link6]: https://doc.rust-lang.org/stable/std/marker/trait.Sync.html + [__link4]: https://docs.rs/layered/0.1.0/layered/?search=Service + [__link5]: https://docs.rs/layered/0.1.0/layered/?search=Layer + [__link6]: https://docs.rs/layered/0.1.0/layered/?search=Layer [__link7]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html [__link8]: https://docs.rs/layered/0.1.0/layered/?search=Intercept [__link9]: https://docs.rs/layered/0.1.0/layered/?search=DynamicService diff --git a/crates/layered/src/dynamic.rs b/crates/layered/src/dynamic.rs index cf88150b..450f37d6 100644 --- a/crates/layered/src/dynamic.rs +++ b/crates/layered/src/dynamic.rs @@ -85,34 +85,6 @@ impl Clone for DynamicService { #[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { - use std::sync::Mutex; - - use futures::executor::block_on; - use static_assertions::assert_impl_all; - - use super::*; - use crate::Execute; - - #[test] - fn assert_types() { - assert_impl_all!(DynamicService<(), ()>: Send, Sync, Clone, Debug); - - // If non-clonable types are used, ensure the DynamicService is still cloneable - assert_impl_all!(DynamicService, Mutex<()>>: Send, Sync, Clone, Debug); - } - - #[test] - fn into_dynamic() { - let dynamic_service: DynamicService = Execute::new(|v| async move { v }).into_dynamic(); - - assert_eq!(block_on(dynamic_service.execute(42)), 42); - } - - #[test] - fn clone_and_debug() { - let svc: DynamicService = Execute::new(|v| async move { v }).into_dynamic(); - let cloned = svc.clone(); - assert_eq!(block_on(cloned.execute(1)), 1); - assert_eq!(format!("{svc:?}"), "DynamicService"); - } + // Integration tests have been moved to tests/dynamic_service.rs + // No internal tests needed for this module } diff --git a/crates/layered/src/intercept.rs b/crates/layered/src/intercept.rs index 6f33a0d6..778305f2 100644 --- a/crates/layered/src/intercept.rs +++ b/crates/layered/src/intercept.rs @@ -426,69 +426,8 @@ mod tests { use super::*; use crate::{Execute, Layer, Stack}; - #[test] - pub fn ensure_types() { - static_assertions::assert_impl_all!(Intercept::: Debug, Clone, Send, Sync); - static_assertions::assert_impl_all!(InterceptLayer::: Debug, Clone, Send, Sync); - } - - #[test] - #[expect(clippy::similar_names, reason = "Test")] - fn input_modification_order() { - let called = Arc::new(AtomicU16::default()); - let called_clone = Arc::clone(&called); - - let called2 = Arc::new(AtomicU16::default()); - let called2_clone = Arc::clone(&called2); - - let stack = ( - Intercept::layer() - .modify_input(|input: String| format!("{input}1")) - .modify_input(|input: String| format!("{input}2")) - .on_input(move |_input| { - called.fetch_add(1, Ordering::Relaxed); - }) - .on_input(move |_input| { - called2.fetch_add(1, Ordering::Relaxed); - }), - Execute::new(|input: String| async move { input }), - ); - - let service = stack.build(); - let response = block_on(service.execute("test".to_string())); - assert_eq!(called_clone.load(Ordering::Relaxed), 1); - assert_eq!(called2_clone.load(Ordering::Relaxed), 1); - assert_eq!(response, "test12"); - } - - #[test] - #[expect(clippy::similar_names, reason = "Test")] - fn out_modification_order() { - let called = Arc::new(AtomicU16::default()); - let called_clone = Arc::clone(&called); - - let called2 = Arc::new(AtomicU16::default()); - let called2_clone = Arc::clone(&called2); - - let stack = ( - Intercept::layer() - .modify_output(|output: String| format!("{output}1")) - .modify_output(|output: String| format!("{output}2")) - .on_output(move |_output| { - called.fetch_add(1, Ordering::Relaxed); - }) - .on_output(move |_output| { - called2.fetch_add(1, Ordering::Relaxed); - }), - Execute::new(|input: String| async move { input }), - ); - - let service = stack.build(); - let response = block_on(service.execute("test".to_string())); - assert_eq!(called_clone.load(Ordering::Relaxed), 1); - assert_eq!(called2_clone.load(Ordering::Relaxed), 1); - assert_eq!(response, "test12"); - } + // Public API tests have been moved to tests/intercept.rs + // Internal tests that use tower_service internals remain here #[test] #[expect(clippy::similar_names, reason = "Test")] @@ -610,16 +549,6 @@ mod tests { } } - #[test] - fn debug_impls() { - let layer = Intercept::::layer() - .on_input(|_| {}) - .on_output(|_| {}) - .modify_input(|s| s) - .modify_output(|s| s); - assert!(format!("{layer:?}").contains("InterceptLayer")); - } - #[test] fn short_circuit_layered() { let stack = ( diff --git a/crates/layered/src/layer/tuples.rs b/crates/layered/src/layer/tuples.rs index 27b474dd..fec7c9d8 100644 --- a/crates/layered/src/layer/tuples.rs +++ b/crates/layered/src/layer/tuples.rs @@ -338,153 +338,6 @@ where #[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { - use super::*; - use tower_layer::Identity; - - type I = Identity; - - #[test] - #[expect(clippy::too_many_lines, reason = "no need to have 16 different unit tests")] - fn stack_tuples() { - let _: () = (I::new(), ()).build(); - let _: () = (I::new(), I::new(), ()).build(); - let _: () = (I::new(), I::new(), I::new(), ()).build(); - let _: () = (I::new(), I::new(), I::new(), I::new(), ()).build(); - let _: () = (I::new(), I::new(), I::new(), I::new(), I::new(), ()).build(); - let _: () = (I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), ()).build(); - let _: () = (I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), ()).build(); - let _: () = (I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), ()).build(); - let _: () = ( - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - (), - ) - .build(); - let _: () = ( - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - (), - ) - .build(); - let _: () = ( - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - (), - ) - .build(); - let _: () = ( - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - (), - ) - .build(); - let _: () = ( - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - (), - ) - .build(); - let _: () = ( - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - (), - ) - .build(); - let _: () = ( - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - (), - ) - .build(); - let _: () = ( - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - I::new(), - (), - ) - .build(); - } + // Integration tests have been moved to tests/stack.rs + // No internal tests needed for this module } diff --git a/crates/layered/src/lib.rs b/crates/layered/src/lib.rs index e5a97a28..eea9d327 100644 --- a/crates/layered/src/lib.rs +++ b/crates/layered/src/lib.rs @@ -54,19 +54,23 @@ //! //! ## Key Concepts //! -//! - **Service**: An async function `In → Out` that processes inputs. -//! - **Middleware**: A service that wraps another service to add behavior (logging, timeouts, retries). -//! - **Layer**: A factory that wraps any service with middleware. Stack layers using tuples -//! like `(layer1, layer2, service)`. +//! - **Service**: A type implementing the [`Service`] trait that transforms inputs into outputs +//! asynchronously. Think of it as `async fn(&self, In) -> Out`. +//! - **Middleware**: A service that wraps another service to add cross-cutting behavior such as +//! logging, timeouts, or retries. Middleware receives requests before the inner service and can +//! process responses after. +//! - **Layer**: A type implementing the [`Layer`] trait that constructs middleware around a +//! service. Layers are composable and can be stacked using tuples like `(layer1, layer2, service)`. //! //! ## Layers and Middleware //! -//! A [`Layer`] wraps a service with additional behavior: +//! A [`Layer`] wraps a service with additional behavior. In this example, we create a logging +//! middleware that prints inputs before passing them to the inner service: //! //! ``` //! use layered::{Execute, Layer, Service, Stack}; //! -//! // A simple logging layer +//! // A layer that creates logging middleware //! struct LogLayer; //! //! impl Layer for LogLayer { @@ -77,6 +81,7 @@ //! } //! } //! +//! // The middleware service that wraps another service //! struct LogService(S); //! //! impl Service for LogService @@ -109,9 +114,12 @@ //! //! ## Features //! -//! - **`intercept`**: Enables [`Intercept`] middleware -//! - **`dynamic-service`**: Enables [`DynamicService`] for type erasure -//! - **`tower-service`**: Enables Tower interoperability via the [`tower`] module +#![cfg_attr(feature = "intercept", doc = "//! - **`intercept`**: Enables [`Intercept`] middleware")] +#![cfg_attr(not(feature = "intercept"), doc = "//! - **`intercept`**: Enables `Intercept` middleware")] +#![cfg_attr(feature = "dynamic-service", doc = "//! - **`dynamic-service`**: Enables [`DynamicService`] for type erasure")] +#![cfg_attr(not(feature = "dynamic-service"), doc = "//! - **`dynamic-service`**: Enables `DynamicService` for type erasure")] +#![cfg_attr(feature = "tower-service", doc = "//! - **`tower-service`**: Enables Tower interoperability via the [`tower`] module")] +#![cfg_attr(not(feature = "tower-service"), doc = "//! - **`tower-service`**: Enables Tower interoperability via the `tower` module")] mod service; pub use service::Service; diff --git a/crates/layered/src/service.rs b/crates/layered/src/service.rs index abb93bcc..1ab47f19 100644 --- a/crates/layered/src/service.rs +++ b/crates/layered/src/service.rs @@ -61,39 +61,6 @@ where #[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { - use futures::executor::block_on; - - use super::*; - - // A simple service that echoes the input. - struct EchoService; - - impl Service for EchoService { - type Out = String; - - async fn execute(&self, input: String) -> Self::Out { - input - } - } - - #[test] - fn test_echo_service() { - let service = EchoService; - let output = block_on(service.execute("Hello, World!".to_string())); - assert_eq!(output, "Hello, World!"); - } - - #[test] - fn test_boxed_service() { - let service: Box = Box::new(EchoService); - let output = block_on(service.execute("Hello, Boxed World!".to_string())); - assert_eq!(output, "Hello, Boxed World!"); - } - - #[test] - fn test_arc_service() { - let service: std::sync::Arc = std::sync::Arc::new(EchoService); - let output = block_on(service.execute("Hello, Arc World!".to_string())); - assert_eq!(output, "Hello, Arc World!"); - } + // Integration tests have been moved to tests/service.rs + // No internal tests needed for this module } diff --git a/crates/layered/src/tower.rs b/crates/layered/src/tower.rs index f0678b9b..0f5268de 100644 --- a/crates/layered/src/tower.rs +++ b/crates/layered/src/tower.rs @@ -152,21 +152,14 @@ where #[cfg(test)] mod tests { use futures::executor::block_on; - use tower::service_fn; use tower_service::Service as TowerService; use super::*; use crate::testing::MockService; + use std::task::Poll; - #[test] - fn adapt_tower_ok() { - let service = service_fn(|req: u32| async move { Ok::<_, ()>(req + 1) }); - let service = Adapter(service); - - let result = block_on(service.execute(0)); - - assert_eq!(result, Ok(1)); - } + // Public API tests have been moved to tests/tower.rs + // Internal tests that use MockService remain here #[test] fn adapt_tower_ensure_poll_error_respected() { @@ -178,52 +171,15 @@ mod tests { assert_eq!(result, Err("error".to_string())); } - #[test] - fn adapt_oxidizer_ok() { - let mock_service = MockService::new(Poll::Ready(Ok(())), Ok("success".to_string())); - let mut service = Adapter(mock_service); - - let result = block_on(async move { service.call("request".to_string()).await }); - - assert_eq!(result, Ok("success".to_string())); - } - - #[test] - fn poll_ready_always_returns_ready_ok() { - let mock_service = MockService::new(Poll::Ready(Ok(())), Ok("success".to_string())); - let mut adapter = Adapter(mock_service); - - let waker = futures::task::noop_waker(); - let mut cx = Context::from_waker(&waker); - - let result = adapter.poll_ready(&mut cx); - assert_eq!(result, Poll::Ready(Ok(()))); - } - - #[test] - fn poll_ready_consistent_behavior() { - let mock_service = MockService::new(Poll::Ready(Ok(())), Ok("success".to_string())); - let mut adapter = Adapter(mock_service); - - let waker = futures::task::noop_waker(); - let mut cx = Context::from_waker(&waker); - - // Multiple calls should return the same result - for _ in 0..3 { - let result = adapter.poll_ready(&mut cx); - assert_eq!(result, Poll::Ready(Ok(()))); - } - } - #[test] fn poll_ready_with_mock_service() { let mock_service = MockService::new(Poll::Ready(Ok(())), Ok("success".to_string())); let mut mock_adapter = Adapter(mock_service); let waker = futures::task::noop_waker(); - let mut cx = Context::from_waker(&waker); + let mut cx = std::task::Context::from_waker(&waker); - assert_eq!(mock_adapter.poll_ready(&mut cx), Poll::Ready(Ok(()))); + assert_eq!(TowerService::poll_ready(&mut mock_adapter, &mut cx), Poll::Ready(Ok(()))); } #[test] @@ -234,9 +190,9 @@ mod tests { let mut adapter = Adapter(mock_service); let waker = futures::task::noop_waker(); - let mut cx = Context::from_waker(&waker); + let mut cx = std::task::Context::from_waker(&waker); - let result1 = adapter.poll_ready(&mut cx); + let result1 = TowerService::poll_ready(&mut adapter, &mut cx); let result2 = Poll::from(Ok::<(), String>(())); // Both should be Poll::Ready(Ok(())) @@ -254,14 +210,4 @@ mod tests { assert_eq!(result, Err("service unavailable".to_string())); } - - #[test] - fn tower_layer_adapter() { - use crate::{Execute, Stack}; - use tower_layer::Identity; - - let stack = (tower_layer(Identity::new()), Execute::new(|x: i32| async move { Ok::<_, ()>(x) })); - let svc = stack.build(); - assert_eq!(block_on(svc.execute(42)), Ok(42)); - } } diff --git a/crates/layered/tests/dynamic_service.rs b/crates/layered/tests/dynamic_service.rs new file mode 100644 index 00000000..7d7c56b9 --- /dev/null +++ b/crates/layered/tests/dynamic_service.rs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Integration tests for DynamicService. + +use futures::executor::block_on; +use layered::{DynamicService, DynamicServiceExt, Execute, Service}; +use static_assertions::assert_impl_all; +use std::sync::Mutex; + +#[test] +fn assert_types() { + assert_impl_all!(DynamicService<(), ()>: Send, Sync, Clone, std::fmt::Debug); + + // If non-clonable types are used, ensure the DynamicService is still cloneable + assert_impl_all!(DynamicService, Mutex<()>>: Send, Sync, Clone, std::fmt::Debug); +} + +#[test] +fn into_dynamic() { + let dynamic_service: DynamicService = Execute::new(|v| async move { v }).into_dynamic(); + + assert_eq!(block_on(dynamic_service.execute(42)), 42); +} + +#[test] +fn clone_and_debug() { + let svc: DynamicService = Execute::new(|v| async move { v }).into_dynamic(); + let cloned = svc.clone(); + assert_eq!(block_on(cloned.execute(1)), 1); + assert_eq!(format!("{svc:?}"), "DynamicService"); +} diff --git a/crates/layered/tests/intercept.rs b/crates/layered/tests/intercept.rs new file mode 100644 index 00000000..05a24be4 --- /dev/null +++ b/crates/layered/tests/intercept.rs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Integration tests for Intercept middleware. + +use futures::executor::block_on; +use layered::{Execute, Intercept, Layer, Service, Stack}; +use static_assertions::assert_impl_all; +use std::sync::atomic::{AtomicU16, Ordering}; +use std::sync::Arc; + +#[test] +pub fn ensure_types() { + assert_impl_all!(Intercept::: std::fmt::Debug, Clone, Send, Sync); + assert_impl_all!(layered::InterceptLayer::: std::fmt::Debug, Clone, Send, Sync); +} + +#[test] +#[expect(clippy::similar_names, reason = "Test")] +fn input_modification_order() { + let called = Arc::new(AtomicU16::default()); + let called_clone = Arc::clone(&called); + + let called2 = Arc::new(AtomicU16::default()); + let called2_clone = Arc::clone(&called2); + + let stack = ( + Intercept::layer() + .modify_input(|input: String| format!("{input}1")) + .modify_input(|input: String| format!("{input}2")) + .on_input(move |_input| { + called.fetch_add(1, Ordering::Relaxed); + }) + .on_input(move |_input| { + called2.fetch_add(1, Ordering::Relaxed); + }), + Execute::new(|input: String| async move { input }), + ); + + let service = stack.build(); + let response = block_on(service.execute("test".to_string())); + assert_eq!(called_clone.load(Ordering::Relaxed), 1); + assert_eq!(called2_clone.load(Ordering::Relaxed), 1); + assert_eq!(response, "test12"); +} + +#[test] +#[expect(clippy::similar_names, reason = "Test")] +fn out_modification_order() { + let called = Arc::new(AtomicU16::default()); + let called_clone = Arc::clone(&called); + + let called2 = Arc::new(AtomicU16::default()); + let called2_clone = Arc::clone(&called2); + + let stack = ( + Intercept::layer() + .modify_output(|output: String| format!("{output}1")) + .modify_output(|output: String| format!("{output}2")) + .on_output(move |_output| { + called.fetch_add(1, Ordering::Relaxed); + }) + .on_output(move |_output| { + called2.fetch_add(1, Ordering::Relaxed); + }), + Execute::new(|input: String| async move { input }), + ); + + let service = stack.build(); + let response = block_on(service.execute("test".to_string())); + assert_eq!(called_clone.load(Ordering::Relaxed), 1); + assert_eq!(called2_clone.load(Ordering::Relaxed), 1); + assert_eq!(response, "test12"); +} + +#[test] +fn debug_impls() { + let layer = Intercept::::layer() + .on_input(|_| {}) + .on_output(|_| {}) + .modify_input(|s| s) + .modify_output(|s| s); + assert!(format!("{layer:?}").contains("InterceptLayer")); +} diff --git a/crates/layered/tests/service.rs b/crates/layered/tests/service.rs new file mode 100644 index 00000000..8752e4f5 --- /dev/null +++ b/crates/layered/tests/service.rs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Integration tests for Service trait implementations. + +use futures::executor::block_on; +use layered::Service; + +// A simple service that echoes the input. +struct EchoService; + +impl Service for EchoService { + type Out = String; + + async fn execute(&self, input: String) -> Self::Out { + input + } +} + +#[test] +fn test_echo_service() { + let service = EchoService; + let output = block_on(service.execute("Hello, World!".to_string())); + assert_eq!(output, "Hello, World!"); +} + +#[test] +fn test_boxed_service() { + let service: Box = Box::new(EchoService); + let output = block_on(service.execute("Hello, Boxed World!".to_string())); + assert_eq!(output, "Hello, Boxed World!"); +} + +#[test] +fn test_arc_service() { + let service: std::sync::Arc = std::sync::Arc::new(EchoService); + let output = block_on(service.execute("Hello, Arc World!".to_string())); + assert_eq!(output, "Hello, Arc World!"); +} diff --git a/crates/layered/tests/stack.rs b/crates/layered/tests/stack.rs new file mode 100644 index 00000000..50453f62 --- /dev/null +++ b/crates/layered/tests/stack.rs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Integration tests for layer stacking. + +use layered::Stack; +use tower_layer::Identity; + +type I = Identity; + +#[test] +#[expect(clippy::too_many_lines, reason = "no need to have 16 different unit tests")] +fn stack_tuples() { + let _: () = (I::new(), ()).build(); + let _: () = (I::new(), I::new(), ()).build(); + let _: () = (I::new(), I::new(), I::new(), ()).build(); + let _: () = (I::new(), I::new(), I::new(), I::new(), ()).build(); + let _: () = (I::new(), I::new(), I::new(), I::new(), I::new(), ()).build(); + let _: () = (I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), ()).build(); + let _: () = (I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), ()).build(); + let _: () = (I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), I::new(), ()).build(); + let _: () = ( + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + (), + ) + .build(); + let _: () = ( + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + (), + ) + .build(); + let _: () = ( + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + (), + ) + .build(); + let _: () = ( + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + (), + ) + .build(); + let _: () = ( + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + (), + ) + .build(); + let _: () = ( + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + (), + ) + .build(); + let _: () = ( + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + (), + ) + .build(); + let _: () = ( + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + I::new(), + (), + ) + .build(); +} diff --git a/crates/layered/tests/tower.rs b/crates/layered/tests/tower.rs new file mode 100644 index 00000000..403e5523 --- /dev/null +++ b/crates/layered/tests/tower.rs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Integration tests for Tower interoperability. + +use futures::executor::block_on; +use layered::tower::{tower_layer, Adapter}; +use layered::{Execute, Service, Stack}; +use std::task::Poll; +use tower::service_fn; +use tower_layer::Identity; +use tower_service::Service as TowerService; + +#[test] +fn adapt_tower_ok() { + let service = service_fn(|req: u32| async move { Ok::<_, ()>(req + 1) }); + let service = Adapter(service); + + let result = block_on(service.execute(0)); + + assert_eq!(result, Ok(1)); +} + +#[test] +fn adapt_oxidizer_ok() { + let service = Execute::new(|req: String| async move { Ok::<_, ()>(format!("Processed: {req}")) }); + let mut adapter = Adapter(service); + + let result = block_on(async move { adapter.call("request".to_string()).await }); + + assert_eq!(result, Ok("Processed: request".to_string())); +} + +#[test] +fn poll_ready_always_returns_ready_ok() { + let service = Execute::new(|req: String| async move { Ok::<_, ()>(format!("Processed: {req}")) }); + let mut adapter = Adapter(service); + + let waker = futures::task::noop_waker(); + let mut cx = std::task::Context::from_waker(&waker); + + let result = adapter.poll_ready(&mut cx); + assert_eq!(result, Poll::Ready(Ok(()))); +} + +#[test] +fn poll_ready_consistent_behavior() { + let service = Execute::new(|req: String| async move { Ok::<_, ()>(format!("Processed: {req}")) }); + let mut adapter = Adapter(service); + + let waker = futures::task::noop_waker(); + let mut cx = std::task::Context::from_waker(&waker); + + // Multiple calls should return the same result + for _ in 0..3 { + let result = adapter.poll_ready(&mut cx); + assert_eq!(result, Poll::Ready(Ok(()))); + } +} + +#[test] +fn tower_layer_adapter() { + let stack = (tower_layer(Identity::new()), Execute::new(|x: i32| async move { Ok::<_, ()>(x) })); + let svc = stack.build(); + assert_eq!(block_on(svc.execute(42)), Ok(42)); +} From 7008fc994dd051257c5b0aa729b2a673befef213 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:54:23 +0000 Subject: [PATCH 3/3] Fix import consistency in tower.rs tests Co-authored-by: martintmk <103487740+martintmk@users.noreply.github.com> --- crates/layered/src/tower.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/layered/src/tower.rs b/crates/layered/src/tower.rs index 0f5268de..6b94e283 100644 --- a/crates/layered/src/tower.rs +++ b/crates/layered/src/tower.rs @@ -177,7 +177,7 @@ mod tests { let mut mock_adapter = Adapter(mock_service); let waker = futures::task::noop_waker(); - let mut cx = std::task::Context::from_waker(&waker); + let mut cx = Context::from_waker(&waker); assert_eq!(TowerService::poll_ready(&mut mock_adapter, &mut cx), Poll::Ready(Ok(()))); } @@ -190,7 +190,7 @@ mod tests { let mut adapter = Adapter(mock_service); let waker = futures::task::noop_waker(); - let mut cx = std::task::Context::from_waker(&waker); + let mut cx = Context::from_waker(&waker); let result1 = TowerService::poll_ready(&mut adapter, &mut cx); let result2 = Poll::from(Ok::<(), String>(()));