2021-08-06 16:17:57 +08:00
|
|
|
mod de;
|
|
|
|
|
|
|
|
|
|
use super::{rejection::*, FromRequest};
|
|
|
|
|
use crate::{extract::RequestParts, routing::UrlParams};
|
|
|
|
|
use async_trait::async_trait;
|
|
|
|
|
use serde::de::DeserializeOwned;
|
|
|
|
|
use std::ops::{Deref, DerefMut};
|
|
|
|
|
|
2021-08-21 14:21:31 +02:00
|
|
|
/// Extractor that will get captures from the URL and parse them using
|
|
|
|
|
/// [`serde`].
|
2021-08-06 16:17:57 +08:00
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```rust,no_run
|
2021-08-18 00:04:15 +02:00
|
|
|
/// use axum::{
|
|
|
|
|
/// extract::Path,
|
|
|
|
|
/// handler::get,
|
2021-08-19 22:37:48 +02:00
|
|
|
/// Router,
|
2021-08-18 00:04:15 +02:00
|
|
|
/// };
|
2021-08-06 16:17:57 +08:00
|
|
|
/// use uuid::Uuid;
|
|
|
|
|
///
|
|
|
|
|
/// async fn users_teams_show(
|
|
|
|
|
/// Path((user_id, team_id)): Path<(Uuid, Uuid)>,
|
|
|
|
|
/// ) {
|
|
|
|
|
/// // ...
|
|
|
|
|
/// }
|
|
|
|
|
///
|
2021-08-19 22:37:48 +02:00
|
|
|
/// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
|
2021-08-06 16:17:57 +08:00
|
|
|
/// # async {
|
|
|
|
|
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
|
|
|
/// # };
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// If the path contains only one parameter, then you can omit the tuple.
|
|
|
|
|
///
|
|
|
|
|
/// ```rust,no_run
|
2021-08-18 00:04:15 +02:00
|
|
|
/// use axum::{
|
|
|
|
|
/// extract::Path,
|
|
|
|
|
/// handler::get,
|
2021-08-19 22:37:48 +02:00
|
|
|
/// Router,
|
2021-08-18 00:04:15 +02:00
|
|
|
/// };
|
2021-08-06 16:17:57 +08:00
|
|
|
/// use uuid::Uuid;
|
|
|
|
|
///
|
|
|
|
|
/// async fn user_info(Path(user_id): Path<Uuid>) {
|
|
|
|
|
/// // ...
|
|
|
|
|
/// }
|
|
|
|
|
///
|
2021-08-19 22:37:48 +02:00
|
|
|
/// let app = Router::new().route("/users/:user_id", get(user_info));
|
2021-08-06 16:17:57 +08:00
|
|
|
/// # async {
|
|
|
|
|
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
|
|
|
/// # };
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
2021-08-21 14:21:31 +02:00
|
|
|
/// Path segments also can be deserialized into any type that implements
|
|
|
|
|
/// [`serde::Deserialize`]. Path segment labels will be matched with struct
|
|
|
|
|
/// field names.
|
2021-08-06 16:17:57 +08:00
|
|
|
///
|
|
|
|
|
/// ```rust,no_run
|
2021-08-18 00:04:15 +02:00
|
|
|
/// use axum::{
|
|
|
|
|
/// extract::Path,
|
|
|
|
|
/// handler::get,
|
2021-08-19 22:37:48 +02:00
|
|
|
/// Router,
|
2021-08-18 00:04:15 +02:00
|
|
|
/// };
|
2021-08-06 16:17:57 +08:00
|
|
|
/// use serde::Deserialize;
|
|
|
|
|
/// use uuid::Uuid;
|
|
|
|
|
///
|
|
|
|
|
/// #[derive(Deserialize)]
|
|
|
|
|
/// struct Params {
|
|
|
|
|
/// user_id: Uuid,
|
|
|
|
|
/// team_id: Uuid,
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// async fn users_teams_show(
|
|
|
|
|
/// Path(Params { user_id, team_id }): Path<Params>,
|
|
|
|
|
/// ) {
|
|
|
|
|
/// // ...
|
|
|
|
|
/// }
|
|
|
|
|
///
|
2021-08-19 22:37:48 +02:00
|
|
|
/// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
|
2021-08-06 16:17:57 +08:00
|
|
|
/// # async {
|
|
|
|
|
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
|
|
|
/// # };
|
|
|
|
|
/// ```
|
2021-08-21 14:21:31 +02:00
|
|
|
///
|
|
|
|
|
/// If you wish to capture all path parameters you can use `HashMap` or `Vec`:
|
|
|
|
|
///
|
|
|
|
|
/// ```rust,no_run
|
|
|
|
|
/// use axum::{
|
|
|
|
|
/// extract::Path,
|
|
|
|
|
/// handler::get,
|
|
|
|
|
/// Router,
|
|
|
|
|
/// };
|
|
|
|
|
/// use std::collections::HashMap;
|
|
|
|
|
///
|
|
|
|
|
/// async fn params_map(
|
|
|
|
|
/// Path(params): Path<HashMap<String, String>>,
|
|
|
|
|
/// ) {
|
|
|
|
|
/// // ...
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// async fn params_vec(
|
|
|
|
|
/// Path(params): Path<Vec<(String, String)>>,
|
|
|
|
|
/// ) {
|
|
|
|
|
/// // ...
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// let app = Router::new()
|
|
|
|
|
/// .route("/users/:user_id/team/:team_id", get(params_map).post(params_vec));
|
|
|
|
|
/// # async {
|
|
|
|
|
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
|
|
|
/// # };
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// [`serde`]: https://crates.io/crates/serde
|
|
|
|
|
/// [`serde::Deserialize`]: https://docs.rs/serde/1.0.127/serde/trait.Deserialize.html
|
2021-08-06 16:17:57 +08:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct Path<T>(pub T);
|
|
|
|
|
|
|
|
|
|
impl<T> Deref for Path<T> {
|
|
|
|
|
type Target = T;
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
&self.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> DerefMut for Path<T> {
|
|
|
|
|
#[inline]
|
|
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
|
&mut self.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl<T, B> FromRequest<B> for Path<T>
|
|
|
|
|
where
|
|
|
|
|
T: DeserializeOwned + Send,
|
|
|
|
|
B: Send,
|
|
|
|
|
{
|
|
|
|
|
type Rejection = PathParamsRejection;
|
|
|
|
|
|
|
|
|
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
|
|
|
|
const EMPTY_URL_PARAMS: &UrlParams = &UrlParams(Vec::new());
|
|
|
|
|
|
|
|
|
|
let url_params = if let Some(params) = req
|
|
|
|
|
.extensions_mut()
|
|
|
|
|
.and_then(|ext| ext.get::<Option<UrlParams>>())
|
|
|
|
|
{
|
|
|
|
|
params.as_ref().unwrap_or(EMPTY_URL_PARAMS)
|
|
|
|
|
} else {
|
|
|
|
|
return Err(MissingRouteParams.into());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
T::deserialize(de::PathDeserializer::new(url_params))
|
|
|
|
|
.map_err(|err| PathParamsRejection::InvalidPathParam(InvalidPathParam::new(err.0)))
|
|
|
|
|
.map(Path)
|
|
|
|
|
}
|
|
|
|
|
}
|