pub trait TypedPath: Display {
const PATH: &'static str;
// Provided methods
fn to_uri(&self) -> Uri { ... }
fn with_query_params<T>(self, params: T) -> WithQueryParams<Self, T>
where T: Serialize,
Self: Sized { ... }
}
typed-routing
only.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 withRouterExt::typed_get
,RouterExt::typed_post
, etc. This implementation usesPath
and thus your struct must also implementserde::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 theDisplay
implementation for each field must return something that’s compatible with itsDeserialize
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",
);
§Customizing the rejection
By default the rejection used in the FromRequest
implementation will be PathRejection
.
That can be customized using #[typed_path("...", rejection(YourType))]
:
use serde::Deserialize;
use axum_extra::routing::TypedPath;
use axum::{
response::{IntoResponse, Response},
extract::rejection::PathRejection,
};
#[derive(TypedPath, Deserialize)]
#[typed_path("/users/:id", rejection(UsersMemberRejection))]
struct UsersMember {
id: String,
}
struct UsersMemberRejection;
// Your rejection type must implement `From<PathRejection>`.
//
// Here you can grab whatever details from the inner rejection
// that you need.
impl From<PathRejection> for UsersMemberRejection {
fn from(rejection: PathRejection) -> Self {
// ...
}
}
// Your rejection must implement `IntoResponse`, like all rejections.
impl IntoResponse for UsersMemberRejection {
fn into_response(self) -> Response {
// ...
}
}
The From<PathRejection>
requirement only applies if your typed path is a struct with named
fields or a tuple struct. For unit structs your rejection type must implement Default
:
use axum_extra::routing::TypedPath;
use axum::response::{IntoResponse, Response};
#[derive(TypedPath)]
#[typed_path("/users", rejection(UsersCollectionRejection))]
struct UsersCollection;
#[derive(Default)]
struct UsersCollectionRejection;
impl IntoResponse for UsersCollectionRejection {
fn into_response(self) -> Response {
// ...
}
}
Required Associated Constants§
Provided Methods§
Sourcefn with_query_params<T>(self, params: T) -> WithQueryParams<Self, T>
fn with_query_params<T>(self, params: T) -> WithQueryParams<Self, T>
Add query parameters to a path.
§Example
use axum_extra::routing::TypedPath;
use serde::Serialize;
#[derive(TypedPath)]
#[typed_path("/users")]
struct Users;
#[derive(Serialize)]
struct Pagination {
page: u32,
per_page: u32,
}
let path = Users.with_query_params(Pagination {
page: 1,
per_page: 10,
});
assert_eq!(path.to_uri(), "/users?&page=1&per_page=10");
§Panics
If params
doesn’t support being serialized as query params WithQueryParams
’s Display
implementation will panic, and thus WithQueryParams::to_uri
will also panic.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.