trillium-api 0.3.0

an api handler for trillium.rs
Documentation
# Recipes

Patterns and ideas for common use cases.

## Middleware with `api()`

An api handler can act as middleware by returning a handler that either
halts the conn (blocking downstream handlers) or does nothing (letting
them proceed).

The key trick: extract with `Option<T>` (which always succeeds), then
decide whether to halt.

```rust
use trillium_api::{api, FromConn, Halt};
use trillium::{Conn, Handler, Status};

#[derive(Debug, Clone)]
struct User(String);

impl FromConn for User {
    async fn from_conn(conn: &mut Conn) -> Option<Self> {
        conn.request_headers()
            .get_str("x-user")
            .map(|s| User(s.to_owned()))
    }
}

async fn require_user(
    _conn: &mut Conn,
    user: Option<User>,
) -> Option<(Status, Halt)> {
    if user.is_none() {
        Some((Status::Forbidden, Halt))
    } else {
        None   // no-op — next handler runs
    }
}

// Place before your router in the handler tuple:
# use trillium_testing::TestServer;
# trillium_testing::block_on(async {
let app = TestServer::new((
    api(require_user),
    "hello, authenticated user",
)).await;
# app.get("/").await.assert_status(Status::Forbidden);
# app.get("/").with_request_header("x-user", "alice").await.assert_ok().assert_body("hello, authenticated user");
# });
```

## Type aliases for complex extractors

When tuple extractors get long, a type alias keeps handler signatures
readable:

```rust,ignore
type CreateArgs = (State<Arc<Db>>, Body<NewItem>, State<AppConfig>);

async fn create(
    conn: &mut Conn,
    (State(db), Body(input), State(config)): CreateArgs,
) -> Result<(Status, Json<Item>), AppError> {
    // ...
}
```

## `Arc<T>` for shared state

When your shared state is expensive to clone, wrap it in `Arc`. The
trillium `State<T>` handler clones `T` into each conn, so using
`Arc<T>` means only the pointer is cloned:

```rust,ignore
use std::sync::Arc;
use trillium_api::State;

struct Db { /* connection pool, etc. */ }

// In app setup:
let app = (
    State(Arc::new(Db { /* ... */ })),
    router,
);

// In handlers:
async fn list(_conn: &mut Conn, State(db): State<Arc<Db>>) -> Json<Vec<Item>> {
    // db is Arc<Db> — cheap to clone, shared across requests
}
```

## `FromConn` for shared state (borrow, don't take)

[`State<T>`](crate::State) calls
[`take_state`](trillium::Conn::take_state), which *removes* the value.
If you need the state to remain available for other handlers or
extractors, implement `FromConn` with `conn.state().cloned()` instead:

```rust,ignore
impl FromConn for Db {
    async fn from_conn(conn: &mut Conn) -> Option<Self> {
        conn.state().cloned() // borrows, doesn't remove
    }
}
```

This is especially important for state that's used in multiple
extractors within the same request (e.g., a database handle used by
both a `User` extractor and the route handler itself).

## Returning `(Status, Json<T>)` for create endpoints

REST APIs commonly return `201 Created` with a body. Since `Status`
doesn't halt, and `Json<T>` does, they compose naturally:

```rust,ignore
async fn create(
    _conn: &mut Conn,
    (db, Body(input)): (Db, Body<NewItem>),
) -> Result<(Status, Json<Item>), AppError> {
    let item = db.insert(input).await?;
    Ok((Status::Created, Json(item)))
}
```

## Domain objects as extractors

Rather than parsing route parameters in every handler, implement
`TryFromConn` on your domain type to load it once from the route
param + database:

```rust,ignore
impl TryFromConn for Todo {
    type Error = Status;

    async fn try_from_conn(conn: &mut Conn) -> Result<Self, Status> {
        let db = Db::from_conn(conn).await.ok_or(Status::InternalServerError)?;
        let id: u64 = conn.param("todo_id")
            .and_then(|p| p.parse().ok())
            .ok_or(Status::BadRequest)?;
        db.find_todo(id).await.ok_or(Status::NotFound)
    }
}

// Now handlers receive a loaded Todo directly:
async fn show(_conn: &mut Conn, todo: Todo) -> Json<Todo> {
    Json(todo)
}

async fn update(
    _conn: &mut Conn,
    (todo, Body(input)): (Todo, Body<UpdateTodo>),
) -> Result<Json<Todo>, AppError> {
    // ...
}
```

## Query string extraction

There's no built-in query string extractor, but `TryFromConn` makes
it straightforward:

```rust,ignore
#[derive(Deserialize)]
struct Pagination {
    page: Option<u64>,
    per_page: Option<u64>,
}

impl TryFromConn for Pagination {
    type Error = Status;

    async fn try_from_conn(conn: &mut Conn) -> Result<Self, Status> {
        serde_urlencoded::from_str(conn.querystring())
            .map_err(|_| Status::BadRequest)
    }
}
```

## `cancel_on_disconnect` for expensive operations

[`cancel_on_disconnect`](crate::cancel_on_disconnect) is like
[`api`](crate::api), but cancels the handler future if the client
disconnects. The handler function does *not* receive `&mut Conn` —
all request data must come through extractors:

```rust,ignore
use trillium_api::cancel_on_disconnect;

async fn expensive_report(
    (db, pagination): (Db, Pagination),
) -> Result<Json<Report>, AppError> {
    // If the client hangs up, this future is dropped
    db.generate_report(pagination).await
        .map(Json)
        .map_err(AppError::from)
}

router().get("/report", cancel_on_disconnect(expensive_report))
```