diff --git a/examples/echo.rs b/examples/echo.rs new file mode 100644 index 00000000..bcae69fc --- /dev/null +++ b/examples/echo.rs @@ -0,0 +1,37 @@ +use std::io; + +use wireframe::{ + app::{Middleware, WireframeApp}, + 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 {} + +#[tokio::main] +async fn main() -> io::Result<()> { + let factory = || { + WireframeApp::new() + .unwrap() + .wrap(Logger) + .unwrap() + .route( + 1, + Box::new(|_| { + Box::pin(async move { + println!("echo request received"); + // `WireframeApp` automatically echoes the envelope back. + }) + }), + ) + .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..e8d45db7 --- /dev/null +++ b/tests/routes.rs @@ -0,0 +1,66 @@ +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); + // `WireframeApp` sends the envelope back automatically + }) + }), + ) + .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..66d7e0b4 --- /dev/null +++ b/tests/util.rs @@ -0,0 +1,53 @@ +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. +/// 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> { + 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; + }); + + 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) +}