mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-07 00:00:12 +02:00
Bump matchit to 0.9.2 (#3702)
The new version now supports prefix and suffix captures such as `/{file}.png`, `/avatar.{extension}`, and `/user-{id}.png`.
Co-authored-by: David Mládek <[email protected]>
This commit is contained in:
co-authored by
David Mládek
parent
67ce49aa52
commit
f31dcd3c1e
Generated
+2
-2
@@ -976,9 +976,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
version = "0.8.4"
|
version = "0.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
checksum = "8863b587001c1b9a8a4e36008cebc6b3612cb1226fe2de94858e06092687b608"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- **changed:** `serve` has an additional generic argument and can now work with any response body
|
- **changed:** `serve` has an additional generic argument and can now work with any response body
|
||||||
type, not just `axum::body::Body` ([#3205])
|
type, not just `axum::body::Body` ([#3205])
|
||||||
- **changed:** `Redirect` constructors now accept any `impl Into<String>` ([#3635])
|
- **changed:** `Redirect` constructors now accept any `impl Into<String>` ([#3635])
|
||||||
|
- **changed:** Updated `matchit` allowing for routes with captures and static prefixes and suffixes ([#3702])
|
||||||
|
|
||||||
[#3158]: https://github.com/tokio-rs/axum/pull/3158
|
[#3158]: https://github.com/tokio-rs/axum/pull/3158
|
||||||
[#3261]: https://github.com/tokio-rs/axum/pull/3261
|
[#3261]: https://github.com/tokio-rs/axum/pull/3261
|
||||||
@@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
[#3601]: https://github.com/tokio-rs/axum/pull/3601
|
[#3601]: https://github.com/tokio-rs/axum/pull/3601
|
||||||
[#3489]: https://github.com/tokio-rs/axum/pull/3489
|
[#3489]: https://github.com/tokio-rs/axum/pull/3489
|
||||||
[#3586]: https://github.com/tokio-rs/axum/pull/3586
|
[#3586]: https://github.com/tokio-rs/axum/pull/3586
|
||||||
|
[#3702]: https://github.com/tokio-rs/axum/pull/3702
|
||||||
|
|
||||||
# 0.8.9
|
# 0.8.9
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -111,7 +111,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.8.4"
|
matchit = "=0.9.2"
|
||||||
memchr = "2.4.1"
|
memchr = "2.4.1"
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Add another route to the router.
|
Add another route to the router.
|
||||||
|
|
||||||
`path` is a string of path segments separated by `/`. Each segment
|
`path` is a string of path segments separated by `/`. Each segment
|
||||||
can be either static, a capture, or a wildcard.
|
can either be static, contain a capture, or be a wildcard.
|
||||||
|
|
||||||
`method_router` is the [`MethodRouter`] that should receive the request if the
|
`method_router` is the [`MethodRouter`] that should receive the request if the
|
||||||
path matches `path`. Usually, `method_router` will be a handler wrapped in a method
|
path matches `path`. Usually, `method_router` will be a handler wrapped in a method
|
||||||
@@ -24,11 +24,15 @@ 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 `//`.
|
||||||
|
|
||||||
|
Each segment may have only one capture, but it may have static prefixes and suffixes.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- `/{key}`
|
- `/{key}`
|
||||||
- `/users/{id}`
|
- `/users/{id}`
|
||||||
- `/users/{id}/tweets`
|
- `/users/{id}/tweets`
|
||||||
|
- `/avatars/{id}.jpg`
|
||||||
|
- `/avatars/{id}.png`
|
||||||
|
|
||||||
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.
|
||||||
@@ -38,6 +42,25 @@ regular expression. You must handle that manually in your handlers.
|
|||||||
|
|
||||||
[`MatchedPath`] can be used to extract the matched path rather than the actual path.
|
[`MatchedPath`] can be used to extract the matched path rather than the actual path.
|
||||||
|
|
||||||
|
Captures must not be empty. For example `/a/` will not match `/a/{capture}` and
|
||||||
|
`/.png` will not match `/{image}.png`.
|
||||||
|
|
||||||
|
You may have either capture(s) with static prefixes, capture(s) with suffixes, or a single
|
||||||
|
capture with both prefix and suffix, but these kinds of captures may not be mixed. You may mix
|
||||||
|
these with static routes and a standalone capture though. If multiple patterns match, static
|
||||||
|
segment takes precedence, then the capture with longest static prefix or suffix.
|
||||||
|
|
||||||
|
Example valid mixed route sets:
|
||||||
|
- `/logo.png`, `/author.jpg`, `/{id}.png`, `/{id}.jpg`, `/{other_file}` (but you may not add `/old-{id}.png` or `/post-{id}`).
|
||||||
|
- `/logo.png`, `/avatar-{id}.jpg`, `/{other_file}` (but you may not add `/{id}.jpg`, `/avatar-{id}.png`).
|
||||||
|
|
||||||
|
This is done on each level of the path and if the path matches even if due to a wildcard, that path
|
||||||
|
will be chosen. For example if one makes a request to `/foobar/baz` the first route will be used by
|
||||||
|
axum because it has better match on the leftmost differing path segment and the whole path matches.
|
||||||
|
|
||||||
|
- `/foobar/{*wildcard}`
|
||||||
|
- `/foo{wildcard}/baz`
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -294,6 +294,47 @@ mod tests {
|
|||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn can_extract_nested_matched_path_with_prefix_in_middleware_on_nested_router() {
|
||||||
|
async fn extract_matched_path<B>(matched_path: MatchedPath, req: Request<B>) -> Request<B> {
|
||||||
|
assert_eq!(matched_path.as_str(), "/foo{one}/bar{two}");
|
||||||
|
req
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = Router::new().nest(
|
||||||
|
"/foo{one}",
|
||||||
|
Router::new()
|
||||||
|
.route("/bar{two}", get(|| async move {}))
|
||||||
|
.layer(map_request(extract_matched_path)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/foo1/bar2").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn can_extract_nested_matched_path_with_prefix_and_suffix_in_middleware_on_nested_router()
|
||||||
|
{
|
||||||
|
async fn extract_matched_path<B>(matched_path: MatchedPath, req: Request<B>) -> Request<B> {
|
||||||
|
assert_eq!(matched_path.as_str(), "/foo{one}foo/bar{two}bar");
|
||||||
|
req
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = Router::new().nest(
|
||||||
|
"/foo{one}foo",
|
||||||
|
Router::new()
|
||||||
|
.route("/bar{two}bar", get(|| async move {}))
|
||||||
|
.layer(map_request(extract_matched_path)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/foo1foo/bar2bar").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
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> {
|
||||||
|
|||||||
@@ -857,6 +857,27 @@ mod tests {
|
|||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn deserialize_into_vec_of_tuples_with_prefixes_and_suffixes() {
|
||||||
|
let app = Router::new().route(
|
||||||
|
"/f{o}o/b{a}r",
|
||||||
|
get(|Path(params): Path<Vec<(String, String)>>| async move {
|
||||||
|
assert_eq!(
|
||||||
|
params,
|
||||||
|
vec![
|
||||||
|
("o".to_owned(), "0".to_owned()),
|
||||||
|
("a".to_owned(), "4".to_owned())
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/f0o/b4r").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn type_that_uses_deserialize_any() {
|
async fn type_that_uses_deserialize_any() {
|
||||||
use time::Date;
|
use time::Date;
|
||||||
|
|||||||
@@ -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 is_capture(prefix_segment) || path_segment == prefix_segment {
|
if prefix_matches(prefix_segment, path_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,12 +148,67 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_capture(segment: &str) -> bool {
|
fn prefix_matches(prefix_segment: &str, path_segment: &str) -> bool {
|
||||||
segment.starts_with('{')
|
if let Some((prefix, suffix)) = capture_prefix_suffix(prefix_segment) {
|
||||||
&& segment.ends_with('}')
|
path_segment.starts_with(prefix) && path_segment.ends_with(suffix)
|
||||||
&& !segment.starts_with("{{")
|
} else {
|
||||||
&& !segment.ends_with("}}")
|
prefix_segment == path_segment
|
||||||
&& !segment.starts_with("{*")
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a segment and returns prefix and suffix of the path, omitting the capture. Currently,
|
||||||
|
/// matchit supports only one capture so this can be a pair. If there is no capture, `None` is
|
||||||
|
/// returned.
|
||||||
|
fn capture_prefix_suffix(segment: &str) -> Option<(&str, &str)> {
|
||||||
|
fn find_first_not_double(needle: u8, haystack: &[u8]) -> Option<usize> {
|
||||||
|
let mut possible_capture = 0;
|
||||||
|
while let Some(index) = haystack
|
||||||
|
.get(possible_capture..)
|
||||||
|
.and_then(|haystack| haystack.iter().position(|byte| byte == &needle))
|
||||||
|
{
|
||||||
|
let index = index + possible_capture;
|
||||||
|
|
||||||
|
if haystack.get(index + 1) == Some(&needle) {
|
||||||
|
possible_capture = index + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
let capture_start = find_first_not_double(b'{', segment.as_bytes())?;
|
||||||
|
|
||||||
|
let Some(capture_end) = find_first_not_double(b'}', segment.as_bytes()) else {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
panic!(
|
||||||
|
"Segment `{segment}` is malformed. It seems to contain a capture start but no \
|
||||||
|
capture end. This should have been rejected at application start, please file a \
|
||||||
|
bug in axum repository."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// This is very bad but let's not panic in production. This will most likely not match.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if capture_start > capture_end {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
panic!(
|
||||||
|
"Segment `{segment}` is malformed. It seems to contain a capture start after \
|
||||||
|
capture end. This should have been rejected at application start, please file a \
|
||||||
|
bug in axum repository."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// This is very bad but let's not panic in production. This will most likely not match.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slicing may panic but we found the indexes inside the string so this should be fine.
|
||||||
|
Some((&segment[..capture_start], &segment[capture_end + 1..]))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -434,6 +434,114 @@ async fn what_matches_wildcard() {
|
|||||||
assert_eq!(get("/x/a/b/").await, "x");
|
assert_eq!(get("/x/a/b/").await, "x");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn prefix_match() {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/{picture}.png", get(|| async { "picture" }))
|
||||||
|
.route("/{picture}.txt", get(|| async { "text" }))
|
||||||
|
.route("/logo.svg", get(|| async { "logo" }))
|
||||||
|
.fallback(|| async { "fallback" });
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let get = |path| {
|
||||||
|
let f = client.get(path);
|
||||||
|
async move { f.await.text().await }
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(get("/").await, "fallback");
|
||||||
|
assert_eq!(get("/a/b.png").await, "fallback");
|
||||||
|
assert_eq!(get("/a.png/").await, "fallback");
|
||||||
|
assert_eq!(get("//a.png").await, "fallback");
|
||||||
|
|
||||||
|
// Empty capture is not allowed
|
||||||
|
assert_eq!(get("/.png").await, "fallback");
|
||||||
|
assert_eq!(get("/..png").await, "picture");
|
||||||
|
assert_eq!(get("/a.png").await, "picture");
|
||||||
|
assert_eq!(get("/b.png").await, "picture");
|
||||||
|
|
||||||
|
assert_eq!(get("/.txt").await, "fallback");
|
||||||
|
assert_eq!(get("/..txt").await, "text");
|
||||||
|
assert_eq!(get("/a.txt").await, "text");
|
||||||
|
assert_eq!(get("/b.txt").await, "text");
|
||||||
|
|
||||||
|
assert_eq!(get("/logo.svg").await, "logo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn suffix_match() {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/new-{id}", get(|| async { "new" }))
|
||||||
|
.route("/old-{id}", get(|| async { "old" }))
|
||||||
|
.route("/any", get(|| async { "any" }))
|
||||||
|
.fallback(|| async { "fallback" });
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let get = |path| {
|
||||||
|
let f = client.get(path);
|
||||||
|
async move { f.await.text().await }
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(get("/").await, "fallback");
|
||||||
|
assert_eq!(get("/a/new-1").await, "fallback");
|
||||||
|
assert_eq!(get("/new-1/").await, "fallback");
|
||||||
|
assert_eq!(get("//new-1/").await, "fallback");
|
||||||
|
|
||||||
|
// Empty capture is not allowed
|
||||||
|
assert_eq!(get("/new-").await, "fallback");
|
||||||
|
assert_eq!(get("/new-1").await, "new");
|
||||||
|
|
||||||
|
assert_eq!(get("/old-").await, "fallback");
|
||||||
|
assert_eq!(get("/old-1").await, "old");
|
||||||
|
|
||||||
|
assert_eq!(get("/any").await, "any");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn prefix_suffix_match() {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/start-{regex}-end", get(|| async { "regex" }))
|
||||||
|
.fallback(|| async { "fallback" });
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let get = |path| {
|
||||||
|
let f = client.get(path);
|
||||||
|
async move { f.await.text().await }
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(get("/").await, "fallback");
|
||||||
|
|
||||||
|
// Empty capture is not allowed
|
||||||
|
assert_eq!(get("/start--end").await, "fallback");
|
||||||
|
|
||||||
|
assert_eq!(get("/start-regex-end").await, "regex");
|
||||||
|
assert_eq!(get("/foo/start-regex-end").await, "fallback");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn prefix_suffix_nested_match() {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/{a}/a", get(|| async { "a" }))
|
||||||
|
.route("/{b}/b", get(|| async { "b" }))
|
||||||
|
.route("/a{c}c/a", get(|| async { "c" }))
|
||||||
|
.route("/a{d}c/{*anything}", get(|| async { "d" }))
|
||||||
|
.fallback(|| async { "fallback" });
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let get = |path| {
|
||||||
|
let f = client.get(path);
|
||||||
|
async move { f.await.text().await }
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(get("/ac/a").await, "a");
|
||||||
|
assert_eq!(get("/ac/b").await, "b");
|
||||||
|
assert_eq!(get("/abc/a").await, "c");
|
||||||
|
assert_eq!(get("/abc/b").await, "d");
|
||||||
|
}
|
||||||
|
|
||||||
#[should_panic(
|
#[should_panic(
|
||||||
expected = "Invalid route \"/{*wild}\": Insertion failed due to conflict with previously registered route: /{*__private__axum_fallback}"
|
expected = "Invalid route \"/{*wild}\": Insertion failed due to conflict with previously registered route: /{*__private__axum_fallback}"
|
||||||
)]
|
)]
|
||||||
@@ -444,6 +552,42 @@ fn colliding_fallback_with_wildcard() {
|
|||||||
.route("/{*wild}", get(|| async { "wildcard" }));
|
.route("/{*wild}", get(|| async { "wildcard" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[should_panic(
|
||||||
|
expected = "Invalid route \"/{wild}-bar\": Insertion failed due to conflict with previously registered route: /foo-{wild}"
|
||||||
|
)]
|
||||||
|
#[test]
|
||||||
|
fn colliding_prefix_suffix() {
|
||||||
|
_ = Router::<()>::new()
|
||||||
|
.route("/foo-{wild}", get(|| async { "wildcard" }))
|
||||||
|
.route("/foo", get(|| async { "wildcard" }))
|
||||||
|
.route("/{wild}", get(|| async { "wildcard" }))
|
||||||
|
.route("/{wild}-bar", get(|| async { "wildcard" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[should_panic(
|
||||||
|
expected = "Invalid route \"/foo-{wild}\": Insertion failed due to conflict with previously registered route: /foo-{wild}-bar"
|
||||||
|
)]
|
||||||
|
#[test]
|
||||||
|
fn colliding_prefixsuffix_prefix() {
|
||||||
|
_ = Router::<()>::new()
|
||||||
|
.route("/foo-{wild}-bar", get(|| async { "wildcard" }))
|
||||||
|
.route("/foo", get(|| async { "wildcard" }))
|
||||||
|
.route("/{wild}", get(|| async { "wildcard" }))
|
||||||
|
.route("/foo-{wild}", get(|| async { "wildcard" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[should_panic(
|
||||||
|
expected = "Invalid route \"/{wild}-bar\": Insertion failed due to conflict with previously registered route: /foo-{wild}-bar"
|
||||||
|
)]
|
||||||
|
#[test]
|
||||||
|
fn colliding_prefixsuffix_suffix() {
|
||||||
|
_ = Router::<()>::new()
|
||||||
|
.route("/foo-{wild}-bar", get(|| async { "wildcard" }))
|
||||||
|
.route("/foo", get(|| async { "wildcard" }))
|
||||||
|
.route("/{wild}", get(|| async { "wildcard" }))
|
||||||
|
.route("/{wild}-bar", get(|| async { "wildcard" }));
|
||||||
|
}
|
||||||
|
|
||||||
// We might want to reject this too
|
// We might want to reject this too
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn colliding_wildcard_with_fallback() {
|
async fn colliding_wildcard_with_fallback() {
|
||||||
|
|||||||
@@ -302,6 +302,62 @@ async fn nest_at_capture() {
|
|||||||
assert_eq!(res.text().await, "a=foo b=bar");
|
assert_eq!(res.text().await, "a=foo b=bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not `crate::test` because `nest_service` would fail.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn nest_at_prefix_capture() {
|
||||||
|
let empty_routes = Router::new();
|
||||||
|
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("/x{a}x", api_routes)
|
||||||
|
.nest("/xax", empty_routes);
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/xax/bar").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(res.text().await, "a=a b=bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn nest_service_at_prefix_capture() {
|
||||||
|
let empty_routes = Router::new();
|
||||||
|
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_service("/x{a}", api_routes)
|
||||||
|
.nest_service("/xa", empty_routes);
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/xa/bar").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn nest_service_at_prefix_suffix_capture() {
|
||||||
|
let empty_routes = Router::new();
|
||||||
|
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_service("/x{a}x", api_routes)
|
||||||
|
.nest_service("/xax", empty_routes);
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/xax/bar").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn nest_with_and_without_trailing() {
|
async fn nest_with_and_without_trailing() {
|
||||||
let app = Router::new().nest_service("/foo", get(|| async {}));
|
let app = Router::new().nest_service("/foo", get(|| async {}));
|
||||||
|
|||||||
Generated
+2
-2
@@ -2631,9 +2631,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
version = "0.8.4"
|
version = "0.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
checksum = "8863b587001c1b9a8a4e36008cebc6b3612cb1226fe2de94858e06092687b608"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
|
|||||||
Reference in New Issue
Block a user