mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-27 00:00:24 +02:00
"matchit" based router (#363)
* "matchit" based router * Update changelog * Remove dependency on `regex` * Docs * Fix typos * Also mention route order in root module docs * Update CHANGELOG.md Co-authored-by: Jonas Platte <[email protected]> * Document that `/:key` and `/foo` overlaps * Provide good error message for wildcards in routes * minor clean ups * Make `Router` cheaper to clone * Ensure middleware still only applies to routes above * Remove call to issues from changelog We're aware of the short coming :) * Fix tests on 1.51 Co-authored-by: Jonas Platte <[email protected]>
This commit is contained in:
co-authored by
Jonas Platte
parent
9fcc884374
commit
1a78a3f224
+4
-21
@@ -1,31 +1,14 @@
|
||||
#![allow(unused_imports, dead_code)]
|
||||
|
||||
use crate::BoxError;
|
||||
use crate::{
|
||||
extract,
|
||||
handler::{any, delete, get, on, patch, post, Handler},
|
||||
response::IntoResponse,
|
||||
routing::MethodFilter,
|
||||
service, Router,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use http::{
|
||||
header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION},
|
||||
Method, Request, StatusCode, Uri,
|
||||
header::{HeaderName, HeaderValue},
|
||||
Request, StatusCode,
|
||||
};
|
||||
use hyper::{Body, Server};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::future::Ready;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::{Infallible, TryFrom},
|
||||
future::ready,
|
||||
convert::TryFrom,
|
||||
net::{SocketAddr, TcpListener},
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
use tower::{make::Shared, service_fn};
|
||||
use tower::make::Shared;
|
||||
use tower_service::Service;
|
||||
|
||||
pub(crate) struct TestClient {
|
||||
|
||||
+28
-23
@@ -25,6 +25,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use tower::service_fn;
|
||||
use tower::timeout::TimeoutLayer;
|
||||
use tower_service::Service;
|
||||
|
||||
pub(crate) use helpers::*;
|
||||
@@ -479,29 +480,6 @@ async fn handler_into_service() {
|
||||
assert_eq!(res.text().await, "you said: hi there!");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn when_multiple_routes_match() {
|
||||
let app = Router::new()
|
||||
.route("/", post(|| async {}))
|
||||
.route("/", get(|| async {}))
|
||||
.route("/foo", get(|| async {}))
|
||||
.nest("/foo", Router::new().route("/bar", get(|| async {})));
|
||||
|
||||
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::OK);
|
||||
|
||||
let res = client.get("/foo/bar").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
||||
let res = client.get("/foo").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn captures_dont_match_empty_segments() {
|
||||
let app = Router::new().route("/:key", get(|| async {}));
|
||||
@@ -539,6 +517,33 @@ async fn json_content_types() {
|
||||
assert!(!valid_json_content_type("text/json").await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn wildcard_sees_whole_url() {
|
||||
let app = Router::new().route("/api/*rest", get(|uri: Uri| async move { uri.to_string() }));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/api/foo/bar").send().await;
|
||||
assert_eq!(res.text().await, "/api/foo/bar");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn middleware_applies_to_routes_above() {
|
||||
let app = Router::new()
|
||||
.route("/one", get(std::future::pending::<()>))
|
||||
.layer(TimeoutLayer::new(Duration::new(0, 0)))
|
||||
.handle_error(|_: BoxError| Ok::<_, Infallible>(StatusCode::REQUEST_TIMEOUT))
|
||||
.route("/two", get(|| async {}));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/one").send().await;
|
||||
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
|
||||
|
||||
let res = client.get("/two").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
pub(crate) fn assert_send<T: Send>() {}
|
||||
pub(crate) fn assert_sync<T: Sync>() {}
|
||||
pub(crate) fn assert_unpin<T: Unpin>() {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::body::box_body;
|
||||
use crate::routing::EmptyRouter;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -13,6 +14,7 @@ async fn nesting_apps() {
|
||||
"/users/:id",
|
||||
get(
|
||||
|params: extract::Path<HashMap<String, String>>| async move {
|
||||
dbg!(¶ms);
|
||||
format!(
|
||||
"{}: users#show ({})",
|
||||
params.get("version").unwrap(),
|
||||
@@ -179,3 +181,79 @@ async fn nest_static_file_server() {
|
||||
let res = client.get("/static/README.md").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::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");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn nested_with_other_route_also_matching_with_route_first() {
|
||||
let app = Router::new().route("/api", get(|| async { "api" })).nest(
|
||||
"/api",
|
||||
Router::new()
|
||||
.route("/users", get(|| async { "users" }))
|
||||
.route("/teams", get(|| async { "teams" })),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
assert_eq!(client.get("/api").send().await.text().await, "api");
|
||||
assert_eq!(client.get("/api/users").send().await.text().await, "users");
|
||||
assert_eq!(client.get("/api/teams").send().await.text().await, "teams");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn nested_with_other_route_also_matching_with_route_last() {
|
||||
let app = Router::new()
|
||||
.nest(
|
||||
"/api",
|
||||
Router::new()
|
||||
.route("/users", get(|| async { "users" }))
|
||||
.route("/teams", get(|| async { "teams" })),
|
||||
)
|
||||
.route("/api", get(|| async { "api" }));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
assert_eq!(client.get("/api").send().await.text().await, "api");
|
||||
assert_eq!(client.get("/api/users").send().await.text().await, "users");
|
||||
assert_eq!(client.get("/api/teams").send().await.text().await, "teams");
|
||||
}
|
||||
|
||||
#[tokio::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");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
|
||||
async fn nest_cannot_contain_wildcards() {
|
||||
Router::<EmptyRouter>::new().nest("/one/*rest", Router::<EmptyRouter>::new());
|
||||
}
|
||||
|
||||
+10
-10
@@ -80,19 +80,19 @@ async fn multiple_ors_balanced_differently() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn or_nested_inside_other_thing() {
|
||||
let inner = Router::new()
|
||||
.route("/bar", get(|| async {}))
|
||||
.or(Router::new().route("/baz", get(|| async {})));
|
||||
let app = Router::new().nest("/foo", inner);
|
||||
async fn nested_or() {
|
||||
let bar = Router::new().route("/bar", get(|| async { "bar" }));
|
||||
let baz = Router::new().route("/baz", get(|| async { "baz" }));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
let bar_or_baz = bar.or(baz);
|
||||
|
||||
let res = client.get("/foo/bar").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let client = TestClient::new(bar_or_baz.clone());
|
||||
assert_eq!(client.get("/bar").send().await.text().await, "bar");
|
||||
assert_eq!(client.get("/baz").send().await.text().await, "baz");
|
||||
|
||||
let res = client.get("/foo/baz").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let client = TestClient::new(Router::new().nest("/foo", bar_or_baz));
|
||||
assert_eq!(client.get("/foo/bar").send().await.text().await, "bar");
|
||||
assert_eq!(client.get("/foo/baz").send().await.text().await, "baz");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user