Patterns for library authors

State

Let's take a look at an implementation of a library that incrementally counts the number of conns that pass through it and attaches the number to each conn. It would be unsafe to store a u64 directly in the state set, because other libraries might be doing so, so we wrap it with a private newtype called ConnNumber. Since this isn't accessible outside of our library, we can be sure that our handler is the only place that sets it. We provide a ConnExt trait in order to provide access to this data.

mod conn_counter {
    use std::sync::{
        atomic::{AtomicU64, Ordering},
        Arc,
    };
    use trillium::{async_trait, Conn, Handler};

    struct ConnNumber(u64);

    #[derive(Default)]
    pub struct ConnCounterHandler(Arc<AtomicU64>);

    impl ConnCounterHandler {
        pub fn new() -> Self {
            Self::default()
        }
    }

    #[async_trait]
    impl Handler for ConnCounterHandler {
        async fn run(&self, conn: Conn) -> Conn {
            let number = self.0.fetch_add(1, Ordering::SeqCst);
            conn.with_state(ConnNumber(number))
        }
    }

    pub trait ConnCounterConnExt {
        fn conn_number(&self) -> u64;
    }

    impl ConnCounterConnExt for Conn {
        fn conn_number(&self) -> u64 {
            self.state::<ConnNumber>()
                .expect("conn_number must be called after the handler")
                .0
        }
    }
}

And usage of the library looks like this:

use conn_counter::{ConnCounterConnExt, ConnCounterHandler};
use trillium::{Conn, Handler};

fn handler() -> impl Handler {
    (ConnCounterHandler::new(), |conn: Conn| async move {
        let conn_number = conn.conn_number();
        conn.ok(format!("conn number was {conn_number}"))
    })
}

fn main() {
    trillium_smol::run(handler());
}

#[cfg(test)]
mod test {
    use trillium_testing::prelude::*;

    #[test]
    fn test_conn_counter() {
        let handler = super::handler();
        assert_ok!(get("/").on(&handler), "conn number was 0");
        assert_ok!(get("/").on(&handler), "conn number was 1");
        assert_ok!(get("/").on(&handler), "conn number was 2");
        assert_ok!(get("/").on(&handler), "conn number was 3");
    }
}