blob: 0544f8be59b8ee86a3bbf6a9ded90ba9ddd7eedb [file] [log] [blame]
use super::*;
use crate::{body::boxed, extract::Extension};
use std::collections::HashMap;
use tower_http::services::ServeDir;
#[crate::test]
async fn nesting_apps() {
let api_routes = Router::new()
.route(
"/users",
get(|| async { "users#index" }).post(|| async { "users#create" }),
)
.route(
"/users/:id",
get(
|params: extract::Path<HashMap<String, String>>| async move {
format!(
"{}: users#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
},
),
)
.route(
"/games/:id",
get(
|params: extract::Path<HashMap<String, String>>| async move {
format!(
"{}: games#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
},
),
);
let app = Router::new()
.route("/", get(|| async { "hi" }))
.nest("/:version/api", api_routes);
let client = TestClient::new(app);
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "hi");
let res = client.get("/v0/api/users").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "users#index");
let res = client.get("/v0/api/users/123").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "v0: users#show (123)");
let res = client.get("/v0/api/games/123").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "v0: games#show (123)");
}
#[crate::test]
async fn wrong_method_nest() {
let nested_app = Router::new().route("/", get(|| async {}));
let app = Router::new().nest("/", nested_app);
let client = TestClient::new(app);
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.post("/").send().await;
assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
assert_eq!(res.headers()[ALLOW], "GET,HEAD");
let res = client.patch("/foo").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[crate::test]
async fn nesting_router_at_root() {
let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() }));
let app = Router::new().nest("/", nested);
let client = TestClient::new(app);
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/foo").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/foo");
let res = client.get("/foo/bar").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[crate::test]
async fn nesting_router_at_empty_path() {
let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() }));
let app = Router::new().nest("", nested);
let client = TestClient::new(app);
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/foo").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/foo");
let res = client.get("/foo/bar").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[crate::test]
async fn nesting_handler_at_root() {
let app = Router::new().nest_service("/", get(|uri: Uri| async move { uri.to_string() }));
let client = TestClient::new(app);
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/");
let res = client.get("/foo").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/foo");
let res = client.get("/foo/bar").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/foo/bar");
}
#[crate::test]
async fn nested_url_extractor() {
let app = Router::new().nest(
"/foo",
Router::new().nest(
"/bar",
Router::new()
.route("/baz", get(|uri: Uri| async move { uri.to_string() }))
.route(
"/qux",
get(|req: Request<Body>| async move { req.uri().to_string() }),
),
),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/baz");
let res = client.get("/foo/bar/qux").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/qux");
}
#[crate::test]
async fn nested_url_original_extractor() {
let app = Router::new().nest(
"/foo",
Router::new().nest(
"/bar",
Router::new().route(
"/baz",
get(|uri: extract::OriginalUri| async move { uri.0.to_string() }),
),
),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/foo/bar/baz");
}
#[crate::test]
async fn nested_service_sees_stripped_uri() {
let app = Router::new().nest(
"/foo",
Router::new().nest(
"/bar",
Router::new().route_service(
"/baz",
service_fn(|req: Request<Body>| async move {
let body = boxed(Body::from(req.uri().to_string()));
Ok::<_, Infallible>(Response::new(body))
}),
),
),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/baz");
}
#[crate::test]
async fn nest_static_file_server() {
let app = Router::new().nest_service("/static", ServeDir::new("."));
let client = TestClient::new(app);
let res = client.get("/static/README.md").send().await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn nested_multiple_routes() {
let app = Router::new()
.nest(
"/api",
Router::new()
.route("/users", get(|| async { "users" }))
.route("/teams", get(|| async { "teams" })),
)
.route("/", get(|| async { "root" }));
let client = TestClient::new(app);
assert_eq!(client.get("/").send().await.text().await, "root");
assert_eq!(client.get("/api/users").send().await.text().await, "users");
assert_eq!(client.get("/api/teams").send().await.text().await, "teams");
}
#[test]
#[should_panic = "Invalid route \"/\": insertion failed due to conflict with previously registered route: /*__private__axum_nest_tail_param"]
fn nested_service_at_root_with_other_routes() {
let _: Router = Router::new()
.nest_service("/", Router::new().route("/users", get(|| async {})))
.route("/", get(|| async {}));
}
#[test]
fn nested_at_root_with_other_routes() {
let _: Router = Router::new()
.nest("/", Router::new().route("/users", get(|| async {})))
.route("/", get(|| async {}));
}
#[crate::test]
async fn multiple_top_level_nests() {
let app = Router::new()
.nest(
"/one",
Router::new().route("/route", get(|| async { "one" })),
)
.nest(
"/two",
Router::new().route("/route", get(|| async { "two" })),
);
let client = TestClient::new(app);
assert_eq!(client.get("/one/route").send().await.text().await, "one");
assert_eq!(client.get("/two/route").send().await.text().await, "two");
}
#[crate::test]
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
async fn nest_cannot_contain_wildcards() {
_ = Router::<(), Body>::new().nest("/one/*rest", Router::new());
}
#[crate::test]
async fn outer_middleware_still_see_whole_url() {
#[derive(Clone)]
struct SetUriExtension<S>(S);
#[derive(Clone)]
struct Uri(http::Uri);
impl<S, B> Service<Request<B>> for SetUriExtension<S>
where
S: Service<Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, mut req: Request<B>) -> Self::Future {
let uri = Uri(req.uri().clone());
req.extensions_mut().insert(uri);
self.0.call(req)
}
}
async fn handler(Extension(Uri(middleware_uri)): Extension<Uri>) -> impl IntoResponse {
middleware_uri.to_string()
}
let app = Router::new()
.route("/", get(handler))
.route("/foo", get(handler))
.route("/foo/bar", get(handler))
.nest("/one", Router::new().route("/two", get(handler)))
.fallback(handler)
.layer(tower::layer::layer_fn(SetUriExtension));
let client = TestClient::new(app);
assert_eq!(client.get("/").send().await.text().await, "/");
assert_eq!(client.get("/foo").send().await.text().await, "/foo");
assert_eq!(client.get("/foo/bar").send().await.text().await, "/foo/bar");
assert_eq!(
client.get("/not-found").send().await.text().await,
"/not-found"
);
assert_eq!(client.get("/one/two").send().await.text().await, "/one/two");
}
#[crate::test]
async fn nest_at_capture() {
let api_routes = Router::new().route(
"/:b",
get(|Path((a, b)): Path<(String, String)>| async move { format!("a={a} b={b}") }),
);
let app = Router::new().nest("/:a", api_routes);
let client = TestClient::new(app);
let res = client.get("/foo/bar").send().await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "a=foo b=bar");
}
#[crate::test]
async fn nest_with_and_without_trailing() {
let app = Router::new().nest_service("/foo", 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::OK);
let res = client.get("/foo/bar").send().await;
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn nesting_with_root_inner_router() {
let app = Router::new()
.nest_service("/service", Router::new().route("/", get(|| async {})))
.nest("/router", Router::new().route("/", get(|| async {})))
.nest("/router-slash/", Router::new().route("/", get(|| async {})));
let client = TestClient::new(app);
// `/service/` does match the `/service` prefix and the remaining path is technically
// empty, which is the same as `/` which matches `.route("/", _)`
let res = client.get("/service").send().await;
assert_eq!(res.status(), StatusCode::OK);
// `/service/` does match the `/service` prefix and the remaining path is `/`
// which matches `.route("/", _)`
//
// this is perhaps a little surprising but don't think there is much we can do
let res = client.get("/service/").send().await;
assert_eq!(res.status(), StatusCode::OK);
// at least it does work like you'd expect when using `nest`
let res = client.get("/router").send().await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/router/").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/router-slash").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/router-slash/").send().await;
assert_eq!(res.status(), StatusCode::OK);
}
macro_rules! nested_route_test {
(
$name:ident,
// the path we nest the inner router at
nest = $nested_path:literal,
// the route the inner router accepts
route = $route_path:literal,
// the route we expect to be able to call
expected = $expected_path:literal $(,)?
) => {
#[crate::test]
async fn $name() {
let inner = Router::new().route($route_path, get(|| async {}));
let app = Router::new().nest($nested_path, inner);
let client = TestClient::new(app);
let res = client.get($expected_path).send().await;
let status = res.status();
assert_eq!(status, StatusCode::OK, "Router");
}
};
}
// test cases taken from https://github.com/tokio-rs/axum/issues/714#issuecomment-1058144460
nested_route_test!(nest_1, nest = "", route = "/", expected = "/");
nested_route_test!(nest_2, nest = "", route = "/a", expected = "/a");
nested_route_test!(nest_3, nest = "", route = "/a/", expected = "/a/");
nested_route_test!(nest_4, nest = "/", route = "/", expected = "/");
nested_route_test!(nest_5, nest = "/", route = "/a", expected = "/a");
nested_route_test!(nest_6, nest = "/", route = "/a/", expected = "/a/");
nested_route_test!(nest_7, nest = "/a", route = "/", expected = "/a");
nested_route_test!(nest_8, nest = "/a", route = "/a", expected = "/a/a");
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_12, nest = "/a/", route = "/a", expected = "/a/a");
nested_route_test!(nest_13, nest = "/a/", route = "/a/", expected = "/a/a/");