Crates.io | rinf-router |
lib.rs | rinf-router |
version | 1.4.0 |
created_at | 2025-05-30 21:47:36.106511+00 |
updated_at | 2025-08-17 08:50:27.263432+00 |
description | Router and handler framework for RINF-based Flutter <> Rust apps |
homepage | |
repository | https://github.com/asaphaaning/rinf-router |
max_upload_size | |
id | 1695702 |
size | 77,021 |
rinf-router
is a tiny, ergonomic routing layer that glues Flutter’s
RINF signals to asynchronous Rust handlers.
It takes care of the boring plumbing so you can focus on writing clean,
testable application logic.
tokio
and async
/await
tower
compatibility (Basic features, more to come)Add the crate to your existing RINF project.
cargo add rinf-router
A minimal example (run with cargo run
):
use {
rinf_router::{Router, State},
rinf::DartSignal,
serde::Deserialize,
std::{
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
},
};
/// Shared state for all handlers
#[derive(Clone)]
struct AppState {
counter: Arc<AtomicUsize>,
}
/// A signal coming from Dart
#[derive(Deserialize, DartSignal)]
struct Increment;
async fn incr(State(state): State<AppState>, _msg: Increment) {
// Atomically increase the counter and print the new value
let new = state.counter.fetch_add(1, Ordering::Relaxed) + 1;
println!("Counter is now: {new}");
}
#[tokio::main]
async fn main() {
let state = AppState {
counter: Arc::new(AtomicUsize::new(0)),
};
Router::new()
.route(incr)
.with_state(state) // 👈 inject shared state
.run()
.await;
}
That’s it – incoming Increment
signals are automatically deserialized, and the current AppState
is dropped right
into your handler!
A router carries exactly one state type.
Trying to register handlers that need different states on the same
router without swapping the state fails to compile:
use rinf_router::{Router, State};
#[derive(Clone)]
struct Foo;
#[derive(Clone)]
struct Bar;
async fn foo(State(_): State<Foo>) { unimplemented!() }
async fn bar(State(_): State<Bar>) { unimplemented!() }
fn main() {
Router::new()
.route(foo) // Router<Foo>
.route(bar) // ❌ Router<Foo> expected, found handler that needs Bar
.run(); // ^^^ mismatched state type
}
Fix it by either
Router::new()
.route(foo)
.with_state(state)
.route(bar)
.with_state(other_state)
.run()
.await;
or by ensuring both handlers share the same state type.
Handler-level middleware can be applied using the .layer()
method for cross-cutting concerns like logging, metrics, or request modification:
use {
rinf_router::{Router, handler::Handler},
rinf::DartSignal,
serde::Deserialize,
tower::{service_fn, ServiceBuilder},
};
#[derive(Deserialize, DartSignal)]
struct MySignal(String);
async fn my_handler(signal: MySignal) {
println!("Handler received: {}", signal.0);
}
#[tokio::main]
async fn main() {
// Create logging middleware using service_fn
let logging_middleware = ServiceBuilder::new()
.map_request(|signal: MySignal| {
println!("Processing signal: {}", signal.0);
signal
});
Router::new()
.route(my_handler.layer(logging_middleware)) // 👈 Apply middleware to handler
.run()
.await;
}
Middleware executes in outside-in order: outer layers wrap inner layers, with the handler at the center.
Run cargo doc --open
for the full API reference, including:
Enjoy – and feel free to open an issue or PR if you spot anything that could be improved!