mod de;
use crate::{
extract::{rejection::*, FromRequestParts},
routing::url_params::UrlParams,
util::PercentDecodedStr,
};
use axum_core::{
extract::OptionalFromRequestParts,
response::{IntoResponse, Response},
RequestPartsExt as _,
};
use http::{request::Parts, StatusCode};
use serde::de::DeserializeOwned;
use std::{fmt, sync::Arc};
#[derive(Debug)]
pub struct Path<T>(pub T);
axum_core::__impl_deref!(Path);
impl<T, S> FromRequestParts<S> for Path<T>
where
T: DeserializeOwned + Send,
S: Send + Sync,
{
type Rejection = PathRejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let params = match parts.extensions.get::<UrlParams>() {
Some(UrlParams::Params(params)) => params,
Some(UrlParams::InvalidUtf8InPathParam { key }) => {
let err = PathDeserializationError {
kind: ErrorKind::InvalidUtf8InPathParam {
key: key.to_string(),
},
};
let err = FailedToDeserializePathParams(err);
return Err(err.into());
}
None => {
return Err(MissingPathParams.into());
}
};
T::deserialize(de::PathDeserializer::new(params))
.map_err(|err| {
PathRejection::FailedToDeserializePathParams(FailedToDeserializePathParams(err))
})
.map(Path)
}
}
impl<T, S> OptionalFromRequestParts<S> for Path<T>
where
T: DeserializeOwned + Send + 'static,
S: Send + Sync,
{
type Rejection = PathRejection;
async fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> Result<Option<Self>, Self::Rejection> {
match parts.extract::<Self>().await {
Ok(Self(params)) => Ok(Some(Self(params))),
Err(PathRejection::FailedToDeserializePathParams(e))
if matches!(e.kind(), ErrorKind::WrongNumberOfParameters { got: 0, .. }) =>
{
Ok(None)
}
Err(e) => Err(e),
}
}
}
#[derive(Debug)]
pub(crate) struct PathDeserializationError {
pub(super) kind: ErrorKind,
}
impl PathDeserializationError {
pub(super) fn new(kind: ErrorKind) -> Self {
Self { kind }
}
pub(super) fn wrong_number_of_parameters() -> WrongNumberOfParameters<()> {
WrongNumberOfParameters { got: () }
}
#[track_caller]
pub(super) fn unsupported_type(name: &'static str) -> Self {
Self::new(ErrorKind::UnsupportedType { name })
}
}
pub(super) struct WrongNumberOfParameters<G> {
got: G,
}
impl<G> WrongNumberOfParameters<G> {
#[allow(clippy::unused_self)]
pub(super) fn got<G2>(self, got: G2) -> WrongNumberOfParameters<G2> {
WrongNumberOfParameters { got }
}
}
impl WrongNumberOfParameters<usize> {
pub(super) fn expected(self, expected: usize) -> PathDeserializationError {
PathDeserializationError::new(ErrorKind::WrongNumberOfParameters {
got: self.got,
expected,
})
}
}
impl serde::de::Error for PathDeserializationError {
#[inline]
fn custom<T>(msg: T) -> Self
where
T: fmt::Display,
{
Self {
kind: ErrorKind::Message(msg.to_string()),
}
}
}
impl fmt::Display for PathDeserializationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.kind.fmt(f)
}
}
impl std::error::Error for PathDeserializationError {}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
WrongNumberOfParameters {
got: usize,
expected: usize,
},
ParseErrorAtKey {
key: String,
value: String,
expected_type: &'static str,
},
ParseErrorAtIndex {
index: usize,
value: String,
expected_type: &'static str,
},
ParseError {
value: String,
expected_type: &'static str,
},
InvalidUtf8InPathParam {
key: String,
},
UnsupportedType {
name: &'static str,
},
DeserializeError {
key: String,
value: String,
message: String,
},
Message(String),
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorKind::Message(error) => error.fmt(f),
ErrorKind::InvalidUtf8InPathParam { key } => write!(f, "Invalid UTF-8 in `{key}`"),
ErrorKind::WrongNumberOfParameters { got, expected } => {
write!(
f,
"Wrong number of path arguments for `Path`. Expected {expected} but got {got}"
)?;
if *expected == 1 {
write!(f, ". Note that multiple parameters must be extracted with a tuple `Path<(_, _)>` or a struct `Path<YourParams>`")?;
}
Ok(())
}
ErrorKind::UnsupportedType { name } => write!(f, "Unsupported type `{name}`"),
ErrorKind::ParseErrorAtKey {
key,
value,
expected_type,
} => write!(
f,
"Cannot parse `{key}` with value `{value}` to a `{expected_type}`"
),
ErrorKind::ParseError {
value,
expected_type,
} => write!(f, "Cannot parse `{value}` to a `{expected_type}`"),
ErrorKind::ParseErrorAtIndex {
index,
value,
expected_type,
} => write!(
f,
"Cannot parse value at index {index} with value `{value}` to a `{expected_type}`"
),
ErrorKind::DeserializeError {
key,
value,
message,
} => write!(f, "Cannot parse `{key}` with value `{value}`: {message}"),
}
}
}
#[derive(Debug)]
pub struct FailedToDeserializePathParams(PathDeserializationError);
impl FailedToDeserializePathParams {
pub fn kind(&self) -> &ErrorKind {
&self.0.kind
}
pub fn into_kind(self) -> ErrorKind {
self.0.kind
}
pub fn body_text(&self) -> String {
match self.0.kind {
ErrorKind::Message(_)
| ErrorKind::DeserializeError { .. }
| ErrorKind::InvalidUtf8InPathParam { .. }
| ErrorKind::ParseError { .. }
| ErrorKind::ParseErrorAtIndex { .. }
| ErrorKind::ParseErrorAtKey { .. } => format!("Invalid URL: {}", self.0.kind),
ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => {
self.0.kind.to_string()
}
}
}
pub fn status(&self) -> StatusCode {
match self.0.kind {
ErrorKind::Message(_)
| ErrorKind::DeserializeError { .. }
| ErrorKind::InvalidUtf8InPathParam { .. }
| ErrorKind::ParseError { .. }
| ErrorKind::ParseErrorAtIndex { .. }
| ErrorKind::ParseErrorAtKey { .. } => StatusCode::BAD_REQUEST,
ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => {
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
}
impl IntoResponse for FailedToDeserializePathParams {
fn into_response(self) -> Response {
let body = self.body_text();
axum_core::__log_rejection!(
rejection_type = Self,
body_text = body,
status = self.status(),
);
(self.status(), body).into_response()
}
}
impl fmt::Display for FailedToDeserializePathParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for FailedToDeserializePathParams {}
#[derive(Debug)]
pub struct RawPathParams(Vec<(Arc<str>, PercentDecodedStr)>);
impl<S> FromRequestParts<S> for RawPathParams
where
S: Send + Sync,
{
type Rejection = RawPathParamsRejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let params = match parts.extensions.get::<UrlParams>() {
Some(UrlParams::Params(params)) => params,
Some(UrlParams::InvalidUtf8InPathParam { key }) => {
return Err(InvalidUtf8InPathParam {
key: Arc::clone(key),
}
.into());
}
None => {
return Err(MissingPathParams.into());
}
};
Ok(Self(params.clone()))
}
}
impl RawPathParams {
pub fn iter(&self) -> RawPathParamsIter<'_> {
self.into_iter()
}
}
impl<'a> IntoIterator for &'a RawPathParams {
type Item = (&'a str, &'a str);
type IntoIter = RawPathParamsIter<'a>;
fn into_iter(self) -> Self::IntoIter {
RawPathParamsIter(self.0.iter())
}
}
#[derive(Debug)]
pub struct RawPathParamsIter<'a>(std::slice::Iter<'a, (Arc<str>, PercentDecodedStr)>);
impl<'a> Iterator for RawPathParamsIter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
let (key, value) = self.0.next()?;
Some((&**key, value.as_str()))
}
}
#[derive(Debug)]
pub struct InvalidUtf8InPathParam {
key: Arc<str>,
}
impl InvalidUtf8InPathParam {
pub fn body_text(&self) -> String {
self.to_string()
}
pub fn status(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
impl fmt::Display for InvalidUtf8InPathParam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Invalid UTF-8 in `{}`", self.key)
}
}
impl std::error::Error for InvalidUtf8InPathParam {}
impl IntoResponse for InvalidUtf8InPathParam {
fn into_response(self) -> Response {
let body = self.body_text();
axum_core::__log_rejection!(
rejection_type = Self,
body_text = body,
status = self.status(),
);
(self.status(), body).into_response()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{routing::get, test_helpers::*, Router};
use serde::Deserialize;
use std::collections::HashMap;
#[crate::test]
async fn extracting_url_params() {
let app = Router::new().route(
"/users/{id}",
get(|Path(id): Path<i32>| async move {
assert_eq!(id, 42);
})
.post(|Path(params_map): Path<HashMap<String, i32>>| async move {
assert_eq!(params_map.get("id").unwrap(), &1337);
}),
);
let client = TestClient::new(app);
let res = client.get("/users/42").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.post("/users/1337").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn extracting_url_params_multiple_times() {
let app = Router::new().route("/users/{id}", get(|_: Path<i32>, _: Path<String>| async {}));
let client = TestClient::new(app);
let res = client.get("/users/42").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn percent_decoding() {
let app = Router::new().route(
"/{key}",
get(|Path(param): Path<String>| async move { param }),
);
let client = TestClient::new(app);
let res = client.get("/one%20two").await;
assert_eq!(res.text().await, "one two");
}
#[crate::test]
async fn supports_128_bit_numbers() {
let app = Router::new()
.route(
"/i/{key}",
get(|Path(param): Path<i128>| async move { param.to_string() }),
)
.route(
"/u/{key}",
get(|Path(param): Path<u128>| async move { param.to_string() }),
);
let client = TestClient::new(app);
let res = client.get("/i/123").await;
assert_eq!(res.text().await, "123");
let res = client.get("/u/123").await;
assert_eq!(res.text().await, "123");
}
#[crate::test]
async fn wildcard() {
let app = Router::new()
.route(
"/foo/{*rest}",
get(|Path(param): Path<String>| async move { param }),
)
.route(
"/bar/{*rest}",
get(|Path(params): Path<HashMap<String, String>>| async move {
params.get("rest").unwrap().clone()
}),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").await;
assert_eq!(res.text().await, "bar/baz");
let res = client.get("/bar/baz/qux").await;
assert_eq!(res.text().await, "baz/qux");
}
#[crate::test]
async fn captures_dont_match_empty_path() {
let app = Router::new().route("/{key}", get(|| async {}));
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn captures_match_empty_inner_segments() {
let app = Router::new().route(
"/{key}/method",
get(|Path(param): Path<String>| async move { param.to_string() }),
);
let client = TestClient::new(app);
let res = client.get("/abc/method").await;
assert_eq!(res.text().await, "abc");
let res = client.get("//method").await;
assert_eq!(res.text().await, "");
}
#[crate::test]
async fn captures_match_empty_inner_segments_near_end() {
let app = Router::new().route(
"/method/{key}/",
get(|Path(param): Path<String>| async move { param.to_string() }),
);
let client = TestClient::new(app);
let res = client.get("/method/abc").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/method/abc/").await;
assert_eq!(res.text().await, "abc");
let res = client.get("/method//").await;
assert_eq!(res.text().await, "");
}
#[crate::test]
async fn captures_match_empty_trailing_segment() {
let app = Router::new().route(
"/method/{key}",
get(|Path(param): Path<String>| async move { param.to_string() }),
);
let client = TestClient::new(app);
let res = client.get("/method/abc/").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/method/abc").await;
assert_eq!(res.text().await, "abc");
let res = client.get("/method/").await;
assert_eq!(res.text().await, "");
let res = client.get("/method").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[crate::test]
async fn str_reference_deserialize() {
struct Param(String);
impl<'de> serde::Deserialize<'de> for Param {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <&str as serde::Deserialize>::deserialize(deserializer)?;
Ok(Param(s.to_owned()))
}
}
let app = Router::new().route(
"/{key}",
get(|param: Path<Param>| async move { param.0 .0 }),
);
let client = TestClient::new(app);
let res = client.get("/foo").await;
assert_eq!(res.text().await, "foo");
let res = client.get("/foo%20bar").await;
assert_eq!(res.text().await, "foo bar");
}
#[crate::test]
async fn two_path_extractors() {
let app = Router::new().route("/{a}/{b}", get(|_: Path<String>, _: Path<String>| async {}));
let client = TestClient::new(app);
let res = client.get("/a/b").await;
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(
res.text().await,
"Wrong number of path arguments for `Path`. Expected 1 but got 2. \
Note that multiple parameters must be extracted with a tuple `Path<(_, _)>` or a struct `Path<YourParams>`",
);
}
#[crate::test]
async fn tuple_param_matches_exactly() {
#[allow(dead_code)]
#[derive(Deserialize)]
struct Tuple(String, String);
let app = Router::new()
.route(
"/foo/{a}/{b}/{c}",
get(|_: Path<(String, String)>| async {}),
)
.route("/bar/{a}/{b}/{c}", get(|_: Path<Tuple>| async {}));
let client = TestClient::new(app);
let res = client.get("/foo/a/b/c").await;
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(
res.text().await,
"Wrong number of path arguments for `Path`. Expected 2 but got 3",
);
let res = client.get("/bar/a/b/c").await;
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(
res.text().await,
"Wrong number of path arguments for `Path`. Expected 2 but got 3",
);
}
#[crate::test]
async fn deserialize_into_vec_of_tuples() {
let app = Router::new().route(
"/{a}/{b}",
get(|Path(params): Path<Vec<(String, String)>>| async move {
assert_eq!(
params,
vec![
("a".to_owned(), "foo".to_owned()),
("b".to_owned(), "bar".to_owned())
]
);
}),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn type_that_uses_deserialize_any() {
use time::Date;
#[derive(Deserialize)]
struct Params {
a: Date,
b: Date,
c: Date,
}
let app = Router::new()
.route(
"/single/{a}",
get(|Path(a): Path<Date>| async move { format!("single: {a}") }),
)
.route(
"/tuple/{a}/{b}/{c}",
get(|Path((a, b, c)): Path<(Date, Date, Date)>| async move {
format!("tuple: {a} {b} {c}")
}),
)
.route(
"/vec/{a}/{b}/{c}",
get(|Path(vec): Path<Vec<Date>>| async move {
let [a, b, c]: [Date; 3] = vec.try_into().unwrap();
format!("vec: {a} {b} {c}")
}),
)
.route(
"/vec_pairs/{a}/{b}/{c}",
get(|Path(vec): Path<Vec<(String, Date)>>| async move {
let [(_, a), (_, b), (_, c)]: [(String, Date); 3] = vec.try_into().unwrap();
format!("vec_pairs: {a} {b} {c}")
}),
)
.route(
"/map/{a}/{b}/{c}",
get(|Path(mut map): Path<HashMap<String, Date>>| async move {
let a = map.remove("a").unwrap();
let b = map.remove("b").unwrap();
let c = map.remove("c").unwrap();
format!("map: {a} {b} {c}")
}),
)
.route(
"/struct/{a}/{b}/{c}",
get(|Path(params): Path<Params>| async move {
format!("struct: {} {} {}", params.a, params.b, params.c)
}),
);
let client = TestClient::new(app);
let res = client.get("/single/2023-01-01").await;
assert_eq!(res.text().await, "single: 2023-01-01");
let res = client.get("/tuple/2023-01-01/2023-01-02/2023-01-03").await;
assert_eq!(res.text().await, "tuple: 2023-01-01 2023-01-02 2023-01-03");
let res = client.get("/vec/2023-01-01/2023-01-02/2023-01-03").await;
assert_eq!(res.text().await, "vec: 2023-01-01 2023-01-02 2023-01-03");
let res = client
.get("/vec_pairs/2023-01-01/2023-01-02/2023-01-03")
.await;
assert_eq!(
res.text().await,
"vec_pairs: 2023-01-01 2023-01-02 2023-01-03",
);
let res = client.get("/map/2023-01-01/2023-01-02/2023-01-03").await;
assert_eq!(res.text().await, "map: 2023-01-01 2023-01-02 2023-01-03");
let res = client.get("/struct/2023-01-01/2023-01-02/2023-01-03").await;
assert_eq!(res.text().await, "struct: 2023-01-01 2023-01-02 2023-01-03");
}
#[crate::test]
async fn wrong_number_of_parameters_json() {
use serde_json::Value;
let app = Router::new()
.route("/one/{a}", get(|_: Path<(Value, Value)>| async {}))
.route("/two/{a}/{b}", get(|_: Path<Value>| async {}));
let client = TestClient::new(app);
let res = client.get("/one/1").await;
assert!(res
.text()
.await
.starts_with("Wrong number of path arguments for `Path`. Expected 2 but got 1"));
let res = client.get("/two/1/2").await;
assert!(res
.text()
.await
.starts_with("Wrong number of path arguments for `Path`. Expected 1 but got 2"));
}
#[crate::test]
async fn raw_path_params() {
let app = Router::new().route(
"/{a}/{b}/{c}",
get(|params: RawPathParams| async move {
params
.into_iter()
.map(|(key, value)| format!("{key}={value}"))
.collect::<Vec<_>>()
.join(" ")
}),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").await;
let body = res.text().await;
assert_eq!(body, "a=foo b=bar c=baz");
}
#[crate::test]
async fn deserialize_error_single_value() {
let app = Router::new().route(
"/resources/{res}",
get(|res: Path<uuid::Uuid>| async move {
let _res = res;
}),
);
let client = TestClient::new(app);
let res = client.get("/resources/123123-123-123123").await;
let body = res.text().await;
assert_eq!(
body,
"Invalid URL: Cannot parse `res` with value `123123-123-123123`: UUID parsing failed: invalid group count: expected 5, found 3"
);
}
#[crate::test]
async fn deserialize_error_multi_value() {
let app = Router::new().route(
"/resources/{res}/sub/{sub}",
get(
|Path((res, sub)): Path<(uuid::Uuid, uuid::Uuid)>| async move {
let _res = res;
let _sub = sub;
},
),
);
let client = TestClient::new(app);
let res = client.get("/resources/456456-123-456456/sub/123").await;
let body = res.text().await;
assert_eq!(
body,
"Invalid URL: Cannot parse `res` with value `456456-123-456456`: UUID parsing failed: invalid group count: expected 5, found 3"
);
}
#[crate::test]
async fn regression_3038() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct MoreChars {
first_two: [char; 2],
second_two: [char; 2],
crate_name: String,
}
let app = Router::new().route(
"/{first_two}/{second_two}/{crate_name}",
get(|Path(_): Path<MoreChars>| async move {}),
);
let client = TestClient::new(app);
let res = client.get("/te/st/_thing").await;
let body = res.text().await;
assert_eq!(body, "Invalid URL: array types are not supported");
}
}