{"id":30615680,"url":"https://github.com/leynos/wireframe","last_synced_at":"2025-08-30T08:06:39.188Z","repository":{"id":298491214,"uuid":"1000145005","full_name":"leynos/wireframe","owner":"leynos","description":"Frame router for wire protocols in Rust","archived":false,"fork":false,"pushed_at":"2025-08-25T17:32:37.000Z","size":841,"stargazers_count":0,"open_issues_count":42,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-25T19:28:35.853Z","etag":null,"topics":["lib","network-protocols","router","rust","rust-crate","wire-protocol"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leynos.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-11T10:31:50.000Z","updated_at":"2025-08-21T01:43:27.000Z","dependencies_parsed_at":"2025-06-11T11:55:22.137Z","dependency_job_id":"c27ce7b6-d6e3-4403-aa0e-b0b0a7c3102f","html_url":"https://github.com/leynos/wireframe","commit_stats":null,"previous_names":["leynos/wireframe"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/leynos/wireframe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leynos%2Fwireframe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leynos%2Fwireframe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leynos%2Fwireframe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leynos%2Fwireframe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leynos","download_url":"https://codeload.github.com/leynos/wireframe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leynos%2Fwireframe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272821200,"owners_count":24998599,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lib","network-protocols","router","rust","rust-crate","wire-protocol"],"created_at":"2025-08-30T08:06:32.936Z","updated_at":"2025-08-30T08:06:39.183Z","avatar_url":"https://github.com/leynos.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Wireframe\n\n**Wireframe** is an experimental Rust library that simplifies building servers\nand clients for custom binary protocols. The design borrows heavily from\n[Actix Web](https://actix.rs/) to provide a familiar, declarative API for\nrouting, extractors, and middleware.\n\n## Motivation\n\nManual handling of binary protocols typically involves verbose serialization\ncode, custom frame parsing, and complex dispatch logic. `wireframe` aims to\nreduce this boilerplate through layered abstractions:\n\n- **Transport adapter** built on Tokio I/O\n- **Framing layer** for length‑prefixed or custom frames\n- **Connection preamble** with customizable validation callbacks\n  \\[[docs](docs/preamble-validator.md)\\]\n- Call `with_preamble::\u003cT\u003e()` before registering success or failure callbacks\n- **Serialization engine** using `bincode` or a `wire-rs` wrapper\n- **Routing engine** that dispatches messages by ID\n- **Handler invocation** with extractor support\n- **Middleware chain** for request/response processing\n- **[Connection lifecycle hooks](#connection-lifecycle)** for per-connection\n  setup and teardown\n\nThese layers correspond to the architecture outlined in the design\ndocument【F:docs/rust-binary-router-library-design.md†L292-L344】.\n\n## API Overview\n\nApplications are configured using a builder pattern similar to Actix Web. A\n`WireframeApp` defines routes and middleware, while `WireframeServer` manages\nconnections and runs the Tokio event loop:\n\n```rust\nWireframeServer::new(|| {\n    WireframeApp::new()\n        .app_data(state.clone())\n        .route(MessageType::Login, handle_login)\n        .wrap(MyLoggingMiddleware::default())\n})\n.bind(\"127.0.0.1:7878\")?\n.run()\n.await\n```\n\nBy default, the number of worker tasks equals the number of CPU cores. If the\nCPU count cannot be determined, the server falls back to a single worker.\n\nThe builder supports methods like `route`, `app_data`, and `wrap` for\nmiddleware configuration. `app_data` stores any `Send + Sync` value keyed by\ntype; registering another value of the same type overwrites the previous one.\nHandlers retrieve these values using the `SharedState\u003cT\u003e`\nextractor【F:docs/rust-binary-router-library-design.md†L622-L710】.\n\nHandlers are asynchronous functions whose parameters implement extractor traits\nand may return responses implementing the `Responder` trait. This pattern\nmirrors Actix Web handlers and keeps protocol logic\nconcise【F:docs/rust-binary-router-library-design.md†L682-L710】.\n\n## Example\n\nThe design document includes a simple echo server that demonstrates routing\nbased on a message ID and the use of a length‑delimited codec:\n\n```rust\nasync fn handle_echo(req: Message\u003cEchoRequest\u003e) -\u003e WireframeResult\u003cEchoResponse\u003e {\n    Ok(EchoResponse {\n        original_payload: req.payload.clone(),\n        echoed_at: time_now(),\n    })\n}\n\nWireframeServer::new(|| {\n    WireframeApp::new()\n        .serializer(BincodeSerializer)\n        .route(MyMessageType::Echo, handle_echo)\n})\n.bind(\"127.0.0.1:8000\")?\n.run()\n.await\n```\n\nThis example showcases how derive macros and the framing abstraction simplify a\nbinary protocol server. See the \u003c!-- markdownlint-disable-next-line MD013 --\u003e\n[full example](docs/rust-binary-router-library-design.md#5-6-illustrative-api-usage-examples)\n in the design document for further details.\n\n## Custom Envelopes\n\n`WireframeApp` defaults to a simple `Envelope` containing a message ID and raw\npayload bytes. Applications can supply their own envelope type by calling\n`WireframeApp::\u003c_, _, MyEnv\u003e::new()`. The custom type must implement the\n`Packet` trait:\n\n```rust\nuse wireframe::app::{Packet, PacketParts, WireframeApp};\n\n#[derive(bincode::Encode, bincode::BorrowDecode)]\nstruct MyEnv { id: u32, correlation_id: Option\u003cu64\u003e, payload: Vec\u003cu8\u003e }\n\nimpl Packet for MyEnv {\n    fn id(\u0026self) -\u003e u32 { self.id }\n    fn correlation_id(\u0026self) -\u003e Option\u003cu64\u003e { self.correlation_id }\n    fn into_parts(self) -\u003e PacketParts {\n        PacketParts::new(self.id, self.correlation_id, self.payload)\n    }\n    fn from_parts(parts: PacketParts) -\u003e Self {\n        let id = parts.id();\n        let correlation_id = parts.correlation_id();\n        let payload = parts.payload();\n        Self { id, correlation_id, payload }\n    }\n}\n\nlet app = WireframeApp::\u003c_, _, MyEnv\u003e::new()\n    .unwrap()\n    .route(1, std::sync::Arc::new(|env: \u0026MyEnv| Box::pin(async move { /* ... */ })))\n    .unwrap();\n```\n\nA `None` correlation ID denotes an unsolicited event or server-initiated push.\nUse `None` rather than `Some(0)` when a frame lacks a correlation ID. See\n[PacketParts](docs/api.md#packetparts) for field details.\n\nThis allows integration with existing packet formats without modifying\n`handle_frame`.\n\n## Response Serialization and Framing\n\nHandlers can return types implementing the `Responder` trait. These values are\nencoded using the application's configured serializer and written back through\nthe `FrameProcessor`【F:docs/rust-binary-router-library-design.md†L724-L730】.\n\nThe included `LengthPrefixedProcessor` illustrates a simple framing strategy\nthat prefixes each frame with its length. The format is configurable (prefix\nsize and endianness) and defaults to a 4‑byte big‑endian length\nprefix【F:docs/rust-binary-router-library-design.md†L1082-L1123】.\n\n```rust\nlet app = WireframeApp::new()?;\n```\n\n## Connection Lifecycle\n\nProtocol callbacks are consolidated under the `WireframeProtocol` trait,\nreplacing the individual `on_connection_setup`/`on_connection_teardown`\nclosures. The trait methods are synchronous so the trait remains object safe,\nbut callbacks can spawn asynchronous tasks when needed. A protocol\nimplementation registers hooks for connection setup, frame mutation and command\ncompletion. The associated `ProtocolError` type is used by other parts of the\nAPI, such as request handling.\n\n```rust\npub trait WireframeProtocol: Send + Sync + 'static {\n    type Frame: FrameLike;\n    type ProtocolError;\n\n    fn on_connection_setup(\n        \u0026self,\n        handle: PushHandle\u003cSelf::Frame\u003e,\n        ctx: \u0026mut ConnectionContext,\n    );\n\n    fn before_send(\u0026self, frame: \u0026mut Self::Frame, ctx: \u0026mut ConnectionContext);\n\n    fn on_command_end(\u0026self, ctx: \u0026mut ConnectionContext);\n}\n\nstruct MySqlProtocolImpl;\n\nimpl WireframeProtocol for MySqlProtocolImpl {\n    type Frame = Vec\u003cu8\u003e;\n    type ProtocolError = ();\n\n    fn on_connection_setup(\n        \u0026self,\n        handle: PushHandle\u003cSelf::Frame\u003e,\n        _ctx: \u0026mut ConnectionContext,\n    ) {\n        // Spawn an async task to send a heartbeat after setup\n        tokio::spawn(async move {\n            let _ = handle.push_high_priority(b\"ping\".to_vec()).await;\n        });\n    }\n\n    fn before_send(\u0026self, _frame: \u0026mut Self::Frame, _ctx: \u0026mut ConnectionContext) {}\n\n    fn on_command_end(\u0026self, _ctx: \u0026mut ConnectionContext) {}\n}\n\n```\n\n```rust\nlet app = WireframeApp::new().with_protocol(MySqlProtocolImpl);\n```\n\n## Session Registry\n\nThe \\[`SessionRegistry`\\] stores weak references to \\[`PushHandle`\\]s for\nactive connections. Background tasks can look up a handle by \\[`ConnectionId`\\]\nto send frames asynchronously without keeping the connection alive. Entries are\npruned on lookup and when calling `active_handles()`. `DashMap::retain` holds\nper-bucket write locks while collecting, so heavy traffic may experience\ncontention. Invoke `prune()` from a maintenance task when only removal of dead\nentries is required, without collecting handles.\n\n```rust\nuse wireframe::{\n    session::{ConnectionId, SessionRegistry},\n    push::PushHandle,\n    ConnectionContext,\n};\n\nlet registry: SessionRegistry\u003cMyFrame\u003e = SessionRegistry::default();\n\n// inside a `WireframeProtocol` implementation\nfn on_connection_setup(\u0026self, handle: PushHandle\u003cMyFrame\u003e, _ctx: \u0026mut ConnectionContext) {\n    let id = ConnectionId::new(42);\n    registry.insert(id, \u0026handle);\n}\n```\n\n## Custom Extractors\n\nExtractors are types that implement `FromMessageRequest`. When a handler lists\nan extractor as a parameter, `wireframe` automatically constructs it using the\nincoming \\[`MessageRequest`\\] and remaining \\[`Payload`\\]. Built‑in extractors\nlike `Message\u003cT\u003e`, `SharedState\u003cT\u003e` and `ConnectionInfo` decode the payload,\naccess app state or expose peer information.\n\nCustom extractors let you centralize parsing and validation logic that would\notherwise be duplicated across handlers. A session token parser, for example,\ncan verify the token before any route-specific code executes [Design Guide:\nData Extraction and Type Safety][data-extraction-guide].\n\n```rust\nuse wireframe::extractor::{ConnectionInfo, FromMessageRequest, MessageRequest, Payload};\n\npub struct SessionToken(String);\n\nimpl FromMessageRequest for SessionToken {\n    type Error = std::convert::Infallible;\n\n    fn from_message_request(\n        _req: \u0026MessageRequest,\n        payload: \u0026mut Payload\u003c'_\u003e,\n    ) -\u003e Result\u003cSelf, Self::Error\u003e {\n        let len = payload.as_ref()[0] as usize;\n        let token = std::str::from_utf8(\u0026payload.as_ref()[1..=len]).unwrap().to_string();\n        payload.advance(1 + len);\n        Ok(Self(token))\n    }\n}\n```\n\nCustom extractors integrate seamlessly with other parameters:\n\n```rust\nasync fn handle_ping(token: SessionToken, info: ConnectionInfo) {\n    println!(\"{} from {:?}\", token.0, info.peer_addr());\n}\n```\n\n## Middleware\n\nMiddleware allows inspecting or modifying requests and responses. The `from_fn`\nhelper builds middleware from an async function or closure:\n\n```rust\nuse wireframe::middleware::from_fn;\n\nlet logging = from_fn(|req, next| async move {\n    tracing::info!(\"received request: {:?}\", req);\n    let res = next.call(req).await?;\n    tracing::info!(\"sending response: {:?}\", res);\n    Ok(res)\n});\n```\n\n## Examples\n\nExample programs are available in the `examples/` directory:\n\n- `echo.rs` — minimal echo server using routing\n- `ping_pong.rs` — showcases serialization and middleware in a ping/pong\n  protocol. See [examples/ping_pong.md](examples/ping_pong.md) for a detailed\n  overview.\n- [`packet_enum.rs`](examples/packet_enum.rs) — shows packet type discrimination\n  with a bincode enum and a frame containing container types like `HashMap` and\n  `Vec`.\n\nRun an example with Cargo:\n\n```bash\ncargo run --example echo\n```\n\nTry the echo server with netcat:\n\n```bash\n$ cargo run --example echo\n# in another terminal\n$ printf '\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00' | nc 127.0.0.1 7878 | xxd\n```\n\nTry the ping‑pong server with netcat:\n\n```bash\n$ cargo run --example ping_pong\n# in another terminal\n$ printf '\\x00\\x00\\x00\\x08\\x01\\x00\\x00\\x00\\x2a\\x00\\x00\\x00' | nc 127.0.0.1 7878 | xxd\n```\n\n## Current Limitations\n\nConnection handling now processes frames and routes messages. Although the\nserver is still experimental, it now compiles in release mode for evaluation or\nproduction use.\n\n## Roadmap\n\nDevelopment priorities are tracked in [docs/roadmap.md](docs/roadmap.md). Key\ntasks include building the Actix‑inspired API, implementing middleware and\nextractor traits, and providing example\napplications【F:docs/roadmap.md†L1-L24】.\n\n## Licence\n\nWireframe is distributed under the terms of the ISC licence. See\n[LICENSE](LICENSE) for details.\n\n[data-extraction-guide]:\ndocs/rust-binary-router-library-design.md#53-data-extraction-and-type-safety\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleynos%2Fwireframe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleynos%2Fwireframe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleynos%2Fwireframe/lists"}