rinf-router

Crates.iorinf-router
lib.rsrinf-router
version1.4.0
created_at2025-05-30 21:47:36.106511+00
updated_at2025-08-17 08:50:27.263432+00
descriptionRouter and handler framework for RINF-based Flutter <> Rust apps
homepage
repositoryhttps://github.com/asaphaaning/rinf-router
max_upload_size
id1695702
size77,021
Frederik Haaning (asaphaaning)

documentation

README

rinf-router

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.


Features

  • Familiar, Axum-style API
  • Zero-boilerplate extraction of data and shared state
  • Fully async – powered by tokio and async/await
  • Runs anywhere RINF runs (desktop, mobile, web)
  • tower compatibility (Basic features, more to come)

Upcoming features

  • Graceful shutdown support

Quick-start

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!


Common pitfall: mismatched states

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.


Tower Middleware

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.

Learn more

Run cargo doc --open for the full API reference, including:

  • Custom extractors
  • Error handling

Enjoy – and feel free to open an issue or PR if you spot anything that could be improved!

Commit count: 9

cargo fmt