Skip to content

Commit 4bcade6

Browse files
authored
Refactor app into modular submodules (#332)
* Modularize application builder * Cache routes and simplify connection framing * Clarify client builder framing docs * Clamp connection buffers and unify framing * Refine connection handling and docs * Simplify serializer override * Drop duplex client in test helper * Drop duplex client half in test helper * Clamp builder configuration and tidy tests * Use std::io in test helper docs * Address review feedback on examples and tests * Use Middleware alias and standardize initialization docs * Scope lint expectations * Refine builder bounds and framed responses * Use header serializer directly in metadata example
1 parent 49b2adb commit 4bcade6

26 files changed

Lines changed: 1080 additions & 992 deletions

Cargo.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ tokio = { version = "1.46.1", default-features = false, features = [
2424
"sync",
2525
"time",
2626
"io-util",
27-
"test-util",
2827
] }
29-
tokio-util = { version = "0.7.16", features = ["rt"] }
28+
tokio-util = { version = "0.7.16", features = ["rt", "codec"] }
3029
futures = "0.3.31"
3130
async-trait = "0.1.88"
3231
bytes = "1.10.1"
@@ -50,10 +49,19 @@ serial_test = "3.2.0"
5049
# Permit compatible bug fixes but block breaking updates
5150
cucumber = "0.21.1"
5251
metrics-util = "0.20.0"
53-
tracing = { version = "0.1.41", features = ["log", "log-always"] }
5452
tracing-test = "0.2.5"
5553
mockall = "0.13.1"
5654

55+
tokio = { version = "1.46.1", default-features = false, features = [
56+
"macros",
57+
"rt-multi-thread",
58+
"sync",
59+
"time",
60+
"io-util",
61+
"net",
62+
"test-util",
63+
] }
64+
5765
[features]
5866
default = ["metrics"]
5967
metrics = ["dep:metrics", "dep:metrics-exporter-prometheus"]

README.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ connections and runs the Tokio event loop:
3535
```rust
3636
WireframeServer::new(|| {
3737
WireframeApp::new()
38-
.frame_processor(MyFrameProcessor::new())
3938
.app_data(state.clone())
4039
.route(MessageType::Login, handle_login)
4140
.wrap(MyLoggingMiddleware::default())
@@ -48,10 +47,10 @@ WireframeServer::new(|| {
4847
By default, the number of worker tasks equals the number of CPU cores. If the
4948
CPU count cannot be determined, the server falls back to a single worker.
5049

51-
The builder supports methods like `frame_processor`, `route`, `app_data`, and
52-
`wrap` for middleware configuration. `app_data` stores any `Send + Sync` value
53-
keyed by type; registering another value of the same type overwrites the
54-
previous one. Handlers retrieve these values using the `SharedState<T>`
50+
The builder supports methods like `route`, `app_data`, and `wrap` for
51+
middleware configuration. `app_data` stores any `Send + Sync` value keyed by
52+
type; registering another value of the same type overwrites the previous one.
53+
Handlers retrieve these values using the `SharedState<T>`
5554
extractor【F:docs/rust-binary-router-library-design.md†L622-L710】.
5655

5756
Handlers are asynchronous functions whose parameters implement extractor traits
@@ -62,7 +61,7 @@ concise【F:docs/rust-binary-router-library-design.md†L682-L710】.
6261
## Example
6362

6463
The design document includes a simple echo server that demonstrates routing
65-
based on a message ID and the use of a length‑prefixed frame processor:
64+
based on a message ID and the use of a length‑delimited codec:
6665

6766
```rust
6867
async fn handle_echo(req: Message<EchoRequest>) -> WireframeResult<EchoResponse> {
@@ -139,10 +138,7 @@ size and endianness) and defaults to a 4‑byte big‑endian length
139138
prefix【F:docs/rust-binary-router-library-design.md†L1082-L1123】.
140139

141140
```rust
142-
use wireframe::frame::{LengthFormat, LengthPrefixedProcessor};
143-
144-
let app = WireframeApp::new()?
145-
.frame_processor(LengthPrefixedProcessor::new(LengthFormat::u16_le()));
141+
let app = WireframeApp::new()?;
146142
```
147143

148144
## Connection Lifecycle

docs/generic-message-fragmentation-and-re-assembly-design.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,6 @@ Developers will enable fragmentation by adding the `FragmentAdapter` to their
162162
// Example: Configuring a server for MySQL-style fragmentation.
163163
WireframeServer::new(|| {
164164
WireframeApp::new()
165-
.frame_processor(
166-
FragmentAdapter::new(MySqlStrategy)
167-
.with_max_message_size(64 * 1024 * 1024) // 64 MiB
168-
.with_reassembly_timeout(Duration::from_secs(30))
169-
)
170165
.route(...)
171166
})
172167
```

docs/rust-binary-router-library-design.md

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -726,8 +726,7 @@ component to run it.
726726
async fn main_server_setup() -> std::io::Result<()> {
727727
let app_state = Arc::new(Mutex::new(AppState::new()));
728728
WireframeServer::new(move || { // Closure provides App per worker thread
729-
WireframeApp::new()
730-
.frame_processor(MyFrameProcessor::new()) // Configure the framing logic
729+
WireframeApp::new()
731730
.app_data(app_state.clone()) // Shared application state
732731
//.service(login_handler) // If using attribute macros and auto-discovery
733732
//.service(chat_handler)
@@ -745,10 +744,8 @@ The WireframeApp builder would offer methods like:
745744

746745
- WireframeApp::new(): Creates a new application builder.
747746

748-
- .frame_processor(impl FrameProcessor): Sets the framing logic.
749-
750-
- .service(handler_function): Registers a handler function, potentially
751-
inferring the message type it handles if attribute macros are used.
747+
- `[deprecated]` `.frame_processor(impl FrameProcessor)`: framing is now
748+
handled by the connection codec.
752749

753750
- .route(message_id, handler_function): Explicitly maps a message identifier to
754751
a handler.
@@ -1273,7 +1270,7 @@ its own `FrameProcessor` trait or provide helpers.) <!-- list break -->
12731270

12741271
WireframeServer::new(|| {
12751272
WireframeApp::new()
1276-
//.frame_processor(LengthPrefixedCodec) // Simplified
1273+
//.frame_processor(LengthPrefixedCodec) // deprecated: framing handled by codec
12771274
.serializer(BincodeSerializer) // Specify serializer
12781275
.route(MyMessageType::Echo, handle_echo) // Route based on ID
12791276
// OR if type-based routing is supported and EchoRequest has an ID:
@@ -1383,14 +1380,14 @@ simplify server implementation.
13831380
let chat_state = Arc::new(Mutex::new(ChatRoomState {
13841381
users: HashMap::new()
13851382
}));
1386-
WireframeServer::new(move || {
1387-
WireframeApp::new()
1388-
//.frame_processor(...)
1389-
.serializer(BincodeSerializer)
1390-
.app_data(chat_state.clone())
1391-
.route(ChatMessageType::ClientJoin, handle_join)
1392-
.route(ChatMessageType::ClientPost, handle_post)
1393-
})
1383+
WireframeServer::new(move || {
1384+
WireframeApp::new()
1385+
//.frame_processor(...) // deprecated: framing handled by codec
1386+
.serializer(BincodeSerializer)
1387+
.app_data(chat_state.clone())
1388+
.route(ChatMessageType::ClientJoin, handle_join)
1389+
.route(ChatMessageType::ClientPost, handle_post)
1390+
})
13941391
.bind("127.0.0.1:8001")?
13951392
.run()
13961393
.await

docs/wireframe-client-design.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ A `WireframeClient::builder()` method configures the client:
3333

3434
```rust
3535
let client = WireframeClient::builder()
36-
.frame_processor(LengthPrefixedProcessor::new(LengthFormat::u32_be()))
3736
.serializer(BincodeSerializer)
3837
.connect("127.0.0.1:7878")
3938
.await?;
4039
```
4140

42-
The same `FrameProcessor` and `Serializer` traits used by the server are reused
43-
here, ensuring messages are framed and encoded consistently.
41+
The same `Serializer` trait used by the server is reused here, ensuring
42+
messages are encoded consistently while framing is handled by the
43+
length‑delimited codec.
4444

4545
### Request/Response Helpers
4646

@@ -52,9 +52,9 @@ let request = Login { username: "guest".into() };
5252
let response: LoginAck = client.call(request).await?;
5353
```
5454

55-
Internally, this uses the `Serializer` to encode the request, writes it through
56-
the `FrameProcessor`, then waits for a frame, decodes it, and deserializes the
57-
response type.
55+
Internally, this uses the `Serializer` to encode the request, sends it through
56+
the length‑delimited codec, then waits for a frame, decodes it, and
57+
deserializes the response type.
5858

5959
### Connection Lifecycle
6060

@@ -68,7 +68,6 @@ initialization logic.
6868
#[tokio::main]
6969
async fn main() -> std::io::Result<()> {
7070
let mut client = WireframeClient::builder()
71-
.frame_processor(LengthPrefixedProcessor::new(LengthFormat::u32_be()))
7271
.serializer(BincodeSerializer)
7372
.connect("127.0.0.1:7878")
7473
.await?;

docs/wireframe-testing-crate.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,12 @@ let (_, frame) = recv_expect!(queues.recv());
135135
```rust
136136
use std::sync::Arc;
137137
use wireframe_testing::{drive_with_frame, drive_with_frames};
138-
use wireframe::processor::LengthPrefixedProcessor;
139138
use crate::tests::{build_test_frame, expected_bytes};
140139

141140
#[tokio::test]
142141
async fn handler_echoes_message() {
143142
let app = WireframeApp::new()
144143
.unwrap()
145-
.frame_processor(LengthPrefixedProcessor::default())
146144
.route(1, Arc::new(|_| Box::pin(async {})))
147145
.unwrap();
148146

examples/echo.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
//! envelope back to the client.
55
66
use wireframe::{
7-
app::{Envelope, WireframeApp},
7+
app::Envelope,
8+
serializer::BincodeSerializer,
89
server::{ServerError, WireframeServer},
910
};
1011

12+
type App = wireframe::app::WireframeApp<BincodeSerializer, (), Envelope>;
13+
1114
#[tokio::main]
1215
async fn main() -> Result<(), ServerError> {
1316
let factory = || {
14-
WireframeApp::new()
17+
App::new()
1518
.expect("failed to create WireframeApp")
1619
.route(
1720
1,

examples/metadata_routing.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ use std::{io, sync::Arc};
88
use bytes::BytesMut;
99
use tokio::io::{AsyncWriteExt, duplex};
1010
use wireframe::{
11-
app::{Envelope, WireframeApp},
11+
app::Envelope,
1212
frame::{FrameMetadata, FrameProcessor, LengthPrefixedProcessor},
1313
message::Message,
1414
serializer::Serializer,
1515
};
1616

17+
type App = wireframe::app::WireframeApp<HeaderSerializer, (), Envelope>;
18+
1719
/// Frame format with a two-byte id, one-byte flags, and bincode payload.
20+
#[derive(Default)]
1821
struct HeaderSerializer;
1922

2023
impl Serializer for HeaderSerializer {
@@ -61,15 +64,13 @@ struct Ping;
6164

6265
#[tokio::main]
6366
async fn main() -> io::Result<()> {
64-
let app = WireframeApp::new()
67+
let app = App::with_serializer(HeaderSerializer)
6568
.expect("failed to create app")
66-
.frame_processor(LengthPrefixedProcessor::default())
67-
.serializer(HeaderSerializer)
6869
.route(
6970
1,
7071
Arc::new(|_env: &Envelope| {
7172
Box::pin(async move {
72-
println!("received ping message");
73+
tracing::info!("received ping message");
7374
})
7475
}),
7576
)
@@ -78,7 +79,7 @@ async fn main() -> io::Result<()> {
7879
2,
7980
Arc::new(|_env: &Envelope| {
8081
Box::pin(async move {
81-
println!("received pong message");
82+
tracing::info!("received pong message");
8283
})
8384
}),
8485
)

examples/packet_enum.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66
use std::{collections::HashMap, future::Future, pin::Pin};
77

88
use async_trait::async_trait;
9+
use tracing::{info, warn};
910
use wireframe::{
10-
app::{Envelope, WireframeApp},
11-
frame::{LengthFormat, LengthPrefixedProcessor},
11+
app::Envelope,
1212
message::Message,
1313
middleware::{HandlerService, Service, ServiceRequest, ServiceResponse, Transform},
14+
serializer::BincodeSerializer,
1415
server::{ServerError, WireframeServer},
1516
};
1617

18+
type App = wireframe::app::WireframeApp<BincodeSerializer, (), Envelope>;
19+
1720
#[derive(bincode::Encode, bincode::BorrowDecode, Debug)]
18-
enum Packet {
21+
enum ExamplePacket {
1922
Ping,
2023
Chat { user: String, msg: String },
2124
Stats(Vec<u32>),
@@ -24,7 +27,7 @@ enum Packet {
2427
#[derive(bincode::Encode, bincode::BorrowDecode, Debug)]
2528
struct Frame {
2629
headers: HashMap<String, String>,
27-
packet: Packet,
30+
packet: ExamplePacket,
2831
}
2932

3033
/// Middleware that decodes incoming frames and logs packet details.
@@ -45,12 +48,12 @@ where
4548
async fn call(&self, req: ServiceRequest) -> Result<ServiceResponse, Self::Error> {
4649
match Frame::from_bytes(req.frame()) {
4750
Ok((frame, _)) => match frame.packet {
48-
Packet::Ping => println!("ping: {:?}", frame.headers),
49-
Packet::Chat { user, msg } => println!("{user} says: {msg}"),
50-
Packet::Stats(values) => println!("stats: {values:?}"),
51+
ExamplePacket::Ping => info!("ping: {:?}", frame.headers),
52+
ExamplePacket::Chat { user, msg } => info!("{user} says: {msg}"),
53+
ExamplePacket::Stats(values) => info!("stats: {values:?}"),
5154
},
5255
Err(e) => {
53-
eprintln!("Failed to decode frame: {e}");
56+
warn!("Failed to decode frame: {e}");
5457
}
5558
}
5659

@@ -71,16 +74,15 @@ impl Transform<HandlerService<Envelope>> for DecodeMiddleware {
7174

7275
fn handle_packet(_env: &Envelope) -> Pin<Box<dyn Future<Output = ()> + Send>> {
7376
Box::pin(async {
74-
println!("packet received");
77+
info!("packet received");
7578
})
7679
}
7780

7881
#[tokio::main]
7982
async fn main() -> Result<(), ServerError> {
8083
let factory = || {
81-
WireframeApp::new()
84+
App::new()
8285
.expect("Failed to create WireframeApp")
83-
.frame_processor(LengthPrefixedProcessor::new(LengthFormat::u16_le()))
8486
.wrap(DecodeMiddleware)
8587
.expect("Failed to wrap middleware")
8688
.route(1, std::sync::Arc::new(handle_packet))

examples/ping_pong.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ use std::{net::SocketAddr, sync::Arc};
77

88
use async_trait::async_trait;
99
use wireframe::{
10-
app::{Envelope, Packet, Result as AppResult, WireframeApp},
10+
app::{Envelope, Packet, Result as AppResult},
1111
message::Message,
1212
middleware::{HandlerService, Service, ServiceRequest, ServiceResponse, Transform},
1313
serializer::BincodeSerializer,
1414
server::{ServerError, WireframeServer},
1515
};
1616

17+
type App = wireframe::app::WireframeApp<BincodeSerializer, (), Envelope>;
18+
1719
#[derive(bincode::Encode, bincode::BorrowDecode, Debug)]
1820
struct Ping(u32);
1921

@@ -40,7 +42,10 @@ const PING_ID: u32 = 1;
4042
///
4143
/// The middleware chain generates the actual response, so this
4244
/// handler intentionally performs no work.
43-
#[allow(clippy::unused_async)]
45+
#[expect(
46+
clippy::unused_async,
47+
reason = "Keep async signature to match Handler and Transform trait expectations"
48+
)]
4449
async fn ping_handler() {}
4550

4651
struct PongMiddleware;
@@ -130,8 +135,8 @@ impl<E: Packet> Transform<HandlerService<E>> for Logging {
130135
}
131136
}
132137

133-
fn build_app() -> AppResult<WireframeApp> {
134-
WireframeApp::new()?
138+
fn build_app() -> AppResult<App> {
139+
App::new()?
135140
.serializer(BincodeSerializer)
136141
.route(PING_ID, Arc::new(|_: &Envelope| Box::pin(ping_handler())))?
137142
.wrap(PongMiddleware)?

0 commit comments

Comments
 (0)