Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion axum/src/docs/routing/route.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ async fn handler(Path(path): Path<String>) -> String {
Note that the leading slash is not included, i.e. for the route `/foo/{*rest}` and
the path `/foo/bar/baz` the value of `rest` will be `bar/baz`.

The captured segments can also be extracted as a sequence:

```rust
use axum::{
Router,
routing::get,
extract::Path,
};

let app: Router = Router::new().route("/files/{*path}", get(handler));

async fn handler(Path(segments): Path<Vec<String>>) -> String {
segments.join(", ")
}
```
For the path `/files/foo/bar/baz`, `segments` will be `["foo", "bar", "baz"]`.

# Accepting multiple methods

To accept multiple methods for the same route you can add all handlers at the
Expand Down Expand Up @@ -121,7 +138,8 @@ let app = Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/{id}", get(show_user))
.route("/api/{version}/users/{id}/action", delete(do_users_action))
.route("/assets/{*path}", get(serve_asset));
.route("/assets/{*path}", get(serve_asset))
.route("/batch/{*ids}", get(batch_process));

async fn root() {}

Expand All @@ -134,6 +152,8 @@ async fn show_user(Path(id): Path<u64>) {}
async fn do_users_action(Path((version, id)): Path<(String, u64)>) {}

async fn serve_asset(Path(path): Path<String>) {}

async fn batch_process(Path(ids): Path<Vec<u64>>) {}
# let _: Router = app;
```

Expand Down
223 changes: 218 additions & 5 deletions axum/src/extract/path/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde_core::{
de::{self, DeserializeSeed, EnumAccess, Error, MapAccess, SeqAccess, VariantAccess, Visitor},
forward_to_deserialize_any, Deserializer,
};
use std::{any::type_name, sync::Arc};
use std::{any::type_name, str::Split, sync::Arc};

macro_rules! unsupported_type {
($trait_fn:ident) => {
Expand Down Expand Up @@ -144,6 +144,12 @@ impl<'de> Deserializer<'de> for PathDeserializer<'de> {
where
V: Visitor<'de>,
{
if let [(_, s)] = self.url_params {
return visitor.visit_seq(ValueSeqDeserializer {
split: s.split('/'),
});
}

visitor.visit_seq(SeqDeserializer {
params: self.url_params,
idx: 0,
Expand Down Expand Up @@ -486,13 +492,13 @@ impl<'de> Deserializer<'de> for ValueDeserializer<'de> {
}
}

fn deserialize_seq<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::unsupported_type(type_name::<
V::Value,
>()))
visitor.visit_seq(ValueSeqDeserializer {
split: self.value.split('/'),
})
}

fn deserialize_tuple_struct<V>(
Expand Down Expand Up @@ -630,6 +636,124 @@ impl<'de> SeqAccess<'de> for SeqDeserializer<'de> {
}
}

macro_rules! parse_raw_str {
($trait_fn:ident, $visit_fn:ident, $ty:literal) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let v = self.value.parse().map_err(|_| {
PathDeserializationError::new(ErrorKind::ParseError {
value: self.value.to_owned(),
expected_type: $ty,
})
})?;
visitor.$visit_fn(v)
}
};
}

#[derive(Debug)]
struct RawStrValueDeserializer<'de> {
value: &'de str,
}

impl<'de> Deserializer<'de> for RawStrValueDeserializer<'de> {
type Error = PathDeserializationError;

parse_raw_str!(deserialize_bool, visit_bool, "bool");
parse_raw_str!(deserialize_i8, visit_i8, "i8");
parse_raw_str!(deserialize_i16, visit_i16, "i16");
parse_raw_str!(deserialize_i32, visit_i32, "i32");
parse_raw_str!(deserialize_i64, visit_i64, "i64");
parse_raw_str!(deserialize_i128, visit_i128, "i128");
parse_raw_str!(deserialize_u8, visit_u8, "u8");
parse_raw_str!(deserialize_u16, visit_u16, "u16");
parse_raw_str!(deserialize_u32, visit_u32, "u32");
parse_raw_str!(deserialize_u64, visit_u64, "u64");
parse_raw_str!(deserialize_u128, visit_u128, "u128");
parse_raw_str!(deserialize_f32, visit_f32, "f32");
parse_raw_str!(deserialize_f64, visit_f64, "f64");
parse_raw_str!(deserialize_string, visit_string, "String");
parse_raw_str!(deserialize_byte_buf, visit_string, "String");
parse_raw_str!(deserialize_char, visit_char, "char");

fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_borrowed_str(self.value)
}

fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_borrowed_bytes(self.value.as_bytes())
}

fn deserialize_newtype_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}

fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_enum(EnumDeserializer { value: self.value })
}

fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::unsupported_type(type_name::<
V::Value,
>()))
}

forward_to_deserialize_any! {
option unit unit_struct seq tuple identifier
tuple_struct map struct ignored_any
}
}

struct ValueSeqDeserializer<'de> {
split: Split<'de, char>,
}

impl<'de> SeqAccess<'de> for ValueSeqDeserializer<'de> {
type Error = PathDeserializationError;

fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where
T: DeserializeSeed<'de>,
{
for s in self.split.by_ref() {
// skip empty segments from trailing or consecutive slashes
if !s.is_empty() {
return Ok(Some(
seed.deserialize(RawStrValueDeserializer { value: s })?,
));
}
}

Ok(None)
}
}

#[derive(Debug, Clone)]
enum KeyOrIdx<'de> {
Key(&'de str),
Expand Down Expand Up @@ -821,6 +945,63 @@ mod tests {
);
}

#[test]
fn test_parse_seq_wildcard() {
let url_params = create_url_params(vec![("a", "x/y/z")]);
assert_eq!(
<Vec<String>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
["x".to_owned(), "y".to_owned(), "z".to_owned()]
);

let url_params = create_url_params(vec![("a", "1/-2/3")]);
assert_eq!(
<Vec<i32>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
[1, -2, 3]
);
}

#[test]
fn test_parse_seq_wildcard_empty() {
let url_params = create_url_params(vec![("a", "x")]);
assert_eq!(
<Vec<String>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
["x".to_owned()]
);

let url_params = create_url_params(vec![("a", "x/")]);
assert_eq!(
<Vec<String>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
["x".to_owned()]
);

let url_params = create_url_params(vec![("a", "x///y")]);
assert_eq!(
<Vec<String>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
["x".to_owned(), "y".to_owned()]
);
}

#[test]
fn test_parse_seq_wildcard_multiple_segments() {
let url_params = create_url_params(vec![("a", "test"), ("b", "x")]);
assert_eq!(
<(String, Vec<String>)>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
("test".to_owned(), vec!["x".to_owned()])
);

let url_params = create_url_params(vec![("a", "test"), ("b", "x/")]);
assert_eq!(
<(String, Vec<String>)>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
("test".to_owned(), vec!["x".to_owned()])
);

let url_params = create_url_params(vec![("a", "test"), ("b", "x/y")]);
assert_eq!(
<(String, Vec<String>)>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
("test".to_owned(), vec!["x".to_owned(), "y".to_owned()])
);
}

macro_rules! test_parse_error {
(
$params:expr,
Expand Down Expand Up @@ -937,6 +1118,14 @@ mod tests {
test_parse_error!(
vec![("a", "false")],
Vec<(u32, String)>,
ErrorKind::UnsupportedType {
name: "(u32, alloc::string::String)"
}
);

test_parse_error!(
vec![("a", "false"), ("b", "true")],
Vec<(u32, String)>,
ErrorKind::Message("Unexpected key type".to_owned())
);
}
Expand Down Expand Up @@ -975,4 +1164,28 @@ mod tests {
}
);
}

#[test]
fn test_parse_seq_wildcard_error() {
test_parse_error!(
vec![("a", "1/notanumber/3")],
Vec<i32>,
ErrorKind::ParseError {
value: "notanumber".to_owned(),
expected_type: "i32",
}
);
}

#[test]
fn test_parse_seq_wildcard_tuple_error() {
test_parse_error!(
vec![("a", "test"), ("b", "x/y")],
(String, Vec<i32>),
ErrorKind::ParseError {
value: "x".to_owned(),
expected_type: "i32",
}
);
}
}
Loading