From eb072a5249e37aa2072e99782dd2af620ff72fbb Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 20 Jun 2025 14:38:00 +0100 Subject: [PATCH 1/2] Add echo example and route test --- examples/echo.rs | 26 +++++++++++++++++++ tests/routes.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/util.rs | 27 ++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 examples/echo.rs create mode 100644 tests/routes.rs create mode 100644 tests/util.rs diff --git a/examples/echo.rs b/examples/echo.rs new file mode 100644 index 00000000..f30bdaa1 --- /dev/null +++ b/examples/echo.rs @@ -0,0 +1,26 @@ +use std::io; + +use wireframe::{ + app::{Middleware, WireframeApp}, + server::WireframeServer, +}; + +struct Logger; +impl Middleware for Logger {} + +#[tokio::main] +async fn main() -> io::Result<()> { + let factory = || { + WireframeApp::new() + .unwrap() + .wrap(Logger) + .unwrap() + .route(1, Box::new(|_env| Box::pin(async {}))) + .unwrap() + }; + + WireframeServer::new(factory) + .bind("127.0.0.1:7878".parse().unwrap())? + .run() + .await +} diff --git a/tests/routes.rs b/tests/routes.rs new file mode 100644 index 00000000..a6701905 --- /dev/null +++ b/tests/routes.rs @@ -0,0 +1,65 @@ +use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, +}; + +use bytes::BytesMut; +use wireframe::{ + Serializer, + app::WireframeApp, + frame::{FrameProcessor, LengthPrefixedProcessor}, + message::Message, + serializer::BincodeSerializer, +}; + +mod util; +use util::run_app_with_frame; + +#[derive(bincode::Encode, bincode::BorrowDecode, PartialEq, Debug)] +struct TestEnvelope { + id: u32, + msg: Vec, +} + +#[derive(bincode::Encode, bincode::BorrowDecode, PartialEq, Debug)] +struct Echo(u8); + +#[tokio::test] +async fn handler_receives_message_and_echoes_response() { + let called = Arc::new(AtomicUsize::new(0)); + let called_clone = called.clone(); + let app = WireframeApp::new() + .unwrap() + .frame_processor(LengthPrefixedProcessor) + .route( + 1, + Box::new(move |_| { + let called_inner = called_clone.clone(); + Box::pin(async move { + called_inner.fetch_add(1, Ordering::SeqCst); + }) + }), + ) + .unwrap(); + let msg_bytes = Echo(42).to_bytes().unwrap(); + let env = TestEnvelope { + id: 1, + msg: msg_bytes, + }; + let env_bytes = BincodeSerializer.serialize(&env).unwrap(); + let mut framed = BytesMut::new(); + LengthPrefixedProcessor + .encode(&env_bytes, &mut framed) + .unwrap(); + + let out = run_app_with_frame(app, framed.to_vec()).await.unwrap(); + + let mut buf = BytesMut::from(&out[..]); + let frame = LengthPrefixedProcessor.decode(&mut buf).unwrap().unwrap(); + let (resp_env, _) = BincodeSerializer + .deserialize::(&frame) + .unwrap(); + let (echo, _) = Echo::from_bytes(&resp_env.msg).unwrap(); + assert_eq!(echo, Echo(42)); + assert_eq!(called.load(Ordering::SeqCst), 1); +} diff --git a/tests/util.rs b/tests/util.rs new file mode 100644 index 00000000..04761ec7 --- /dev/null +++ b/tests/util.rs @@ -0,0 +1,27 @@ +use tokio::io::{self, AsyncReadExt, AsyncWriteExt, duplex}; +use wireframe::app::WireframeApp; + +/// Feed a single frame into `app` and collect the response bytes. +/// +/// # Errors +/// +/// Propagates I/O errors from the in-memory connection. +/// +/// # Panics +/// +/// Panics if the spawned task running the application panics. +pub async fn run_app_with_frame(app: WireframeApp, frame: Vec) -> io::Result> { + let (mut client, server) = duplex(1024); + let server_task = tokio::spawn(async move { + app.handle_connection(server).await; + }); + + client.write_all(&frame).await?; + client.shutdown().await?; + + let mut buf = Vec::new(); + client.read_to_end(&mut buf).await?; + + server_task.await.unwrap(); + Ok(buf) +} From ea2f7c5a7174bf751e610044200468fdea3ac1db Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 20 Jun 2025 16:25:25 +0100 Subject: [PATCH 2/2] Refine echo example and test utilities --- examples/echo.rs | 13 ++++++++++++- tests/routes.rs | 1 + tests/util.rs | 28 +++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index f30bdaa1..bcae69fc 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -5,6 +5,9 @@ use wireframe::{ server::WireframeServer, }; +/// Simple middleware demonstrating the `wrap` API. +/// +/// `Middleware` has no hooks yet, so this type is just a marker. struct Logger; impl Middleware for Logger {} @@ -15,7 +18,15 @@ async fn main() -> io::Result<()> { .unwrap() .wrap(Logger) .unwrap() - .route(1, Box::new(|_env| Box::pin(async {}))) + .route( + 1, + Box::new(|_| { + Box::pin(async move { + println!("echo request received"); + // `WireframeApp` automatically echoes the envelope back. + }) + }), + ) .unwrap() }; diff --git a/tests/routes.rs b/tests/routes.rs index a6701905..e8d45db7 100644 --- a/tests/routes.rs +++ b/tests/routes.rs @@ -37,6 +37,7 @@ async fn handler_receives_message_and_echoes_response() { let called_inner = called_clone.clone(); Box::pin(async move { called_inner.fetch_add(1, Ordering::SeqCst); + // `WireframeApp` sends the envelope back automatically }) }), ) diff --git a/tests/util.rs b/tests/util.rs index 04761ec7..66d7e0b4 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -10,8 +10,34 @@ use wireframe::app::WireframeApp; /// # Panics /// /// Panics if the spawned task running the application panics. +/// Optional duplex buffer capacity for `run_app_with_frame`. +const DEFAULT_CAPACITY: usize = 4096; + +/// Run `app` with a single input `frame` using the default buffer capacity. +/// +/// # Errors +/// +/// Returns any I/O errors encountered while interacting with the in-memory +/// duplex stream. pub async fn run_app_with_frame(app: WireframeApp, frame: Vec) -> io::Result> { - let (mut client, server) = duplex(1024); + run_app_with_frame_with_capacity(app, frame, DEFAULT_CAPACITY).await +} + +/// Drive `app` with a single frame using a duplex buffer of `capacity` bytes. +/// +/// # Errors +/// +/// Propagates any I/O errors from the in-memory connection. +/// +/// # Panics +/// +/// Panics if the spawned task running the application panics. +pub async fn run_app_with_frame_with_capacity( + app: WireframeApp, + frame: Vec, + capacity: usize, +) -> io::Result> { + let (mut client, server) = duplex(capacity); let server_task = tokio::spawn(async move { app.handle_connection(server).await; });