pub trait TypedPath: Display {
    const PATH: &'static str;
    fn to_uri(&self) -> Uri { ... }
}
Expand description

A type safe path.

This is used to statically connect a path to its corresponding handler using RouterExt::typed_get, RouterExt::typed_post, etc.

Example

use serde::Deserialize;
use axum::{Router, extract::Json};
use axum_extra::routing::{
    TypedPath,
    RouterExt, // for `Router::typed_*`
};

// A type safe route with `/users/:id` as its associated path.
#[derive(TypedPath, Deserialize)]
#[typed_path("/users/:id")]
struct UsersMember {
    id: u32,
}

// A regular handler function that takes `UsersMember` as the first argument
// and thus creates a typed connection between this handler and the `/users/:id` path.
//
// The `TypedPath` must be the first argument to the function.
async fn users_show(
    UsersMember { id }: UsersMember,
) {
    // ...
}

let app = Router::new()
    // Add our typed route to the router.
    //
    // The path will be inferred to `/users/:id` since `users_show`'s
    // first argument is `UsersMember` which implements `TypedPath`
    .typed_get(users_show)
    .typed_post(users_create)
    .typed_delete(users_destroy);

#[derive(TypedPath)]
#[typed_path("/users")]
struct UsersCollection;

#[derive(Deserialize)]
struct UsersCreatePayload { /* ... */ }

async fn users_create(
    _: UsersCollection,
    // Our handlers can accept other extractors.
    Json(payload): Json<UsersCreatePayload>,
) {
    // ...
}

async fn users_destroy(_: UsersCollection) { /* ... */ }

Using #[derive(TypedPath)]

While TypedPath can be implemented manually, it’s highly recommended to derive it:

use serde::Deserialize;
use axum_extra::routing::TypedPath;

#[derive(TypedPath, Deserialize)]
#[typed_path("/users/:id")]
struct UsersMember {
    id: u32,
}

The macro expands to:

  • A TypedPath implementation.
  • A FromRequest implementation compatible with RouterExt::typed_get, RouterExt::typed_post, etc. This implementation uses Path and thus your struct must also implement serde::Deserialize, unless it’s a unit struct.
  • A Display implementation that interpolates the captures. This can be used to, among other things, create links to known paths and have them verified statically. Note that the Display implementation for each field must return something that’s compatible with its Deserialize implementation.

Additionally the macro will verify the captures in the path matches the fields of the struct. For example this fails to compile since the struct doesn’t have a team_id field:

use serde::Deserialize;
use axum_extra::routing::TypedPath;

#[derive(TypedPath, Deserialize)]
#[typed_path("/users/:id/teams/:team_id")]
struct UsersMember {
    id: u32,
}

Unit and tuple structs are also supported:

use serde::Deserialize;
use axum_extra::routing::TypedPath;

#[derive(TypedPath)]
#[typed_path("/users")]
struct UsersCollection;

#[derive(TypedPath, Deserialize)]
#[typed_path("/users/:id")]
struct UsersMember(u32);

Percent encoding

The generated Display implementation will automatically percent-encode the arguments:

use serde::Deserialize;
use axum_extra::routing::TypedPath;

#[derive(TypedPath, Deserialize)]
#[typed_path("/users/:id")]
struct UsersMember {
    id: String,
}

assert_eq!(
    UsersMember {
        id: "foo bar".to_string(),
    }.to_string(),
    "/users/foo%20bar",
);

Associated Constants

The path with optional captures such as /users/:id.

Provided methods

Convert the path into a Uri.

Panics

The default implementation parses the required Display implemetation. If that fails it will panic.

Using #[derive(TypedPath)] will never result in a panic since it percent-encodes arguments.

Implementors