Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,25 @@ incoming \[`MessageRequest`\] and remaining \[`Payload`\]. Built‑in extractors
like `Message<T>`, `SharedState<T>` and `ConnectionInfo` decode the payload,
access app state or expose peer information.

Custom extractors let you centralize parsing and validation logic that would
otherwise be duplicated across handlers. A session token parser, for example,
can verify the token before any route-specific code executes
- `echo.rs` — minimal echo server using routing
- `ping_pong.rs` — showcases serialization and middleware in a ping/pong
protocol. See [examples/ping_pong.md](examples/ping_pong.md) for a detailed
overview.
[Design Guide: Data Extraction and Type Safety][data-extraction-guide].

```rust
use wireframe::extractor::{ConnectionInfo, FromMessageRequest, MessageRequest, Payload};

pub struct SessionToken(String);

Try the echo server with netcat:

```bash
$ cargo run --example echo
# in another terminal
$ printf '\x00\x00\x00\x00\x01\x00\x00\x00' | nc 127.0.0.1 7878 | xxd
```

impl FromMessageRequest for SessionToken {
type Error = std::convert::Infallible;

Expand Down Expand Up @@ -204,12 +213,26 @@ let logging = from_fn(|req, next| async move {

## Examples

The `examples/` directory contains runnable demos illustrating different
protocol designs:
Example programs are available in the `examples/` directory:

- `echo.rs` – minimal server that echoes incoming frames.
- `echo.rs` – minimal echo server using routing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider using em dashes for consistency.

The documentation uses regular hyphens in the example descriptions. Consider using em dashes for better typographical consistency.

-- `echo.rs` – minimal echo server using routing
+- `echo.rs` — minimal echo server using routing
-- `ping_pong.rs` – showcases serialization and middleware in a ping/pong
+- `ping_pong.rs` — showcases serialization and middleware in a ping/pong

Also applies to: 212-212

🧰 Tools
🪛 LanguageTool

[typographical] ~209-~209: Consider using an em dash in dialogues and enumerations.
Context: ...the examples/ directory: - echo.rs – minimal echo server using routing - `pa...

(DASH_RULE)

🤖 Prompt for AI Agents
In README.md at lines 209 and 212, replace the regular hyphens used in example
descriptions with em dashes to improve typographical consistency. Locate the
hyphens separating the example names and their descriptions and substitute them
with em dashes (—) while preserving spacing around the dash for readability.

- `packet_enum.rs` – shows packet type discrimination with a bincode enum and a
frame containing container types like `HashMap` and `Vec`.
- `ping_pong.rs` – showcases serialization and middleware in a ping/pong
protocol

Run an example with Cargo:

```bash
cargo run --example echo
```

Try the ping‑pong server with netcat:

