mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-27 00:00:24 +02:00
Remove trailing slash redirects (#1119)
* Prepare axum-next branch * Remove deprecated `extractor_middleware` function (#1077) * Allow `Error: Into<Infallible>` for `Route::{layer, route_layer}` (#948) * Allow `Error: Into<Infallible>` for `Route::{layer, route_layer}` (#924) * Allow `Error: Into<Infallible>` for `Route::{layer, route_layer}` Fixes https://github.com/tokio-rs/axum/issues/922 * changelog * fixup changelog * Panic on overlapping routes in `MethodRouter` (#1102) * Panic on overlapping routes in `MethodRouter` * changelog link * add test to ensure `head` and `get` don't overlap * Fix changelog * Prepare axum-next branch * Remove trailing slash redirects * changelog link * Fix changelog * remove asserting to make make the test more clear * remove tsr related feature * Add `RouterExt::route_with_tsr` * Apply suggestions from code review Co-authored-by: Jonas Platte <[email protected]> * Update axum-extra/src/routing/mod.rs Co-authored-by: Jonas Platte <[email protected]> * fix typos in docs * Update axum/CHANGELOG.md Co-authored-by: Jonas Platte <[email protected]> * mention `RouterExt::route_with_tsr` in the changelog Co-authored-by: Jonas Platte <[email protected]>
This commit is contained in:
co-authored by
Jonas Platte
parent
23808f72a2
commit
a4c820420d
@@ -1,6 +1,13 @@
|
||||
//! Additional types for defining routes.
|
||||
|
||||
use axum::{handler::Handler, Router};
|
||||
use axum::{
|
||||
handler::Handler,
|
||||
http::Request,
|
||||
response::{Redirect, Response},
|
||||
Router,
|
||||
};
|
||||
use std::{convert::Infallible, future::ready};
|
||||
use tower_service::Service;
|
||||
|
||||
mod resource;
|
||||
|
||||
@@ -126,6 +133,37 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
H: Handler<T, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
/// Add another route to the router with an additional "trailing slash redirect" route.
|
||||
///
|
||||
/// If you add a route _without_ a trailing slash, such as `/foo`, this method will also add a
|
||||
/// route for `/foo/` that redirects to `/foo`.
|
||||
///
|
||||
/// If you add a route _with_ a trailing slash, such as `/bar/`, this method will also add a
|
||||
/// route for `/bar` that redirects to `/bar/`.
|
||||
///
|
||||
/// This is similar to what axum 0.5.x did by default, except this explicitly adds another
|
||||
/// route, so trying to add a `/foo/` route after calling `.route_with_tsr("/foo", /* ... */)`
|
||||
/// will result in a panic due to route overlap.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{Router, routing::get};
|
||||
/// use axum_extra::routing::RouterExt;
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// // `/foo/` will redirect to `/foo`
|
||||
/// .route_with_tsr("/foo", get(|| async {}))
|
||||
/// // `/bar` will redirect to `/bar/`
|
||||
/// .route_with_tsr("/bar/", get(|| async {}));
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
fn route_with_tsr<T>(self, path: &str, service: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Future: Send + 'static,
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<B> RouterExt<B> for Router<B>
|
||||
@@ -211,9 +249,62 @@ where
|
||||
{
|
||||
self.route(P::PATH, axum::routing::trace(handler))
|
||||
}
|
||||
|
||||
fn route_with_tsr<T>(mut self, path: &str, service: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Future: Send + 'static,
|
||||
Self: Sized,
|
||||
{
|
||||
self = self.route(path, service);
|
||||
|
||||
let redirect = Redirect::permanent(path);
|
||||
|
||||
if let Some(path_without_trailing_slash) = path.strip_suffix('/') {
|
||||
self.route(
|
||||
path_without_trailing_slash,
|
||||
(move || ready(redirect.clone())).into_service(),
|
||||
)
|
||||
} else {
|
||||
self.route(
|
||||
&format!("{}/", path),
|
||||
(move || ready(redirect.clone())).into_service(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl<B> Sealed for axum::Router<B> {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_helpers::*;
|
||||
use axum::{http::StatusCode, routing::get};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tsr() {
|
||||
let app = Router::new()
|
||||
.route_with_tsr("/foo", get(|| async {}))
|
||||
.route_with_tsr("/bar/", get(|| async {}));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/foo").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
||||
let res = client.get("/foo/").send().await;
|
||||
assert_eq!(res.status(), StatusCode::PERMANENT_REDIRECT);
|
||||
assert_eq!(res.headers()["location"], "/foo");
|
||||
|
||||
let res = client.get("/bar/").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
||||
let res = client.get("/bar").send().await;
|
||||
assert_eq!(res.status(), StatusCode::PERMANENT_REDIRECT);
|
||||
assert_eq!(res.headers()["location"], "/bar/");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user