mirror of
https://github.com/tokio-rs/axum.git
synced 2024-12-12 09:50:45 +01:00
chore: Upgrade matchit to 0.8 (#2645)
This commit is contained in:
parent
5db62e8452
commit
6318b57fda
46 changed files with 414 additions and 208 deletions
|
@ -28,7 +28,7 @@ use serde::de::DeserializeOwned;
|
||||||
///
|
///
|
||||||
/// let app = Router::new()
|
/// let app = Router::new()
|
||||||
/// .route("/blog", get(render_blog))
|
/// .route("/blog", get(render_blog))
|
||||||
/// .route("/blog/:page", get(render_blog));
|
/// .route("/blog/{page}", get(render_blog));
|
||||||
/// # let app: Router = app;
|
/// # let app: Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -75,7 +75,7 @@ mod tests {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(handle))
|
.route("/", get(handle))
|
||||||
.route("/:num", get(handle));
|
.route("/{num}", get(handle));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ pub trait HandlerCallWithExtractors<T, S>: Sized {
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route(
|
/// let app = Router::new().route(
|
||||||
/// "/users/:id",
|
/// "/users/{id}",
|
||||||
/// get(
|
/// get(
|
||||||
/// // first try `admin`, if that rejects run `user`, finally falling back
|
/// // first try `admin`, if that rejects run `user`, finally falling back
|
||||||
/// // to `guest`
|
/// // to `guest`
|
||||||
|
|
|
@ -134,7 +134,7 @@ mod tests {
|
||||||
"fallback"
|
"fallback"
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new().route("/:id", get(one.or(two).or(three)));
|
let app = Router::new().route("/{id}", get(one.or(two).or(three)));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ use prost::Message;
|
||||||
/// # unimplemented!()
|
/// # unimplemented!()
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route("/users/:id", get(get_user));
|
/// let app = Router::new().route("/users/{id}", get(get_user));
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
|
|
@ -371,11 +371,11 @@ mod tests {
|
||||||
async fn tsr_with_params() {
|
async fn tsr_with_params() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route_with_tsr(
|
.route_with_tsr(
|
||||||
"/a/:a",
|
"/a/{a}",
|
||||||
get(|Path(param): Path<String>| async move { param }),
|
get(|Path(param): Path<String>| async move { param }),
|
||||||
)
|
)
|
||||||
.route_with_tsr(
|
.route_with_tsr(
|
||||||
"/b/:b/",
|
"/b/{b}/",
|
||||||
get(|Path(param): Path<String>| async move { param }),
|
get(|Path(param): Path<String>| async move { param }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@ use axum::{
|
||||||
/// .create(|| async {})
|
/// .create(|| async {})
|
||||||
/// // `GET /users/new`
|
/// // `GET /users/new`
|
||||||
/// .new(|| async {})
|
/// .new(|| async {})
|
||||||
/// // `GET /users/:users_id`
|
/// // `GET /users/{users_id}`
|
||||||
/// .show(|Path(user_id): Path<u64>| async {})
|
/// .show(|Path(user_id): Path<u64>| async {})
|
||||||
/// // `GET /users/:users_id/edit`
|
/// // `GET /users/{users_id}/edit`
|
||||||
/// .edit(|Path(user_id): Path<u64>| async {})
|
/// .edit(|Path(user_id): Path<u64>| async {})
|
||||||
/// // `PUT or PATCH /users/:users_id`
|
/// // `PUT or PATCH /users/{users_id}`
|
||||||
/// .update(|Path(user_id): Path<u64>| async {})
|
/// .update(|Path(user_id): Path<u64>| async {})
|
||||||
/// // `DELETE /users/:users_id`
|
/// // `DELETE /users/{users_id}`
|
||||||
/// .destroy(|Path(user_id): Path<u64>| async {});
|
/// .destroy(|Path(user_id): Path<u64>| async {});
|
||||||
///
|
///
|
||||||
/// let app = Router::new().merge(users);
|
/// let app = Router::new().merge(users);
|
||||||
|
@ -82,7 +82,9 @@ where
|
||||||
self.route(&path, get(handler))
|
self.route(&path, get(handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a handler at `GET /{resource_name}/:{resource_name}_id`.
|
/// Add a handler at `GET /<resource_name>/{<resource_name>_id}`.
|
||||||
|
///
|
||||||
|
/// For example when the resources are posts: `GET /post/{post_id}`.
|
||||||
pub fn show<H, T>(self, handler: H) -> Self
|
pub fn show<H, T>(self, handler: H) -> Self
|
||||||
where
|
where
|
||||||
H: Handler<T, S>,
|
H: Handler<T, S>,
|
||||||
|
@ -92,17 +94,21 @@ where
|
||||||
self.route(&path, get(handler))
|
self.route(&path, get(handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a handler at `GET /{resource_name}/:{resource_name}_id/edit`.
|
/// Add a handler at `GET /<resource_name>/{<resource_name>_id}/edit`.
|
||||||
|
///
|
||||||
|
/// For example when the resources are posts: `GET /post/{post_id}/edit`.
|
||||||
pub fn edit<H, T>(self, handler: H) -> Self
|
pub fn edit<H, T>(self, handler: H) -> Self
|
||||||
where
|
where
|
||||||
H: Handler<T, S>,
|
H: Handler<T, S>,
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
let path = format!("/{0}/:{0}_id/edit", self.name);
|
let path = format!("/{0}/{{{0}_id}}/edit", self.name);
|
||||||
self.route(&path, get(handler))
|
self.route(&path, get(handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a handler at `PUT or PATCH /resource_name/:{resource_name}_id`.
|
/// Add a handler at `PUT or PATCH /<resource_name>/{<resource_name>_id}`.
|
||||||
|
///
|
||||||
|
/// For example when the resources are posts: `PUT /post/{post_id}`.
|
||||||
pub fn update<H, T>(self, handler: H) -> Self
|
pub fn update<H, T>(self, handler: H) -> Self
|
||||||
where
|
where
|
||||||
H: Handler<T, S>,
|
H: Handler<T, S>,
|
||||||
|
@ -115,7 +121,9 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a handler at `DELETE /{resource_name}/:{resource_name}_id`.
|
/// Add a handler at `DELETE /<resource_name>/{<resource_name>_id}`.
|
||||||
|
///
|
||||||
|
/// For example when the resources are posts: `DELETE /post/{post_id}`.
|
||||||
pub fn destroy<H, T>(self, handler: H) -> Self
|
pub fn destroy<H, T>(self, handler: H) -> Self
|
||||||
where
|
where
|
||||||
H: Handler<T, S>,
|
H: Handler<T, S>,
|
||||||
|
@ -130,7 +138,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_update_destroy_path(&self) -> String {
|
fn show_update_destroy_path(&self) -> String {
|
||||||
format!("/{0}/:{0}_id", self.name)
|
format!("/{0}/{{{0}_id}}", self.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route(mut self, path: &str, method_router: MethodRouter<S>) -> Self {
|
fn route(mut self, path: &str, method_router: MethodRouter<S>) -> Self {
|
||||||
|
|
|
@ -19,15 +19,15 @@ use serde::Serialize;
|
||||||
/// RouterExt, // for `Router::typed_*`
|
/// RouterExt, // for `Router::typed_*`
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// // A type safe route with `/users/:id` as its associated path.
|
/// // A type safe route with `/users/{id}` as its associated path.
|
||||||
/// #[derive(TypedPath, Deserialize)]
|
/// #[derive(TypedPath, Deserialize)]
|
||||||
/// #[typed_path("/users/:id")]
|
/// #[typed_path("/users/{id}")]
|
||||||
/// struct UsersMember {
|
/// struct UsersMember {
|
||||||
/// id: u32,
|
/// id: u32,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // A regular handler function that takes `UsersMember` as the first argument
|
/// // 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.
|
/// // and thus creates a typed connection between this handler and the `/users/{id}` path.
|
||||||
/// //
|
/// //
|
||||||
/// // The `TypedPath` must be the first argument to the function.
|
/// // The `TypedPath` must be the first argument to the function.
|
||||||
/// async fn users_show(
|
/// async fn users_show(
|
||||||
|
@ -39,7 +39,7 @@ use serde::Serialize;
|
||||||
/// let app = Router::new()
|
/// let app = Router::new()
|
||||||
/// // Add our typed route to the router.
|
/// // Add our typed route to the router.
|
||||||
/// //
|
/// //
|
||||||
/// // The path will be inferred to `/users/:id` since `users_show`'s
|
/// // The path will be inferred to `/users/{id}` since `users_show`'s
|
||||||
/// // first argument is `UsersMember` which implements `TypedPath`
|
/// // first argument is `UsersMember` which implements `TypedPath`
|
||||||
/// .typed_get(users_show)
|
/// .typed_get(users_show)
|
||||||
/// .typed_post(users_create)
|
/// .typed_post(users_create)
|
||||||
|
@ -75,7 +75,7 @@ use serde::Serialize;
|
||||||
/// use axum_extra::routing::TypedPath;
|
/// use axum_extra::routing::TypedPath;
|
||||||
///
|
///
|
||||||
/// #[derive(TypedPath, Deserialize)]
|
/// #[derive(TypedPath, Deserialize)]
|
||||||
/// #[typed_path("/users/:id")]
|
/// #[typed_path("/users/{id}")]
|
||||||
/// struct UsersMember {
|
/// struct UsersMember {
|
||||||
/// id: u32,
|
/// id: u32,
|
||||||
/// }
|
/// }
|
||||||
|
@ -100,7 +100,7 @@ use serde::Serialize;
|
||||||
/// use axum_extra::routing::TypedPath;
|
/// use axum_extra::routing::TypedPath;
|
||||||
///
|
///
|
||||||
/// #[derive(TypedPath, Deserialize)]
|
/// #[derive(TypedPath, Deserialize)]
|
||||||
/// #[typed_path("/users/:id/teams/:team_id")]
|
/// #[typed_path("/users/{id}/teams/{team_id}")]
|
||||||
/// struct UsersMember {
|
/// struct UsersMember {
|
||||||
/// id: u32,
|
/// id: u32,
|
||||||
/// }
|
/// }
|
||||||
|
@ -117,7 +117,7 @@ use serde::Serialize;
|
||||||
/// struct UsersCollection;
|
/// struct UsersCollection;
|
||||||
///
|
///
|
||||||
/// #[derive(TypedPath, Deserialize)]
|
/// #[derive(TypedPath, Deserialize)]
|
||||||
/// #[typed_path("/users/:id")]
|
/// #[typed_path("/users/{id}")]
|
||||||
/// struct UsersMember(u32);
|
/// struct UsersMember(u32);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
@ -130,7 +130,7 @@ use serde::Serialize;
|
||||||
/// use axum_extra::routing::TypedPath;
|
/// use axum_extra::routing::TypedPath;
|
||||||
///
|
///
|
||||||
/// #[derive(TypedPath, Deserialize)]
|
/// #[derive(TypedPath, Deserialize)]
|
||||||
/// #[typed_path("/users/:id")]
|
/// #[typed_path("/users/{id}")]
|
||||||
/// struct UsersMember {
|
/// struct UsersMember {
|
||||||
/// id: String,
|
/// id: String,
|
||||||
/// }
|
/// }
|
||||||
|
@ -158,7 +158,7 @@ use serde::Serialize;
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// #[derive(TypedPath, Deserialize)]
|
/// #[derive(TypedPath, Deserialize)]
|
||||||
/// #[typed_path("/users/:id", rejection(UsersMemberRejection))]
|
/// #[typed_path("/users/{id}", rejection(UsersMemberRejection))]
|
||||||
/// struct UsersMember {
|
/// struct UsersMember {
|
||||||
/// id: String,
|
/// id: String,
|
||||||
/// }
|
/// }
|
||||||
|
@ -215,7 +215,7 @@ use serde::Serialize;
|
||||||
/// [`Deserialize`]: serde::Deserialize
|
/// [`Deserialize`]: serde::Deserialize
|
||||||
/// [`PathRejection`]: axum::extract::rejection::PathRejection
|
/// [`PathRejection`]: axum::extract::rejection::PathRejection
|
||||||
pub trait TypedPath: std::fmt::Display {
|
pub trait TypedPath: std::fmt::Display {
|
||||||
/// The path with optional captures such as `/users/:id`.
|
/// The path with optional captures such as `/users/{id}`.
|
||||||
const PATH: &'static str;
|
const PATH: &'static str;
|
||||||
|
|
||||||
/// Convert the path into a `Uri`.
|
/// Convert the path into a `Uri`.
|
||||||
|
@ -398,7 +398,7 @@ mod tests {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/users/:id")]
|
#[typed_path("/users/{id}")]
|
||||||
struct UsersShow {
|
struct UsersShow {
|
||||||
id: i32,
|
id: i32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ use std::convert::Infallible;
|
||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
|
/// let app = Router::new().route("/users/{user_id}/team/{team_id}", get(users_teams_show));
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
|
|
@ -383,8 +383,12 @@ fn parse_path(path: &LitStr) -> syn::Result<Vec<Segment>> {
|
||||||
.split('/')
|
.split('/')
|
||||||
.map(|segment| {
|
.map(|segment| {
|
||||||
if let Some(capture) = segment
|
if let Some(capture) = segment
|
||||||
.strip_prefix(':')
|
.strip_prefix('{')
|
||||||
.or_else(|| segment.strip_prefix('*'))
|
.and_then(|segment| segment.strip_suffix('}'))
|
||||||
|
.and_then(|segment| {
|
||||||
|
(!segment.starts_with('{') && !segment.ends_with('}')).then_some(segment)
|
||||||
|
})
|
||||||
|
.map(|capture| capture.strip_prefix('*').unwrap_or(capture))
|
||||||
{
|
{
|
||||||
Ok(Segment::Capture(capture.to_owned(), path.span()))
|
Ok(Segment::Capture(capture.to_owned(), path.span()))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use axum_macros::TypedPath;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/users/:id")]
|
#[typed_path("/users/{id}")]
|
||||||
struct MyPath {}
|
struct MyPath {}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error[E0026]: struct `MyPath` does not have a field named `id`
|
error[E0026]: struct `MyPath` does not have a field named `id`
|
||||||
--> tests/typed_path/fail/missing_field.rs:5:14
|
--> tests/typed_path/fail/missing_field.rs:5:14
|
||||||
|
|
|
|
||||||
5 | #[typed_path("/users/:id")]
|
5 | #[typed_path("/users/{id}")]
|
||||||
| ^^^^^^^^^^^^ struct `MyPath` does not have this field
|
| ^^^^^^^^^^^^^ struct `MyPath` does not have this field
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use axum_macros::TypedPath;
|
use axum_macros::TypedPath;
|
||||||
|
|
||||||
#[derive(TypedPath)]
|
#[derive(TypedPath)]
|
||||||
#[typed_path("/users/:id")]
|
#[typed_path("/users/{id}")]
|
||||||
struct MyPath {
|
struct MyPath {
|
||||||
id: u32,
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use axum_extra::routing::TypedPath;
|
use axum_extra::routing::TypedPath;
|
||||||
|
|
||||||
#[derive(TypedPath)]
|
#[derive(TypedPath)]
|
||||||
#[typed_path(":foo")]
|
#[typed_path("{foo}")]
|
||||||
struct MyPath;
|
struct MyPath;
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error: paths must start with a `/`
|
error: paths must start with a `/`
|
||||||
--> tests/typed_path/fail/route_not_starting_with_slash_non_empty.rs:4:14
|
--> tests/typed_path/fail/route_not_starting_with_slash_non_empty.rs:4:14
|
||||||
|
|
|
|
||||||
4 | #[typed_path(":foo")]
|
4 | #[typed_path("{foo}")]
|
||||||
| ^^^^^^
|
| ^^^^^^^
|
||||||
|
|
|
@ -2,7 +2,7 @@ use axum_macros::TypedPath;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/users/:id")]
|
#[typed_path("/users/{id}")]
|
||||||
struct MyPath;
|
struct MyPath;
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error: Typed paths for unit structs cannot contain captures
|
error: Typed paths for unit structs cannot contain captures
|
||||||
--> tests/typed_path/fail/unit_with_capture.rs:5:14
|
--> tests/typed_path/fail/unit_with_capture.rs:5:14
|
||||||
|
|
|
|
||||||
5 | #[typed_path("/users/:id")]
|
5 | #[typed_path("/users/{id}")]
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
@ -6,7 +6,7 @@ use axum_extra::routing::{RouterExt, TypedPath};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/:foo", rejection(MyRejection))]
|
#[typed_path("/{foo}", rejection(MyRejection))]
|
||||||
struct MyPathNamed {
|
struct MyPathNamed {
|
||||||
foo: String,
|
foo: String,
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ struct MyPathNamed {
|
||||||
struct MyPathUnit;
|
struct MyPathUnit;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/:foo", rejection(MyRejection))]
|
#[typed_path("/{foo}", rejection(MyRejection))]
|
||||||
struct MyPathUnnamed(String);
|
struct MyPathUnnamed(String);
|
||||||
|
|
||||||
struct MyRejection;
|
struct MyRejection;
|
||||||
|
|
|
@ -3,13 +3,13 @@ use axum::http::Uri;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/:id")]
|
#[typed_path("/{id}")]
|
||||||
struct Named {
|
struct Named {
|
||||||
id: u32,
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/:id")]
|
#[typed_path("/{id}")]
|
||||||
struct Unnamed(u32);
|
struct Unnamed(u32);
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use axum_extra::routing::TypedPath;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/users/:user_id/teams/:team_id")]
|
#[typed_path("/users/{user_id}/teams/{team_id}")]
|
||||||
struct MyPath {
|
struct MyPath {
|
||||||
user_id: u32,
|
user_id: u32,
|
||||||
team_id: u32,
|
team_id: u32,
|
||||||
|
@ -11,7 +11,7 @@ struct MyPath {
|
||||||
fn main() {
|
fn main() {
|
||||||
_ = axum::Router::<()>::new().route("/", axum::routing::get(|_: MyPath| async {}));
|
_ = axum::Router::<()>::new().route("/", axum::routing::get(|_: MyPath| async {}));
|
||||||
|
|
||||||
assert_eq!(MyPath::PATH, "/users/:user_id/teams/:team_id");
|
assert_eq!(MyPath::PATH, "/users/{user_id}/teams/{team_id}");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!(
|
format!(
|
||||||
"{}",
|
"{}",
|
||||||
|
|
|
@ -3,7 +3,7 @@ use axum::{extract::rejection::PathRejection, http::StatusCode};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/users/:id")]
|
#[typed_path("/users/{id}")]
|
||||||
struct UsersShow {
|
struct UsersShow {
|
||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,12 @@ use serde::Deserialize;
|
||||||
pub type Result<T> = std::result::Result<T, ()>;
|
pub type Result<T> = std::result::Result<T, ()>;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/users/:user_id/teams/:team_id")]
|
#[typed_path("/users/{user_id}/teams/{team_id}")]
|
||||||
struct MyPath(u32, u32);
|
struct MyPath(u32, u32);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
_ = axum::Router::<()>::new().route("/", axum::routing::get(|_: MyPath| async {}));
|
_ = axum::Router::<()>::new().route("/", axum::routing::get(|_: MyPath| async {}));
|
||||||
|
|
||||||
assert_eq!(MyPath::PATH, "/users/:user_id/teams/:team_id");
|
assert_eq!(MyPath::PATH, "/users/{user_id}/teams/{team_id}");
|
||||||
assert_eq!(format!("{}", MyPath(1, 2)), "/users/1/teams/2");
|
assert_eq!(format!("{}", MyPath(1, 2)), "/users/1/teams/2");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ use axum_extra::routing::TypedPath;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/:param")]
|
#[typed_path("/{param}")]
|
||||||
struct Named {
|
struct Named {
|
||||||
param: String,
|
param: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/:param")]
|
#[typed_path("/{param}")]
|
||||||
struct Unnamed(String);
|
struct Unnamed(String);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use axum_extra::routing::{RouterExt, TypedPath};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(TypedPath, Deserialize)]
|
#[derive(TypedPath, Deserialize)]
|
||||||
#[typed_path("/*rest")]
|
#[typed_path("/{*rest}")]
|
||||||
struct MyPath {
|
struct MyPath {
|
||||||
rest: String,
|
rest: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ http = "1.0.0"
|
||||||
http-body = "1.0.0"
|
http-body = "1.0.0"
|
||||||
http-body-util = "0.1.0"
|
http-body-util = "0.1.0"
|
||||||
itoa = "1.0.5"
|
itoa = "1.0.5"
|
||||||
matchit = "0.7"
|
matchit = "=0.8.0"
|
||||||
memchr = "2.4.1"
|
memchr = "2.4.1"
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
|
|
|
@ -77,7 +77,7 @@ async fn extension(Extension(state): Extension<State>) {}
|
||||||
struct State { /* ... */ }
|
struct State { /* ... */ }
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/path/:user_id", post(path))
|
.route("/path/{user_id}", post(path))
|
||||||
.route("/query", post(query))
|
.route("/query", post(query))
|
||||||
.route("/string", post(string))
|
.route("/string", post(string))
|
||||||
.route("/bytes", post(bytes))
|
.route("/bytes", post(bytes))
|
||||||
|
@ -100,7 +100,7 @@ use axum::{
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
let app = Router::new().route("/users/:id/things", get(get_user_things));
|
let app = Router::new().route("/users/{id}/things", get(get_user_things));
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Pagination {
|
struct Pagination {
|
||||||
|
|
|
@ -16,7 +16,7 @@ use axum::{
|
||||||
// define some routes separately
|
// define some routes separately
|
||||||
let user_routes = Router::new()
|
let user_routes = Router::new()
|
||||||
.route("/users", get(users_list))
|
.route("/users", get(users_list))
|
||||||
.route("/users/:id", get(users_show));
|
.route("/users/{id}", get(users_show));
|
||||||
|
|
||||||
let team_routes = Router::new()
|
let team_routes = Router::new()
|
||||||
.route("/teams", get(teams_list));
|
.route("/teams", get(teams_list));
|
||||||
|
@ -30,7 +30,7 @@ let app = Router::new()
|
||||||
|
|
||||||
// Our app now accepts
|
// Our app now accepts
|
||||||
// - GET /users
|
// - GET /users
|
||||||
// - GET /users/:id
|
// - GET /users/{id}
|
||||||
// - GET /teams
|
// - GET /teams
|
||||||
# let _: Router = app;
|
# let _: Router = app;
|
||||||
```
|
```
|
||||||
|
|
|
@ -11,7 +11,7 @@ use axum::{
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_routes = Router::new().route("/:id", get(|| async {}));
|
let user_routes = Router::new().route("/{id}", get(|| async {}));
|
||||||
|
|
||||||
let team_routes = Router::new().route("/", post(|| async {}));
|
let team_routes = Router::new().route("/", post(|| async {}));
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ let api_routes = Router::new()
|
||||||
let app = Router::new().nest("/api", api_routes);
|
let app = Router::new().nest("/api", api_routes);
|
||||||
|
|
||||||
// Our app now accepts
|
// Our app now accepts
|
||||||
// - GET /api/users/:id
|
// - GET /api/users/{id}
|
||||||
// - POST /api/teams
|
// - POST /api/teams
|
||||||
# let _: Router = app;
|
# let _: Router = app;
|
||||||
```
|
```
|
||||||
|
@ -54,9 +54,9 @@ async fn users_get(Path(params): Path<HashMap<String, String>>) {
|
||||||
let id = params.get("id");
|
let id = params.get("id");
|
||||||
}
|
}
|
||||||
|
|
||||||
let users_api = Router::new().route("/users/:id", get(users_get));
|
let users_api = Router::new().route("/users/{id}", get(users_get));
|
||||||
|
|
||||||
let app = Router::new().nest("/:version/api", users_api);
|
let app = Router::new().nest("/{version}/api", users_api);
|
||||||
# let _: Router = app;
|
# let _: Router = app;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ let nested_router = Router::new()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/foo/*rest", get(|uri: Uri| async {
|
.route("/foo/{*rest}", get(|uri: Uri| async {
|
||||||
// `uri` will contain `/foo`
|
// `uri` will contain `/foo`
|
||||||
}))
|
}))
|
||||||
.nest("/bar", nested_router);
|
.nest("/bar", nested_router);
|
||||||
|
|
|
@ -20,15 +20,15 @@ be called.
|
||||||
|
|
||||||
# Captures
|
# Captures
|
||||||
|
|
||||||
Paths can contain segments like `/:key` which matches any single segment and
|
Paths can contain segments like `/{key}` which matches any single segment and
|
||||||
will store the value captured at `key`. The value captured can be zero-length
|
will store the value captured at `key`. The value captured can be zero-length
|
||||||
except for in the invalid path `//`.
|
except for in the invalid path `//`.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- `/:key`
|
- `/{key}`
|
||||||
- `/users/:id`
|
- `/users/{id}`
|
||||||
- `/users/:id/tweets`
|
- `/users/{id}/tweets`
|
||||||
|
|
||||||
Captures can be extracted using [`Path`](crate::extract::Path). See its
|
Captures can be extracted using [`Path`](crate::extract::Path). See its
|
||||||
documentation for more details.
|
documentation for more details.
|
||||||
|
@ -41,19 +41,19 @@ path rather than the actual path.
|
||||||
|
|
||||||
# Wildcards
|
# Wildcards
|
||||||
|
|
||||||
Paths can end in `/*key` which matches all segments and will store the segments
|
Paths can end in `/{*key}` which matches all segments and will store the segments
|
||||||
captured at `key`.
|
captured at `key`.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- `/*key`
|
- `/{*key}`
|
||||||
- `/assets/*path`
|
- `/assets/{*path}`
|
||||||
- `/:id/:repo/*tree`
|
- `/{id}/{repo}/{*tree}`
|
||||||
|
|
||||||
Note that `/*key` doesn't match empty segments. Thus:
|
Note that `/{*key}` doesn't match empty segments. Thus:
|
||||||
|
|
||||||
- `/*key` doesn't match `/` but does match `/a`, `/a/`, etc.
|
- `/{*key}` doesn't match `/` but does match `/a`, `/a/`, etc.
|
||||||
- `/x/*key` doesn't match `/x` or `/x/` but does match `/x/a`, `/x/a/`, etc.
|
- `/x/{*key}` doesn't match `/x` or `/x/` but does match `/x/a`, `/x/a/`, etc.
|
||||||
|
|
||||||
Wildcard captures can also be extracted using [`Path`](crate::extract::Path):
|
Wildcard captures can also be extracted using [`Path`](crate::extract::Path):
|
||||||
|
|
||||||
|
@ -64,14 +64,14 @@ use axum::{
|
||||||
extract::Path,
|
extract::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
let app: Router = Router::new().route("/*key", get(handler));
|
let app: Router = Router::new().route("/{*key}", get(handler));
|
||||||
|
|
||||||
async fn handler(Path(path): Path<String>) -> String {
|
async fn handler(Path(path): Path<String>) -> String {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the leading slash is not included, i.e. for the route `/foo/*rest` and
|
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 path `/foo/bar/baz` the value of `rest` will be `bar/baz`.
|
||||||
|
|
||||||
# Accepting multiple methods
|
# Accepting multiple methods
|
||||||
|
@ -120,9 +120,9 @@ use axum::{Router, routing::{get, delete}, extract::Path};
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(root))
|
.route("/", get(root))
|
||||||
.route("/users", get(list_users).post(create_user))
|
.route("/users", get(list_users).post(create_user))
|
||||||
.route("/users/:id", get(show_user))
|
.route("/users/{id}", get(show_user))
|
||||||
.route("/api/:version/users/:id/action", delete(do_users_action))
|
.route("/api/{version}/users/{id}/action", delete(do_users_action))
|
||||||
.route("/assets/*path", get(serve_asset));
|
.route("/assets/{*path}", get(serve_asset));
|
||||||
|
|
||||||
async fn root() {}
|
async fn root() {}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ let app = Router::new()
|
||||||
# let _: Router = app;
|
# let _: Router = app;
|
||||||
```
|
```
|
||||||
|
|
||||||
The static route `/foo` and the dynamic route `/:key` are not considered to
|
The static route `/foo` and the dynamic route `/{key}` are not considered to
|
||||||
overlap and `/foo` will take precedence.
|
overlap and `/foo` will take precedence.
|
||||||
|
|
||||||
Also panics if `path` is empty.
|
Also panics if `path` is empty.
|
||||||
|
|
43
axum/src/docs/routing/without_v07_checks.md
Normal file
43
axum/src/docs/routing/without_v07_checks.md
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
Turn off checks for compatibility with route matching syntax from 0.7.
|
||||||
|
|
||||||
|
This allows usage of paths starting with a colon `:` or an asterisk `*` which are otherwise prohibited.
|
||||||
|
|
||||||
|
# Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use axum::{
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
|
||||||
|
let app = Router::<()>::new()
|
||||||
|
.without_v07_checks()
|
||||||
|
.route("/:colon", get(|| async {}))
|
||||||
|
.route("/*asterisk", get(|| async {}));
|
||||||
|
|
||||||
|
// Our app now accepts
|
||||||
|
// - GET /:colon
|
||||||
|
// - GET /*asterisk
|
||||||
|
# let _: Router = app;
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding such routes without calling this method first will panic.
|
||||||
|
|
||||||
|
```rust,should_panic
|
||||||
|
use axum::{
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This panics...
|
||||||
|
let app = Router::<()>::new()
|
||||||
|
.route("/:colon", get(|| async {}));
|
||||||
|
```
|
||||||
|
|
||||||
|
# Merging
|
||||||
|
|
||||||
|
When two routers are merged, v0.7 checks are disabled for route registrations on the resulting router if both of the two routers had them also disabled.
|
||||||
|
|
||||||
|
# Nesting
|
||||||
|
|
||||||
|
Each router needs to have the checks explicitly disabled. Nesting a router with the checks either enabled or disabled has no effect on the outer router.
|
|
@ -13,10 +13,10 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route(
|
/// let app = Router::new().route(
|
||||||
/// "/users/:id",
|
/// "/users/{id}",
|
||||||
/// get(|path: MatchedPath| async move {
|
/// get(|path: MatchedPath| async move {
|
||||||
/// let path = path.as_str();
|
/// let path = path.as_str();
|
||||||
/// // `path` will be "/users/:id"
|
/// // `path` will be "/users/{id}"
|
||||||
/// })
|
/// })
|
||||||
/// );
|
/// );
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
|
@ -38,7 +38,7 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
/// use tower_http::trace::TraceLayer;
|
/// use tower_http::trace::TraceLayer;
|
||||||
///
|
///
|
||||||
/// let app = Router::new()
|
/// let app = Router::new()
|
||||||
/// .route("/users/:id", get(|| async { /* ... */ }))
|
/// .route("/users/{id}", get(|| async { /* ... */ }))
|
||||||
/// .layer(
|
/// .layer(
|
||||||
/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
|
/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
|
||||||
/// let path = if let Some(path) = req.extensions().get::<MatchedPath>() {
|
/// let path = if let Some(path) = req.extensions().get::<MatchedPath>() {
|
||||||
|
@ -141,22 +141,22 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn extracting_on_handler() {
|
async fn extracting_on_handler() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/:a",
|
"/{a}",
|
||||||
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
||||||
);
|
);
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
let res = client.get("/foo").await;
|
let res = client.get("/foo").await;
|
||||||
assert_eq!(res.text().await, "/:a");
|
assert_eq!(res.text().await, "/{a}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn extracting_on_handler_in_nested_router() {
|
async fn extracting_on_handler_in_nested_router() {
|
||||||
let app = Router::new().nest(
|
let app = Router::new().nest(
|
||||||
"/:a",
|
"/{a}",
|
||||||
Router::new().route(
|
Router::new().route(
|
||||||
"/:b",
|
"/{b}",
|
||||||
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -164,17 +164,17 @@ mod tests {
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
let res = client.get("/foo/bar").await;
|
let res = client.get("/foo/bar").await;
|
||||||
assert_eq!(res.text().await, "/:a/:b");
|
assert_eq!(res.text().await, "/{a}/{b}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn extracting_on_handler_in_deeply_nested_router() {
|
async fn extracting_on_handler_in_deeply_nested_router() {
|
||||||
let app = Router::new().nest(
|
let app = Router::new().nest(
|
||||||
"/:a",
|
"/{a}",
|
||||||
Router::new().nest(
|
Router::new().nest(
|
||||||
"/:b",
|
"/{b}",
|
||||||
Router::new().route(
|
Router::new().route(
|
||||||
"/:c",
|
"/{c}",
|
||||||
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -183,7 +183,7 @@ mod tests {
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
let res = client.get("/foo/bar/baz").await;
|
let res = client.get("/foo/bar/baz").await;
|
||||||
assert_eq!(res.text().await, "/:a/:b/:c");
|
assert_eq!(res.text().await, "/{a}/{b}/{c}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
|
@ -197,7 +197,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest_service("/:a", Router::new().route("/:b", get(|| async move {})))
|
.nest_service("/{a}", Router::new().route("/{b}", get(|| async move {})))
|
||||||
.layer(map_request(extract_matched_path));
|
.layer(map_request(extract_matched_path));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
@ -212,12 +212,12 @@ mod tests {
|
||||||
matched_path: Option<MatchedPath>,
|
matched_path: Option<MatchedPath>,
|
||||||
req: Request<B>,
|
req: Request<B>,
|
||||||
) -> Request<B> {
|
) -> Request<B> {
|
||||||
assert_eq!(matched_path.unwrap().as_str(), "/:a/:b");
|
assert_eq!(matched_path.unwrap().as_str(), "/{a}/{b}");
|
||||||
req
|
req
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/:a", Router::new().route("/:b", get(|| async move {})))
|
.nest("/{a}", Router::new().route("/{b}", get(|| async move {})))
|
||||||
.layer(map_request(extract_matched_path));
|
.layer(map_request(extract_matched_path));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
@ -234,7 +234,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest_service("/:a", Router::new().route("/:b", get(|| async move {})))
|
.nest_service("/{a}", Router::new().route("/{b}", get(|| async move {})))
|
||||||
.layer(map_request(assert_no_matched_path));
|
.layer(map_request(assert_no_matched_path));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
@ -251,7 +251,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/:a", Router::new().route("/:b", get(|| async move {})))
|
.nest("/{a}", Router::new().route("/{b}", get(|| async move {})))
|
||||||
.layer(map_request(assert_matched_path));
|
.layer(map_request(assert_matched_path));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
@ -263,14 +263,14 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn can_extract_nested_matched_path_in_middleware_on_nested_router() {
|
async fn can_extract_nested_matched_path_in_middleware_on_nested_router() {
|
||||||
async fn extract_matched_path<B>(matched_path: MatchedPath, req: Request<B>) -> Request<B> {
|
async fn extract_matched_path<B>(matched_path: MatchedPath, req: Request<B>) -> Request<B> {
|
||||||
assert_eq!(matched_path.as_str(), "/:a/:b");
|
assert_eq!(matched_path.as_str(), "/{a}/{b}");
|
||||||
req
|
req
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new().nest(
|
let app = Router::new().nest(
|
||||||
"/:a",
|
"/{a}",
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/:b", get(|| async move {}))
|
.route("/{b}", get(|| async move {}))
|
||||||
.layer(map_request(extract_matched_path)),
|
.layer(map_request(extract_matched_path)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -284,14 +284,14 @@ mod tests {
|
||||||
async fn can_extract_nested_matched_path_in_middleware_on_nested_router_via_extension() {
|
async fn can_extract_nested_matched_path_in_middleware_on_nested_router_via_extension() {
|
||||||
async fn extract_matched_path<B>(req: Request<B>) -> Request<B> {
|
async fn extract_matched_path<B>(req: Request<B>) -> Request<B> {
|
||||||
let matched_path = req.extensions().get::<MatchedPath>().unwrap();
|
let matched_path = req.extensions().get::<MatchedPath>().unwrap();
|
||||||
assert_eq!(matched_path.as_str(), "/:a/:b");
|
assert_eq!(matched_path.as_str(), "/{a}/{b}");
|
||||||
req
|
req
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new().nest(
|
let app = Router::new().nest(
|
||||||
"/:a",
|
"/{a}",
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/:b", get(|| async move {}))
|
.route("/{b}", get(|| async move {}))
|
||||||
.layer(map_request(extract_matched_path)),
|
.layer(map_request(extract_matched_path)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -307,7 +307,7 @@ mod tests {
|
||||||
assert!(path.is_none());
|
assert!(path.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new().nest_service("/:a", handler.into_service());
|
let app = Router::new().nest_service("/{a}", handler.into_service());
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ mod tests {
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
|
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/*path",
|
"/{*path}",
|
||||||
any(|req: Request| {
|
any(|req: Request| {
|
||||||
Router::new()
|
Router::new()
|
||||||
.nest("/", Router::new().route("/foo", get(|| async {})))
|
.nest("/", Router::new().route("/foo", get(|| async {})))
|
||||||
|
@ -349,4 +349,44 @@ mod tests {
|
||||||
let res = client.get("/foo/bar").await;
|
let res = client.get("/foo/bar").await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn matching_colon() {
|
||||||
|
let app = Router::new().without_v07_checks().route(
|
||||||
|
"/:foo",
|
||||||
|
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/:foo").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(res.text().await, "/:foo");
|
||||||
|
|
||||||
|
let res = client.get("/:bar").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let res = client.get("/foo").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn matching_asterisk() {
|
||||||
|
let app = Router::new().without_v07_checks().route(
|
||||||
|
"/*foo",
|
||||||
|
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/*foo").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(res.text().await, "/*foo");
|
||||||
|
|
||||||
|
let res = client.get("/*bar").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let res = client.get("/foo").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ use std::{fmt, sync::Arc};
|
||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
|
/// let app = Router::new().route("/users/{user_id}/team/{team_id}", get(users_teams_show));
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
@ -61,7 +61,7 @@ use std::{fmt, sync::Arc};
|
||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route("/users/:user_id", get(user_info));
|
/// let app = Router::new().route("/users/{user_id}", get(user_info));
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
@ -98,7 +98,7 @@ use std::{fmt, sync::Arc};
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route(
|
/// let app = Router::new().route(
|
||||||
/// "/users/:user_id/team/:team_id",
|
/// "/users/{user_id}/team/{team_id}",
|
||||||
/// get(users_teams_show).post(users_teams_create),
|
/// get(users_teams_show).post(users_teams_create),
|
||||||
/// );
|
/// );
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
|
@ -127,7 +127,7 @@ use std::{fmt, sync::Arc};
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new()
|
/// let app = Router::new()
|
||||||
/// .route("/users/:user_id/team/:team_id", get(params_map).post(params_vec));
|
/// .route("/users/{user_id}/team/{team_id}", get(params_map).post(params_vec));
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
@ -438,7 +438,7 @@ impl std::error::Error for FailedToDeserializePathParams {}
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
|
/// let app = Router::new().route("/users/{user_id}/team/{team_id}", get(users_teams_show));
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -548,7 +548,7 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn extracting_url_params() {
|
async fn extracting_url_params() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/users/:id",
|
"/users/{id}",
|
||||||
get(|Path(id): Path<i32>| async move {
|
get(|Path(id): Path<i32>| async move {
|
||||||
assert_eq!(id, 42);
|
assert_eq!(id, 42);
|
||||||
})
|
})
|
||||||
|
@ -568,7 +568,7 @@ mod tests {
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn extracting_url_params_multiple_times() {
|
async fn extracting_url_params_multiple_times() {
|
||||||
let app = Router::new().route("/users/:id", get(|_: Path<i32>, _: Path<String>| async {}));
|
let app = Router::new().route("/users/{id}", get(|_: Path<i32>, _: Path<String>| async {}));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -579,7 +579,7 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn percent_decoding() {
|
async fn percent_decoding() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/:key",
|
"/{key}",
|
||||||
get(|Path(param): Path<String>| async move { param }),
|
get(|Path(param): Path<String>| async move { param }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -594,11 +594,11 @@ mod tests {
|
||||||
async fn supports_128_bit_numbers() {
|
async fn supports_128_bit_numbers() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/i/:key",
|
"/i/{key}",
|
||||||
get(|Path(param): Path<i128>| async move { param.to_string() }),
|
get(|Path(param): Path<i128>| async move { param.to_string() }),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/u/:key",
|
"/u/{key}",
|
||||||
get(|Path(param): Path<u128>| async move { param.to_string() }),
|
get(|Path(param): Path<u128>| async move { param.to_string() }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -615,11 +615,11 @@ mod tests {
|
||||||
async fn wildcard() {
|
async fn wildcard() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/foo/*rest",
|
"/foo/{*rest}",
|
||||||
get(|Path(param): Path<String>| async move { param }),
|
get(|Path(param): Path<String>| async move { param }),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/bar/*rest",
|
"/bar/{*rest}",
|
||||||
get(|Path(params): Path<HashMap<String, String>>| async move {
|
get(|Path(params): Path<HashMap<String, String>>| async move {
|
||||||
params.get("rest").unwrap().clone()
|
params.get("rest").unwrap().clone()
|
||||||
}),
|
}),
|
||||||
|
@ -636,7 +636,7 @@ mod tests {
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn captures_dont_match_empty_path() {
|
async fn captures_dont_match_empty_path() {
|
||||||
let app = Router::new().route("/:key", get(|| async {}));
|
let app = Router::new().route("/{key}", get(|| async {}));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -650,7 +650,7 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn captures_match_empty_inner_segments() {
|
async fn captures_match_empty_inner_segments() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/:key/method",
|
"/{key}/method",
|
||||||
get(|Path(param): Path<String>| async move { param.to_string() }),
|
get(|Path(param): Path<String>| async move { param.to_string() }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -666,7 +666,7 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn captures_match_empty_inner_segments_near_end() {
|
async fn captures_match_empty_inner_segments_near_end() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/method/:key/",
|
"/method/{key}/",
|
||||||
get(|Path(param): Path<String>| async move { param.to_string() }),
|
get(|Path(param): Path<String>| async move { param.to_string() }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -685,7 +685,7 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn captures_match_empty_trailing_segment() {
|
async fn captures_match_empty_trailing_segment() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/method/:key",
|
"/method/{key}",
|
||||||
get(|Path(param): Path<String>| async move { param.to_string() }),
|
get(|Path(param): Path<String>| async move { param.to_string() }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -717,7 +717,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new().route("/:key", get(|param: Path<Param>| async move { param.0 .0 }));
|
let app = Router::new().route(
|
||||||
|
"/{key}",
|
||||||
|
get(|param: Path<Param>| async move { param.0 .0 }),
|
||||||
|
);
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -731,7 +734,7 @@ mod tests {
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn two_path_extractors() {
|
async fn two_path_extractors() {
|
||||||
let app = Router::new().route("/:a/:b", get(|_: Path<String>, _: Path<String>| async {}));
|
let app = Router::new().route("/{a}/{b}", get(|_: Path<String>, _: Path<String>| async {}));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -751,8 +754,11 @@ mod tests {
|
||||||
struct Tuple(String, String);
|
struct Tuple(String, String);
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/foo/:a/:b/:c", get(|_: Path<(String, String)>| async {}))
|
.route(
|
||||||
.route("/bar/:a/:b/:c", get(|_: Path<Tuple>| async {}));
|
"/foo/{a}/{b}/{c}",
|
||||||
|
get(|_: Path<(String, String)>| async {}),
|
||||||
|
)
|
||||||
|
.route("/bar/{a}/{b}/{c}", get(|_: Path<Tuple>| async {}));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -774,7 +780,7 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn deserialize_into_vec_of_tuples() {
|
async fn deserialize_into_vec_of_tuples() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/:a/:b",
|
"/{a}/{b}",
|
||||||
get(|Path(params): Path<Vec<(String, String)>>| async move {
|
get(|Path(params): Path<Vec<(String, String)>>| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params,
|
params,
|
||||||
|
@ -805,31 +811,31 @@ mod tests {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/single/:a",
|
"/single/{a}",
|
||||||
get(|Path(a): Path<Date>| async move { format!("single: {a}") }),
|
get(|Path(a): Path<Date>| async move { format!("single: {a}") }),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/tuple/:a/:b/:c",
|
"/tuple/{a}/{b}/{c}",
|
||||||
get(|Path((a, b, c)): Path<(Date, Date, Date)>| async move {
|
get(|Path((a, b, c)): Path<(Date, Date, Date)>| async move {
|
||||||
format!("tuple: {a} {b} {c}")
|
format!("tuple: {a} {b} {c}")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/vec/:a/:b/:c",
|
"/vec/{a}/{b}/{c}",
|
||||||
get(|Path(vec): Path<Vec<Date>>| async move {
|
get(|Path(vec): Path<Vec<Date>>| async move {
|
||||||
let [a, b, c]: [Date; 3] = vec.try_into().unwrap();
|
let [a, b, c]: [Date; 3] = vec.try_into().unwrap();
|
||||||
format!("vec: {a} {b} {c}")
|
format!("vec: {a} {b} {c}")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/vec_pairs/:a/:b/:c",
|
"/vec_pairs/{a}/{b}/{c}",
|
||||||
get(|Path(vec): Path<Vec<(String, Date)>>| async move {
|
get(|Path(vec): Path<Vec<(String, Date)>>| async move {
|
||||||
let [(_, a), (_, b), (_, c)]: [(String, Date); 3] = vec.try_into().unwrap();
|
let [(_, a), (_, b), (_, c)]: [(String, Date); 3] = vec.try_into().unwrap();
|
||||||
format!("vec_pairs: {a} {b} {c}")
|
format!("vec_pairs: {a} {b} {c}")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/map/:a/:b/:c",
|
"/map/{a}/{b}/{c}",
|
||||||
get(|Path(mut map): Path<HashMap<String, Date>>| async move {
|
get(|Path(mut map): Path<HashMap<String, Date>>| async move {
|
||||||
let a = map.remove("a").unwrap();
|
let a = map.remove("a").unwrap();
|
||||||
let b = map.remove("b").unwrap();
|
let b = map.remove("b").unwrap();
|
||||||
|
@ -838,7 +844,7 @@ mod tests {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/struct/:a/:b/:c",
|
"/struct/{a}/{b}/{c}",
|
||||||
get(|Path(params): Path<Params>| async move {
|
get(|Path(params): Path<Params>| async move {
|
||||||
format!("struct: {} {} {}", params.a, params.b, params.c)
|
format!("struct: {} {} {}", params.a, params.b, params.c)
|
||||||
}),
|
}),
|
||||||
|
@ -875,8 +881,8 @@ mod tests {
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/one/:a", get(|_: Path<(Value, Value)>| async {}))
|
.route("/one/{a}", get(|_: Path<(Value, Value)>| async {}))
|
||||||
.route("/two/:a/:b", get(|_: Path<Value>| async {}));
|
.route("/two/{a}/{b}", get(|_: Path<Value>| async {}));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -896,7 +902,7 @@ mod tests {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn raw_path_params() {
|
async fn raw_path_params() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/:a/:b/:c",
|
"/{a}/{b}/{c}",
|
||||||
get(|params: RawPathParams| async move {
|
get(|params: RawPathParams| async move {
|
||||||
params
|
params
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
@ -46,7 +46,7 @@ use std::convert::Infallible;
|
||||||
/// use tower_http::trace::TraceLayer;
|
/// use tower_http::trace::TraceLayer;
|
||||||
///
|
///
|
||||||
/// let api_routes = Router::new()
|
/// let api_routes = Router::new()
|
||||||
/// .route("/users/:id", get(|| async { /* ... */ }))
|
/// .route("/users/{id}", get(|| async { /* ... */ }))
|
||||||
/// .layer(
|
/// .layer(
|
||||||
/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
|
/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
|
||||||
/// let path = if let Some(path) = req.extensions().get::<OriginalUri>() {
|
/// let path = if let Some(path) = req.extensions().get::<OriginalUri>() {
|
||||||
|
|
|
@ -82,7 +82,7 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||||
/// # unimplemented!()
|
/// # unimplemented!()
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let app = Router::new().route("/users/:id", get(get_user));
|
/// let app = Router::new().route("/users/{id}", get(get_user));
|
||||||
/// # let _: Router = app;
|
/// # let _: Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
|
|
@ -251,7 +251,7 @@
|
||||||
//! }),
|
//! }),
|
||||||
//! )
|
//! )
|
||||||
//! .route(
|
//! .route(
|
||||||
//! "/users/:id",
|
//! "/users/{id}",
|
||||||
//! get({
|
//! get({
|
||||||
//! let shared_state = Arc::clone(&shared_state);
|
//! let shared_state = Arc::clone(&shared_state);
|
||||||
//! move |path| get_user(path, shared_state)
|
//! move |path| get_user(path, shared_state)
|
||||||
|
|
|
@ -99,9 +99,9 @@ impl<S> fmt::Debug for Router<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param";
|
pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param";
|
||||||
pub(crate) const NEST_TAIL_PARAM_CAPTURE: &str = "/*__private__axum_nest_tail_param";
|
pub(crate) const NEST_TAIL_PARAM_CAPTURE: &str = "/{*__private__axum_nest_tail_param}";
|
||||||
pub(crate) const FALLBACK_PARAM: &str = "__private__axum_fallback";
|
pub(crate) const FALLBACK_PARAM: &str = "__private__axum_fallback";
|
||||||
pub(crate) const FALLBACK_PARAM_PATH: &str = "/*__private__axum_fallback";
|
pub(crate) const FALLBACK_PARAM_PATH: &str = "/{*__private__axum_fallback}";
|
||||||
|
|
||||||
impl<S> Router<S>
|
impl<S> Router<S>
|
||||||
where
|
where
|
||||||
|
@ -154,6 +154,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc = include_str!("../docs/routing/without_v07_checks.md")]
|
||||||
|
pub fn without_v07_checks(self) -> Self {
|
||||||
|
self.tap_inner_mut(|this| {
|
||||||
|
this.path_router.without_v07_checks();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[doc = include_str!("../docs/routing/route.md")]
|
#[doc = include_str!("../docs/routing/route.md")]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn route(self, path: &str, method_router: MethodRouter<S>) -> Self {
|
pub fn route(self, path: &str, method_router: MethodRouter<S>) -> Self {
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub(super) struct PathRouter<S, const IS_FALLBACK: bool> {
|
||||||
routes: HashMap<RouteId, Endpoint<S>>,
|
routes: HashMap<RouteId, Endpoint<S>>,
|
||||||
node: Arc<Node>,
|
node: Arc<Node>,
|
||||||
prev_route_id: RouteId,
|
prev_route_id: RouteId,
|
||||||
|
v7_checks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> PathRouter<S, true>
|
impl<S> PathRouter<S, true>
|
||||||
|
@ -32,26 +33,56 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_path(v7_checks: bool, path: &str) -> Result<(), &'static str> {
|
||||||
|
if path.is_empty() {
|
||||||
|
return Err("Paths must start with a `/`. Use \"/\" for root routes");
|
||||||
|
} else if !path.starts_with('/') {
|
||||||
|
return Err("Paths must start with a `/`");
|
||||||
|
}
|
||||||
|
|
||||||
|
if v7_checks {
|
||||||
|
validate_v07_paths(path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_v07_paths(path: &str) -> Result<(), &'static str> {
|
||||||
|
path.split('/')
|
||||||
|
.find_map(|segment| {
|
||||||
|
if segment.starts_with(':') {
|
||||||
|
Some(Err(
|
||||||
|
"Path segments must not start with `:`. For capture groups, use \
|
||||||
|
`{capture}`. If you meant to literally match a segment starting with \
|
||||||
|
a colon, call `without_v07_checks` on the router.",
|
||||||
|
))
|
||||||
|
} else if segment.starts_with('*') {
|
||||||
|
Some(Err(
|
||||||
|
"Path segments must not start with `*`. For wildcard capture, use \
|
||||||
|
`{*wildcard}`. If you meant to literally match a segment starting with \
|
||||||
|
an asterisk, call `without_v07_checks` on the router.",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
impl<S, const IS_FALLBACK: bool> PathRouter<S, IS_FALLBACK>
|
impl<S, const IS_FALLBACK: bool> PathRouter<S, IS_FALLBACK>
|
||||||
where
|
where
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
pub(super) fn without_v07_checks(&mut self) {
|
||||||
|
self.v7_checks = false;
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn route(
|
pub(super) fn route(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &str,
|
path: &str,
|
||||||
method_router: MethodRouter<S>,
|
method_router: MethodRouter<S>,
|
||||||
) -> Result<(), Cow<'static, str>> {
|
) -> Result<(), Cow<'static, str>> {
|
||||||
fn validate_path(path: &str) -> Result<(), &'static str> {
|
validate_path(self.v7_checks, path)?;
|
||||||
if path.is_empty() {
|
|
||||||
return Err("Paths must start with a `/`. Use \"/\" for root routes");
|
|
||||||
} else if !path.starts_with('/') {
|
|
||||||
return Err("Paths must start with a `/`");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_path(path)?;
|
|
||||||
|
|
||||||
let endpoint = if let Some((route_id, Endpoint::MethodRouter(prev_method_router))) = self
|
let endpoint = if let Some((route_id, Endpoint::MethodRouter(prev_method_router))) = self
|
||||||
.node
|
.node
|
||||||
|
@ -97,11 +128,7 @@ where
|
||||||
path: &str,
|
path: &str,
|
||||||
endpoint: Endpoint<S>,
|
endpoint: Endpoint<S>,
|
||||||
) -> Result<(), Cow<'static, str>> {
|
) -> Result<(), Cow<'static, str>> {
|
||||||
if path.is_empty() {
|
validate_path(self.v7_checks, path)?;
|
||||||
return Err("Paths must start with a `/`. Use \"/\" for root routes".into());
|
|
||||||
} else if !path.starts_with('/') {
|
|
||||||
return Err("Paths must start with a `/`".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = self.next_route_id();
|
let id = self.next_route_id();
|
||||||
self.set_node(path, id)?;
|
self.set_node(path, id)?;
|
||||||
|
@ -125,8 +152,12 @@ where
|
||||||
routes,
|
routes,
|
||||||
node,
|
node,
|
||||||
prev_route_id: _,
|
prev_route_id: _,
|
||||||
|
v7_checks,
|
||||||
} = other;
|
} = other;
|
||||||
|
|
||||||
|
// If either of the two did not allow paths starting with `:` or `*`, do not allow them for the merged router either.
|
||||||
|
self.v7_checks |= v7_checks;
|
||||||
|
|
||||||
for (id, route) in routes {
|
for (id, route) in routes {
|
||||||
let path = node
|
let path = node
|
||||||
.route_id_to_path
|
.route_id_to_path
|
||||||
|
@ -162,12 +193,14 @@ where
|
||||||
path_to_nest_at: &str,
|
path_to_nest_at: &str,
|
||||||
router: PathRouter<S, IS_FALLBACK>,
|
router: PathRouter<S, IS_FALLBACK>,
|
||||||
) -> Result<(), Cow<'static, str>> {
|
) -> Result<(), Cow<'static, str>> {
|
||||||
let prefix = validate_nest_path(path_to_nest_at);
|
let prefix = validate_nest_path(self.v7_checks, path_to_nest_at);
|
||||||
|
|
||||||
let PathRouter {
|
let PathRouter {
|
||||||
routes,
|
routes,
|
||||||
node,
|
node,
|
||||||
prev_route_id: _,
|
prev_route_id: _,
|
||||||
|
// Ignore the configuration of the nested router
|
||||||
|
v7_checks: _,
|
||||||
} = router;
|
} = router;
|
||||||
|
|
||||||
for (id, endpoint) in routes {
|
for (id, endpoint) in routes {
|
||||||
|
@ -205,13 +238,13 @@ where
|
||||||
T::Response: IntoResponse,
|
T::Response: IntoResponse,
|
||||||
T::Future: Send + 'static,
|
T::Future: Send + 'static,
|
||||||
{
|
{
|
||||||
let path = validate_nest_path(path_to_nest_at);
|
let path = validate_nest_path(self.v7_checks, path_to_nest_at);
|
||||||
let prefix = path;
|
let prefix = path;
|
||||||
|
|
||||||
let path = if path.ends_with('/') {
|
let path = if path.ends_with('/') {
|
||||||
format!("{path}*{NEST_TAIL_PARAM}")
|
format!("{path}{{*{NEST_TAIL_PARAM}}}")
|
||||||
} else {
|
} else {
|
||||||
format!("{path}/*{NEST_TAIL_PARAM}")
|
format!("{path}/{{*{NEST_TAIL_PARAM}}}")
|
||||||
};
|
};
|
||||||
|
|
||||||
let layer = (
|
let layer = (
|
||||||
|
@ -222,7 +255,7 @@ where
|
||||||
|
|
||||||
self.route_endpoint(&path, endpoint.clone())?;
|
self.route_endpoint(&path, endpoint.clone())?;
|
||||||
|
|
||||||
// `/*rest` is not matched by `/` so we need to also register a router at the
|
// `/{*rest}` is not matched by `/` so we need to also register a router at the
|
||||||
// prefix itself. Otherwise if you were to nest at `/foo` then `/foo` itself
|
// prefix itself. Otherwise if you were to nest at `/foo` then `/foo` itself
|
||||||
// wouldn't match, which it should
|
// wouldn't match, which it should
|
||||||
self.route_endpoint(prefix, endpoint.clone())?;
|
self.route_endpoint(prefix, endpoint.clone())?;
|
||||||
|
@ -255,6 +288,7 @@ where
|
||||||
routes,
|
routes,
|
||||||
node: self.node,
|
node: self.node,
|
||||||
prev_route_id: self.prev_route_id,
|
prev_route_id: self.prev_route_id,
|
||||||
|
v7_checks: self.v7_checks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +321,7 @@ where
|
||||||
routes,
|
routes,
|
||||||
node: self.node,
|
node: self.node,
|
||||||
prev_route_id: self.prev_route_id,
|
prev_route_id: self.prev_route_id,
|
||||||
|
v7_checks: self.v7_checks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,6 +348,7 @@ where
|
||||||
routes,
|
routes,
|
||||||
node: self.node,
|
node: self.node,
|
||||||
prev_route_id: self.prev_route_id,
|
prev_route_id: self.prev_route_id,
|
||||||
|
v7_checks: self.v7_checks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,11 +398,7 @@ where
|
||||||
}
|
}
|
||||||
// explicitly handle all variants in case matchit adds
|
// explicitly handle all variants in case matchit adds
|
||||||
// new ones we need to handle differently
|
// new ones we need to handle differently
|
||||||
Err(
|
Err(MatchError::NotFound) => Err((req, state)),
|
||||||
MatchError::NotFound
|
|
||||||
| MatchError::ExtraTrailingSlash
|
|
||||||
| MatchError::MissingTrailingSlash,
|
|
||||||
) => Err((req, state)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,6 +431,7 @@ impl<S, const IS_FALLBACK: bool> Default for PathRouter<S, IS_FALLBACK> {
|
||||||
routes: Default::default(),
|
routes: Default::default(),
|
||||||
node: Default::default(),
|
node: Default::default(),
|
||||||
prev_route_id: RouteId(0),
|
prev_route_id: RouteId(0),
|
||||||
|
v7_checks: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,6 +451,7 @@ impl<S, const IS_FALLBACK: bool> Clone for PathRouter<S, IS_FALLBACK> {
|
||||||
routes: self.routes.clone(),
|
routes: self.routes.clone(),
|
||||||
node: self.node.clone(),
|
node: self.node.clone(),
|
||||||
prev_route_id: self.prev_route_id,
|
prev_route_id: self.prev_route_id,
|
||||||
|
v7_checks: self.v7_checks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,16 +498,22 @@ impl fmt::Debug for Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn validate_nest_path(path: &str) -> &str {
|
fn validate_nest_path(v7_checks: bool, path: &str) -> &str {
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
// nesting at `""` and `"/"` should mean the same thing
|
// nesting at `""` and `"/"` should mean the same thing
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
if path.contains('*') {
|
if path.split('/').any(|segment| {
|
||||||
|
segment.starts_with("{*") && segment.ends_with('}') && !segment.ends_with("}}")
|
||||||
|
}) {
|
||||||
panic!("Invalid route: nested routes cannot contain wildcards (*)");
|
panic!("Invalid route: nested routes cannot contain wildcards (*)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v7_checks {
|
||||||
|
validate_v07_paths(path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ fn strip_prefix(uri: &Uri, prefix: &str) -> Option<Uri> {
|
||||||
// ^^^^ this much is matched and the length is 4. Thus if we chop off the first 4
|
// ^^^^ this much is matched and the length is 4. Thus if we chop off the first 4
|
||||||
// characters we get the remainder
|
// characters we get the remainder
|
||||||
//
|
//
|
||||||
// prefix = /api/:version
|
// prefix = /api/{version}
|
||||||
// path = /api/v0/users
|
// path = /api/v0/users
|
||||||
// ^^^^^^^ this much is matched and the length is 7.
|
// ^^^^^^^ this much is matched and the length is 7.
|
||||||
let mut matching_prefix_length = Some(0);
|
let mut matching_prefix_length = Some(0);
|
||||||
|
@ -66,7 +66,7 @@ fn strip_prefix(uri: &Uri, prefix: &str) -> Option<Uri> {
|
||||||
|
|
||||||
match item {
|
match item {
|
||||||
Item::Both(path_segment, prefix_segment) => {
|
Item::Both(path_segment, prefix_segment) => {
|
||||||
if prefix_segment.starts_with(':') || path_segment == prefix_segment {
|
if is_capture(prefix_segment) || path_segment == prefix_segment {
|
||||||
// the prefix segment is either a param, which matches anything, or
|
// the prefix segment is either a param, which matches anything, or
|
||||||
// it actually matches the path segment
|
// it actually matches the path segment
|
||||||
*matching_prefix_length.as_mut().unwrap() += path_segment.len();
|
*matching_prefix_length.as_mut().unwrap() += path_segment.len();
|
||||||
|
@ -148,6 +148,14 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_capture(segment: &str) -> bool {
|
||||||
|
segment.starts_with('{')
|
||||||
|
&& segment.ends_with('}')
|
||||||
|
&& !segment.starts_with("{{")
|
||||||
|
&& !segment.ends_with("}}")
|
||||||
|
&& !segment.starts_with("{*")
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Item<T> {
|
enum Item<T> {
|
||||||
Both(T, T),
|
Both(T, T),
|
||||||
|
@ -279,74 +287,89 @@ mod tests {
|
||||||
expected = Some("/"),
|
expected = Some("/"),
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(param_0, uri = "/", prefix = "/:param", expected = Some("/"),);
|
test!(
|
||||||
|
param_0,
|
||||||
|
uri = "/",
|
||||||
|
prefix = "/{param}",
|
||||||
|
expected = Some("/"),
|
||||||
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_1,
|
param_1,
|
||||||
uri = "/a",
|
uri = "/a",
|
||||||
prefix = "/:param",
|
prefix = "/{param}",
|
||||||
expected = Some("/"),
|
expected = Some("/"),
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_2,
|
param_2,
|
||||||
uri = "/a/b",
|
uri = "/a/b",
|
||||||
prefix = "/:param",
|
prefix = "/{param}",
|
||||||
expected = Some("/b"),
|
expected = Some("/b"),
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_3,
|
param_3,
|
||||||
uri = "/b/a",
|
uri = "/b/a",
|
||||||
prefix = "/:param",
|
prefix = "/{param}",
|
||||||
expected = Some("/a"),
|
expected = Some("/a"),
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_4,
|
param_4,
|
||||||
uri = "/a/b",
|
uri = "/a/b",
|
||||||
prefix = "/a/:param",
|
prefix = "/a/{param}",
|
||||||
expected = Some("/"),
|
expected = Some("/"),
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(param_5, uri = "/b/a", prefix = "/a/:param", expected = None,);
|
test!(
|
||||||
|
param_5,
|
||||||
|
uri = "/b/a",
|
||||||
|
prefix = "/a/{param}",
|
||||||
|
expected = None,
|
||||||
|
);
|
||||||
|
|
||||||
test!(param_6, uri = "/a/b", prefix = "/:param/a", expected = None,);
|
test!(
|
||||||
|
param_6,
|
||||||
|
uri = "/a/b",
|
||||||
|
prefix = "/{param}/a",
|
||||||
|
expected = None,
|
||||||
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_7,
|
param_7,
|
||||||
uri = "/b/a",
|
uri = "/b/a",
|
||||||
prefix = "/:param/a",
|
prefix = "/{param}/a",
|
||||||
expected = Some("/"),
|
expected = Some("/"),
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_8,
|
param_8,
|
||||||
uri = "/a/b/c",
|
uri = "/a/b/c",
|
||||||
prefix = "/a/:param/c",
|
prefix = "/a/{param}/c",
|
||||||
expected = Some("/"),
|
expected = Some("/"),
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_9,
|
param_9,
|
||||||
uri = "/c/b/a",
|
uri = "/c/b/a",
|
||||||
prefix = "/a/:param/c",
|
prefix = "/a/{param}/c",
|
||||||
expected = None,
|
expected = None,
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_10,
|
param_10,
|
||||||
uri = "/a/",
|
uri = "/a/",
|
||||||
prefix = "/:param",
|
prefix = "/{param}",
|
||||||
expected = Some("/"),
|
expected = Some("/"),
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(param_11, uri = "/a", prefix = "/:param/", expected = None,);
|
test!(param_11, uri = "/a", prefix = "/{param}/", expected = None,);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
param_12,
|
param_12,
|
||||||
uri = "/a/",
|
uri = "/a/",
|
||||||
prefix = "/:param/",
|
prefix = "/{param}/",
|
||||||
expected = Some("/"),
|
expected = Some("/"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -83,9 +83,9 @@ async fn routing() {
|
||||||
"/users",
|
"/users",
|
||||||
get(|_: Request| async { "users#index" }).post(|_: Request| async { "users#create" }),
|
get(|_: Request| async { "users#index" }).post(|_: Request| async { "users#create" }),
|
||||||
)
|
)
|
||||||
.route("/users/:id", get(|_: Request| async { "users#show" }))
|
.route("/users/{id}", get(|_: Request| async { "users#show" }))
|
||||||
.route(
|
.route(
|
||||||
"/users/:id/action",
|
"/users/{id}/action",
|
||||||
get(|_: Request| async { "users#action" }),
|
get(|_: Request| async { "users#action" }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -289,7 +289,10 @@ async fn multiple_methods_for_one_handler() {
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn wildcard_sees_whole_url() {
|
async fn wildcard_sees_whole_url() {
|
||||||
let app = Router::new().route("/api/*rest", get(|uri: Uri| async move { uri.to_string() }));
|
let app = Router::new().route(
|
||||||
|
"/api/{*rest}",
|
||||||
|
get(|uri: Uri| async move { uri.to_string() }),
|
||||||
|
);
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -357,7 +360,7 @@ async fn with_and_without_trailing_slash() {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn wildcard_doesnt_match_just_trailing_slash() {
|
async fn wildcard_doesnt_match_just_trailing_slash() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/x/*path",
|
"/x/{*path}",
|
||||||
get(|Path(path): Path<String>| async move { path }),
|
get(|Path(path): Path<String>| async move { path }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -377,8 +380,8 @@ async fn wildcard_doesnt_match_just_trailing_slash() {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn what_matches_wildcard() {
|
async fn what_matches_wildcard() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/*key", get(|| async { "root" }))
|
.route("/{*key}", get(|| async { "root" }))
|
||||||
.route("/x/*key", get(|| async { "x" }))
|
.route("/x/{*key}", get(|| async { "x" }))
|
||||||
.fallback(|| async { "fallback" });
|
.fallback(|| async { "fallback" });
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
@ -406,7 +409,7 @@ async fn what_matches_wildcard() {
|
||||||
async fn static_and_dynamic_paths() {
|
async fn static_and_dynamic_paths() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/:key",
|
"/{key}",
|
||||||
get(|Path(key): Path<String>| async move { format!("dynamic: {key}") }),
|
get(|Path(key): Path<String>| async move { format!("dynamic: {key}") }),
|
||||||
)
|
)
|
||||||
.route("/foo", get(|| async { "static" }));
|
.route("/foo", get(|| async { "static" }));
|
||||||
|
@ -1054,3 +1057,19 @@ async fn impl_handler_for_into_response() {
|
||||||
assert_eq!(res.status(), StatusCode::CREATED);
|
assert_eq!(res.status(), StatusCode::CREATED);
|
||||||
assert_eq!(res.text().await, "thing created");
|
assert_eq!(res.text().await, "thing created");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
#[should_panic(
|
||||||
|
expected = "Path segments must not start with `:`. For capture groups, use `{capture}`. If you meant to literally match a segment starting with a colon, call `without_v07_checks` on the router."
|
||||||
|
)]
|
||||||
|
async fn colon_in_route() {
|
||||||
|
_ = Router::<()>::new().route("/:foo", get(|| async move {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
#[should_panic(
|
||||||
|
expected = "Path segments must not start with `*`. For wildcard capture, use `{*wildcard}`. If you meant to literally match a segment starting with an asterisk, call `without_v07_checks` on the router."
|
||||||
|
)]
|
||||||
|
async fn asterisk_in_route() {
|
||||||
|
_ = Router::<()>::new().route("/*foo", get(|| async move {}));
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ async fn nesting_apps() {
|
||||||
get(|| async { "users#index" }).post(|| async { "users#create" }),
|
get(|| async { "users#index" }).post(|| async { "users#create" }),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/users/:id",
|
"/users/{id}",
|
||||||
get(
|
get(
|
||||||
|params: extract::Path<HashMap<String, String>>| async move {
|
|params: extract::Path<HashMap<String, String>>| async move {
|
||||||
format!(
|
format!(
|
||||||
|
@ -22,7 +22,7 @@ async fn nesting_apps() {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/games/:id",
|
"/games/{id}",
|
||||||
get(
|
get(
|
||||||
|params: extract::Path<HashMap<String, String>>| async move {
|
|params: extract::Path<HashMap<String, String>>| async move {
|
||||||
format!(
|
format!(
|
||||||
|
@ -36,7 +36,7 @@ async fn nesting_apps() {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(|| async { "hi" }))
|
.route("/", get(|| async { "hi" }))
|
||||||
.nest("/:version/api", api_routes);
|
.nest("/{version}/api", api_routes);
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ async fn nested_multiple_routes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic = "Invalid route \"/\": insertion failed due to conflict with previously registered route: /*__private__axum_nest_tail_param"]
|
#[should_panic = "Invalid route \"/\": insertion failed due to conflict with previously registered route: /"]
|
||||||
fn nested_service_at_root_with_other_routes() {
|
fn nested_service_at_root_with_other_routes() {
|
||||||
let _: Router = Router::new()
|
let _: Router = Router::new()
|
||||||
.nest_service("/", Router::new().route("/users", get(|| async {})))
|
.nest_service("/", Router::new().route("/users", get(|| async {})))
|
||||||
|
@ -263,7 +263,7 @@ async fn multiple_top_level_nests() {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
|
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
|
||||||
async fn nest_cannot_contain_wildcards() {
|
async fn nest_cannot_contain_wildcards() {
|
||||||
_ = Router::<()>::new().nest("/one/*rest", Router::new());
|
_ = Router::<()>::new().nest("/one/{*rest}", Router::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
|
@ -317,11 +317,11 @@ async fn outer_middleware_still_see_whole_url() {
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn nest_at_capture() {
|
async fn nest_at_capture() {
|
||||||
let api_routes = Router::new().route(
|
let api_routes = Router::new().route(
|
||||||
"/:b",
|
"/{b}",
|
||||||
get(|Path((a, b)): Path<(String, String)>| async move { format!("a={a} b={b}") }),
|
get(|Path((a, b)): Path<(String, String)>| async move { format!("a={a} b={b}") }),
|
||||||
);
|
);
|
||||||
|
|
||||||
let app = Router::new().nest("/:a", api_routes);
|
let app = Router::new().nest("/{a}", api_routes);
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
@ -417,3 +417,19 @@ nested_route_test!(nest_9, nest = "/a", route = "/a/", expected = "/a/a/");
|
||||||
nested_route_test!(nest_11, nest = "/a/", route = "/", expected = "/a/");
|
nested_route_test!(nest_11, nest = "/a/", route = "/", expected = "/a/");
|
||||||
nested_route_test!(nest_12, nest = "/a/", route = "/a", expected = "/a/a");
|
nested_route_test!(nest_12, nest = "/a/", route = "/a", expected = "/a/a");
|
||||||
nested_route_test!(nest_13, nest = "/a/", route = "/a/", expected = "/a/a/");
|
nested_route_test!(nest_13, nest = "/a/", route = "/a/", expected = "/a/a/");
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
#[should_panic(
|
||||||
|
expected = "Path segments must not start with `:`. For capture groups, use `{capture}`. If you meant to literally match a segment starting with a colon, call `without_v07_checks` on the router."
|
||||||
|
)]
|
||||||
|
async fn colon_in_route() {
|
||||||
|
_ = Router::<()>::new().nest("/:foo", Router::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
#[should_panic(
|
||||||
|
expected = "Path segments must not start with `*`. For wildcard capture, use `{*wildcard}`. If you meant to literally match a segment starting with an asterisk, call `without_v07_checks` on the router."
|
||||||
|
)]
|
||||||
|
async fn asterisk_in_route() {
|
||||||
|
_ = Router::<()>::new().nest("/*foo", Router::new());
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ async fn main() {
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// build our application with a route
|
// build our application with a route
|
||||||
let app = Router::new().route("/users/:user_id/teams/:team_id", get(handler));
|
let app = Router::new().route("/users/{user_id}/teams/{team_id}", get(handler));
|
||||||
|
|
||||||
// run it
|
// run it
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||||
|
|
|
@ -52,14 +52,14 @@ async fn main() {
|
||||||
// Using trait objects is recommended unless you really need generics.
|
// Using trait objects is recommended unless you really need generics.
|
||||||
|
|
||||||
let using_dyn = Router::new()
|
let using_dyn = Router::new()
|
||||||
.route("/users/:id", get(get_user_dyn))
|
.route("/users/{id}", get(get_user_dyn))
|
||||||
.route("/users", post(create_user_dyn))
|
.route("/users", post(create_user_dyn))
|
||||||
.with_state(AppStateDyn {
|
.with_state(AppStateDyn {
|
||||||
user_repo: Arc::new(user_repo.clone()),
|
user_repo: Arc::new(user_repo.clone()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let using_generic = Router::new()
|
let using_generic = Router::new()
|
||||||
.route("/users/:id", get(get_user_generic::<InMemoryUserRepo>))
|
.route("/users/{id}", get(get_user_generic::<InMemoryUserRepo>))
|
||||||
.route("/users", post(create_user_generic::<InMemoryUserRepo>))
|
.route("/users", post(create_user_generic::<InMemoryUserRepo>))
|
||||||
.with_state(AppStateGeneric { user_repo });
|
.with_state(AppStateGeneric { user_repo });
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ async fn main() {
|
||||||
// Build our application by composing routes
|
// Build our application by composing routes
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/:key",
|
"/{key}",
|
||||||
// Add compression to `kv_get`
|
// Add compression to `kv_get`
|
||||||
get(kv_get.layer(CompressionLayer::new()))
|
get(kv_get.layer(CompressionLayer::new()))
|
||||||
// But don't compress `kv_set`
|
// But don't compress `kv_set`
|
||||||
|
@ -125,7 +125,7 @@ fn admin_routes() -> Router<SharedState> {
|
||||||
|
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/keys", delete(delete_all_keys))
|
.route("/keys", delete(delete_all_keys))
|
||||||
.route("/key/:key", delete(remove_key))
|
.route("/key/{key}", delete(remove_key))
|
||||||
// Require bearer auth for all admin routes
|
// Require bearer auth for all admin routes
|
||||||
.layer(ValidateRequestHeaderLayer::bearer("secret-token"))
|
.layer(ValidateRequestHeaderLayer::bearer("secret-token"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ async fn main() {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(show_form).post(accept_form))
|
.route("/", get(show_form).post(accept_form))
|
||||||
.route("/file/:file_name", post(save_request_body));
|
.route("/file/{file_name}", post(save_request_body));
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -25,7 +25,7 @@ async fn main() {
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// build our application with some routes
|
// build our application with some routes
|
||||||
let app = Router::new().route("/greet/:name", get(greet));
|
let app = Router::new().route("/greet/{name}", get(greet));
|
||||||
|
|
||||||
// run it
|
// run it
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
//!
|
//!
|
||||||
//! - `GET /todos`: return a JSON list of Todos.
|
//! - `GET /todos`: return a JSON list of Todos.
|
||||||
//! - `POST /todos`: create a new Todo.
|
//! - `POST /todos`: create a new Todo.
|
||||||
//! - `PATCH /todos/:id`: update a specific Todo.
|
//! - `PATCH /todos/{id}`: update a specific Todo.
|
||||||
//! - `DELETE /todos/:id`: delete a specific Todo.
|
//! - `DELETE /todos/{id}`: delete a specific Todo.
|
||||||
//!
|
//!
|
||||||
//! Run with
|
//! Run with
|
||||||
//!
|
//!
|
||||||
|
@ -48,7 +48,7 @@ async fn main() {
|
||||||
// Compose the routes
|
// Compose the routes
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/todos", get(todos_index).post(todos_create))
|
.route("/todos", get(todos_index).post(todos_create))
|
||||||
.route("/todos/:id", patch(todos_update).delete(todos_delete))
|
.route("/todos/{id}", patch(todos_update).delete(todos_delete))
|
||||||
// Add middleware to all routes
|
// Add middleware to all routes
|
||||||
.layer(
|
.layer(
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
|
|
|
@ -25,7 +25,7 @@ async fn main() {
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// build our application with some routes
|
// build our application with some routes
|
||||||
let app = Router::new().route("/:version/foo", get(handler));
|
let app = Router::new().route("/{version}/foo", get(handler));
|
||||||
|
|
||||||
// run it
|
// run it
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||||
|
|
Loading…
Reference in a new issue