```bash
$ cargo run --example ping_pong
# in another terminal
$ printf '\x00\x00\x00\x08\x01\x00\x00\x00\x2a\x00\x00\x00' | nc 127.0.0.1 7878 | xxd

## Current Limitations

Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ after formatting. Line numbers below refer to that file.

## 3. Initial Examples and Documentation

- [ ] Provide examples demonstrating routing, serialization, and middleware.
- [x] Provide examples demonstrating routing, serialization, and middleware.
Document configuration and usage reflecting the API design section.

## 4. Extended Features
Expand Down
53 changes: 53 additions & 0 deletions examples/ping_pong.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Ping-Pong Example

This example demonstrates routing, serialization, and middleware usage in a
small ping/pong protocol. The server accepts a `Ping` message containing a
counter and responds with a `Pong` containing the incremented value. Logging
middleware prints each request and response.

```mermaid
classDiagram
class Ping {
+u32 0
+to_bytes()
+from_bytes()
}
class Pong {
+u32 0
+to_bytes()
+from_bytes()
}
class ErrorMsg {
+String 0
+to_bytes()
+from_bytes()
}
class PongMiddleware {
}
class PongService {
+inner: S
+call(req: ServiceRequest) Result<ServiceResponse, Infallible>
}
class Logging {
}
class LoggingService {
+inner: S
+call(req: ServiceRequest) Result<ServiceResponse, Infallible>
}
class HandlerService {
+id()
+from_service(id, service)
}
PongMiddleware --|> Transform
PongService --|> Service
Logging --|> Transform
LoggingService --|> Service
HandlerService <.. PongService : inner
HandlerService <.. LoggingService : inner
PongMiddleware ..> HandlerService : transform
Logging ..> HandlerService : transform
WireframeApp <.. build_app : factory
WireframeServer <.. main : uses
build_app --> WireframeApp
main --> WireframeServer
```
148 changes: 148 additions & 0 deletions examples/ping_pong.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use std::{io, net::SocketAddr, sync::Arc};

use async_trait::async_trait;
use wireframe::{
app::{Result as AppResult, WireframeApp},
message::Message,
middleware::{HandlerService, Service, ServiceRequest, ServiceResponse, Transform},
serializer::BincodeSerializer,
server::WireframeServer,
};

#[derive(bincode::Encode, bincode::BorrowDecode, Debug)]
struct Ping(u32);

#[derive(bincode::Encode, bincode::BorrowDecode, Debug)]
struct Pong(u32);

#[derive(bincode::Encode, bincode::BorrowDecode, Debug)]
struct ErrorMsg(String);

fn encode_error(msg: impl Into<String>) -> Vec<u8> {
let err = ErrorMsg(msg.into());
match err.to_bytes() {
Ok(bytes) => bytes,
Err(e) => {
eprintln!("failed to encode error: {e:?}");
Vec::new()
}
}
}

const PING_ID: u32 = 1;

/// Handler invoked for `PING_ID` messages.
///
/// The middleware chain generates the actual response, so this
/// handler intentionally performs no work.
#[allow(clippy::unused_async)]
async fn ping_handler() {}

struct PongMiddleware;

struct PongService<S> {
inner: S,
}

#[async_trait]
impl<S> Service for PongService<S>
where
S: Service<Error = std::convert::Infallible> + Send + Sync + 'static,
{
type Error = std::convert::Infallible;

async fn call(&self, req: ServiceRequest) -> Result<ServiceResponse, Self::Error> {
let (ping_req, _) = match Ping::from_bytes(req.frame()) {
Ok(val) => val,
Err(e) => {
eprintln!("failed to decode ping: {e:?}");
return Ok(ServiceResponse::new(encode_error(format!(
"decode error: {e:?}"
))));
}
};
let mut response = self.inner.call(req).await?;
let pong_resp = if let Some(v) = ping_req.0.checked_add(1) {
Pong(v)
} else {
eprintln!("ping overflowed at {}", ping_req.0);
return Ok(ServiceResponse::new(encode_error("overflow")));
};
match pong_resp.to_bytes() {
Ok(bytes) => *response.frame_mut() = bytes,
Err(e) => {
eprintln!("failed to encode pong: {e:?}");
return Ok(ServiceResponse::new(encode_error(format!(
"encode error: {e:?}"
))));
}
}
Ok(response)
}
}

#[async_trait]
impl Transform<HandlerService> for PongMiddleware {

Check failure on line 85 in examples/ping_pong.rs

View workflow job for this annotation

GitHub Actions / build-test

missing generics for struct `wireframe::middleware::HandlerService`
type Output = HandlerService;

Check failure on line 86 in examples/ping_pong.rs

View workflow job for this annotation

GitHub Actions / build-test

missing generics for struct `wireframe::middleware::HandlerService`

// `HandlerService` is a boxed trait object without generic parameters,
// so the transform signature uses the concrete type directly.
async fn transform(&self, service: HandlerService) -> Self::Output {

Check failure on line 90 in examples/ping_pong.rs

View workflow job for this annotation

GitHub Actions / build-test

missing generics for struct `wireframe::middleware::HandlerService`
let id = service.id();
HandlerService::from_service(id, PongService { inner: service })
}
}

struct Logging;

struct LoggingService<S> {
inner: S,
}

#[async_trait]
impl<S> Service for LoggingService<S>
where
S: Service<Error = std::convert::Infallible> + Send + Sync + 'static,
{
type Error = std::convert::Infallible;

async fn call(&self, req: ServiceRequest) -> Result<ServiceResponse, Self::Error> {
println!("request: {:?}", req.frame());
let resp = self.inner.call(req).await?;
println!("response: {:?}", resp.frame());
Ok(resp)
}
}

#[async_trait]
impl Transform<HandlerService> for Logging {

Check failure on line 118 in examples/ping_pong.rs

View workflow job for this annotation

GitHub Actions / build-test

missing generics for struct `wireframe::middleware::HandlerService`
type Output = HandlerService;

Check failure on line 119 in examples/ping_pong.rs

View workflow job for this annotation

GitHub Actions / build-test

missing generics for struct `wireframe::middleware::HandlerService`

// `HandlerService` is a concrete type, not a generic wrapper.
async fn transform(&self, service: HandlerService) -> Self::Output {

Check failure on line 122 in examples/ping_pong.rs

View workflow job for this annotation

GitHub Actions / build-test

missing generics for struct `wireframe::middleware::HandlerService`
let id = service.id();
HandlerService::from_service(id, LoggingService { inner: service })
}
}

fn build_app() -> AppResult<WireframeApp> {
WireframeApp::new()?
.serializer(BincodeSerializer)
.route(PING_ID, Arc::new(|_| Box::pin(ping_handler())))?
.wrap(PongMiddleware)?
.wrap(Logging)
}

#[tokio::main]
async fn main() -> io::Result<()> {
let factory = || build_app().expect("app build failed");

let default_addr = "127.0.0.1:7878";
let addr_str = std::env::args()
.nth(1)
.unwrap_or_else(|| default_addr.into());
let addr: SocketAddr = addr_str
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
WireframeServer::new(factory).bind(addr)?.run().await
}
Loading