Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Extension

The Extension<T> extractor reads values from the request’s extension map. Extensions are inserted by middleware or by the serve_with() modifier closure.

When to Use Extensions

  • Per-request values that aren’t known at router setup time
  • Authenticated user info set by auth middleware
  • Request IDs set by RequestIdLayer
  • Tracing correlation IDs
  • Any value that is computed per-request and consumed downstream

Do not use extensions for global application state. Use State<T> instead.

Extracting an Extension

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
struct CurrentUser {
    id: u64,
    name: String,
}

async fn profile(Extension(user): Extension<CurrentUser>) -> String {
    format!("Welcome, {}!", user.name)
}
}

Setting Extensions via Middleware

Middleware can insert extensions before the handler runs:

#![allow(unused)]
fn main() {
use std::task::{Context, Poll};
use tower::{Layer, Service};
use volter::{Request, Response};

#[derive(Clone)]
struct AuthLayer;

impl<S> Layer<S> for AuthLayer {
    type Service = AuthService<S>;
    fn layer(&self, inner: S) -> Self::Service {
        AuthService { inner }
    }
}

#[derive(Clone)]
struct AuthService<S> {
    inner: S,
}

impl<S> Service<Request> for AuthService<S>
where
    S: Service<Request, Response = Response> + Clone + Send + 'static,
    S::Future: Send,
    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    type Response = Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Request) -> Self::Future {
        let mut req = req;
        req.extensions_mut().insert(CurrentUser { id: 1, name: "Alice".into() });
        self.inner.call(req)
    }
}
}

Setting Extensions via serve_with

The serve_with() function accepts a closure that modifies each request before dispatch:

#![allow(unused)]
fn main() {
use volter::*;
use std::sync::atomic::{AtomicU64, Ordering};

let counter = AtomicU64::new(0);

let app = Router::new()
    .route("/", get(handler));

let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;

serve_with(listener, app, move |req: &mut Request| {
    let n = counter.fetch_add(1, Ordering::SeqCst);
    req.extensions_mut().insert(RequestId(n));
})
.await
}

Rejection

#![allow(unused)]
fn main() {
pub enum ExtensionRejection {
    MissingExtension(&'static str),   // → 500 Internal Server Error
}
}

If a handler requests Extension<T> but no middleware inserted a value of type T, the rejection returns 500 Internal Server Error with a message indicating the missing type.

Built-in Extension: RequestId

The RequestIdLayer inserts a unique RequestId into every request:

#![allow(unused)]
fn main() {
use volter::*;

let app = Router::new()
    .route("/", get(handler))
    .layer(RequestIdLayer::new());

async fn handler(Extension(id): Extension<RequestId>) -> String {
    format!("Request ID: {id}")
}
}