Skip to content

Commit f0b6e1d

Browse files
committed
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
1 parent 8c01850 commit f0b6e1d

2 files changed

Lines changed: 69 additions & 33 deletions

File tree

axum/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
- **breaking:** Allow `Error: Into<Infallible>` for `Route::{layer, route_layer}` ([#924])
2828
- **breaking:** Remove `extractor_middleware` which was previously deprecated.
2929
Use `axum::middleware::from_extractor` instead ([#1077])
30+
- **breaking:** `MethodRouter` now panics on overlapping routes ([#1102])
3031

3132
[#1077]: https://github.com/tokio-rs/axum/pull/1077
3233
[#1078]: https://github.com/tokio-rs/axum/pull/1078
@@ -35,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3536
[#1095]: https://github.com/tokio-rs/axum/pull/1095
3637
[#1098]: https://github.com/tokio-rs/axum/pull/1098
3738
[#924]: https://github.com/tokio-rs/axum/pull/924
39+
[#1102]: https://github.com/tokio-rs/axum/pull/1102
3840

3941
# 0.5.7 (08. June, 2022)
4042

axum/src/routing/method_routing.rs

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,32 @@ impl<ReqBody, E> MethodRouter<ReqBody, E> {
912912
S: Service<Request<ReqBody>, Response = Response, Error = E> + Clone + Send + 'static,
913913
S::Future: Send + 'static,
914914
{
915+
macro_rules! set_service {
916+
(
917+
$filter:ident,
918+
$svc:ident,
919+
$allow_header:ident,
920+
[
921+
$(
922+
($out:ident, $variant:ident, [$($method:literal),+])
923+
),+
924+
$(,)?
925+
]
926+
) => {
927+
$(
928+
if $filter.contains(MethodFilter::$variant) {
929+
if $out.is_some() {
930+
panic!("Overlapping method route. Cannot add two method routes that both handle `{}`", stringify!($variant))
931+
}
932+
$out = $svc.clone();
933+
$(
934+
append_allow_header(&mut $allow_header, $method);
935+
)+
936+
}
937+
)+
938+
}
939+
}
940+
915941
// written with a pattern match like this to ensure we update all fields
916942
let Self {
917943
mut get,
@@ -927,39 +953,21 @@ impl<ReqBody, E> MethodRouter<ReqBody, E> {
927953
_request_body: _,
928954
} = self;
929955
let svc = Some(Route::new(svc));
930-
if filter.contains(MethodFilter::GET) {
931-
get = svc.clone();
932-
append_allow_header(&mut allow_header, "GET");
933-
append_allow_header(&mut allow_header, "HEAD");
934-
}
935-
if filter.contains(MethodFilter::HEAD) {
936-
append_allow_header(&mut allow_header, "HEAD");
937-
head = svc.clone();
938-
}
939-
if filter.contains(MethodFilter::DELETE) {
940-
append_allow_header(&mut allow_header, "DELETE");
941-
delete = svc.clone();
942-
}
943-
if filter.contains(MethodFilter::OPTIONS) {
944-
append_allow_header(&mut allow_header, "OPTIONS");
945-
options = svc.clone();
946-
}
947-
if filter.contains(MethodFilter::PATCH) {
948-
append_allow_header(&mut allow_header, "PATCH");
949-
patch = svc.clone();
950-
}
951-
if filter.contains(MethodFilter::POST) {
952-
append_allow_header(&mut allow_header, "POST");
953-
post = svc.clone();
954-
}
955-
if filter.contains(MethodFilter::PUT) {
956-
append_allow_header(&mut allow_header, "PUT");
957-
put = svc.clone();
958-
}
959-
if filter.contains(MethodFilter::TRACE) {
960-
append_allow_header(&mut allow_header, "TRACE");
961-
trace = svc;
962-
}
956+
set_service!(
957+
filter,
958+
svc,
959+
allow_header,
960+
[
961+
(get, GET, ["GET", "HEAD"]),
962+
(head, HEAD, ["HEAD"]),
963+
(delete, DELETE, ["DELETE"]),
964+
(options, OPTIONS, ["OPTIONS"]),
965+
(patch, PATCH, ["PATCH"]),
966+
(post, POST, ["POST"]),
967+
(put, PUT, ["PUT"]),
968+
(trace, TRACE, ["TRACE"]),
969+
]
970+
);
963971
Self {
964972
get,
965973
head,
@@ -1294,6 +1302,32 @@ mod tests {
12941302
assert_eq!(headers[ALLOW], "GET,POST");
12951303
}
12961304

1305+
#[tokio::test]
1306+
#[should_panic(
1307+
expected = "Overlapping method route. Cannot add two method routes that both handle `GET`"
1308+
)]
1309+
async fn handler_overlaps() {
1310+
let _: MethodRouter = get(ok).get(ok);
1311+
}
1312+
1313+
#[tokio::test]
1314+
#[should_panic(
1315+
expected = "Overlapping method route. Cannot add two method routes that both handle `POST`"
1316+
)]
1317+
async fn service_overlaps() {
1318+
let _: MethodRouter = post_service(ok.into_service()).post_service(ok.into_service());
1319+
}
1320+
1321+
#[tokio::test]
1322+
async fn get_head_does_not_overlap() {
1323+
let _: MethodRouter = get(ok).head(ok);
1324+
}
1325+
1326+
#[tokio::test]
1327+
async fn head_get_does_not_overlap() {
1328+
let _: MethodRouter = head(ok).get(ok);
1329+
}
1330+
12971331
async fn call<S>(method: Method, svc: &mut S) -> (StatusCode, HeaderMap, String)
12981332
where
12991333
S: Service<Request<Body>, Response = Response, Error = Infallible>,

0 commit comments

Comments
 (0)