Skip to content

Commit 0a38fa7

Browse files
committed
Improve path matching in RESTEasy Reactive for ambiguous path templates
The PathMatcher now returns list of all possible matches rather than just the first match. This is for case when the first match end up not matching the request path, the remaining, lower priority matches, can be tried.
1 parent 5ea8be9 commit 0a38fa7

File tree

3 files changed

+69
-8
lines changed

3 files changed

+69
-8
lines changed

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/PathMatcher.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.jboss.resteasy.reactive.server.mapping;
22

3+
import java.util.ArrayList;
4+
import java.util.Collections;
35
import java.util.Comparator;
46
import java.util.List;
57
import java.util.Set;
@@ -34,24 +36,28 @@ class PathMatcher<T> implements Dumpable {
3436
* @param path The relative path to match
3537
* @return The match match. This will never be null, however if none matched its value field will be
3638
*/
37-
PathMatch<T> match(String path) {
39+
List<PathMatch<T>> match(String path) {
3840
int length = path.length();
3941
final int[] lengths = this.lengths;
42+
ArrayList<PathMatch<T>> matches = new ArrayList<>();
4043
for (int i = 0; i < lengths.length; ++i) {
4144
int pathLength = lengths[i];
4245
if (pathLength == length) {
4346
SubstringMap.SubstringMatch<T> next = paths.get(path, length);
4447
if (next != null) {
45-
return new PathMatch<>(path, "", next.getValue());
48+
matches.add(new PathMatch<>(path, "", next.getValue()));
4649
}
4750
} else if (pathLength < length) {
4851
SubstringMap.SubstringMatch<T> next = paths.get(path, pathLength);
4952
if (next != null) {
50-
return new PathMatch<>(next.getKey(), path.substring(pathLength), next.getValue());
53+
matches.add(new PathMatch<>(next.getKey(), path.substring(pathLength), next.getValue()));
5154
}
5255
}
5356
}
54-
return defaultMatch(path);
57+
if (!matches.isEmpty()) {
58+
return matches;
59+
}
60+
return Collections.singletonList(defaultMatch(path));
5561
}
5662

5763
PathMatch<T> defaultMatch(String path) {

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.Arrays;
55
import java.util.Collections;
66
import java.util.HashMap;
7+
import java.util.List;
78
import java.util.Map;
89
import java.util.function.BiConsumer;
910
import java.util.regex.Matcher;
@@ -48,9 +49,12 @@ public void accept(String stem, ArrayList<RequestPath<T>> list) {
4849
* @return best RequestMatch, or null if the path has no match
4950
*/
5051
public RequestMatch<T> map(String path) {
51-
var result = mapFromPathMatcher(path, requestPaths.match(path), 0);
52-
if (result != null) {
53-
return result;
52+
List<PathMatcher.PathMatch<ArrayList<RequestPath<T>>>> matches = requestPaths.match(path);
53+
for (PathMatcher.PathMatch<ArrayList<RequestPath<T>>> match : matches) {
54+
var result = mapFromPathMatcher(path, match, 0);
55+
if (result != null) {
56+
return result;
57+
}
5458
}
5559

5660
// the following code is meant to handle cases like https://github.com/quarkusio/quarkus/issues/30667
@@ -68,7 +72,7 @@ public RequestMatch<T> continueMatching(String path, RequestMatch<T> lastMatch)
6872
return null;
6973
}
7074

71-
var initialMatches = requestPaths.match(path);
75+
var initialMatches = requestPaths.match(path).get(0);
7276
var result = mapFromPathMatcher(path, initialMatches, 0);
7377
if (result != null) {
7478
int idx = nextMatchStartingIndex(initialMatches, lastMatch);

independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PathParamOverlapTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,44 @@ public void test() {
5353
get("/hello/other/test/wrong")
5454
.then()
5555
.statusCode(404);
56+
57+
get("/hello/foo")
58+
.then()
59+
.statusCode(404);
60+
61+
get("/hello/foo/value")
62+
.then()
63+
.statusCode(200)
64+
.body(equalTo("Foo value"));
65+
66+
get("/hello/foo/bar")
67+
.then()
68+
.statusCode(200)
69+
.body(equalTo("Foo bar"));
70+
71+
get("/hello/foo/bar/value")
72+
.then()
73+
.statusCode(200)
74+
.body(equalTo("FooBar value"));
75+
76+
get("/hello/foo/bah_value")
77+
.then()
78+
.statusCode(200)
79+
.body(equalTo("Foo bah_value"));
80+
81+
get("/hello/foo/bar_value")
82+
.then()
83+
.statusCode(200)
84+
.body(equalTo("Foo bar_value"));
85+
}
86+
87+
// TODO: remove this test before commit
88+
@Test
89+
public void test2() {
90+
get("/hello/foo/bar_value")
91+
.then()
92+
.statusCode(200)
93+
.body(equalTo("Foo bar_value"));
5694
}
5795

5896
@Path("/hello")
@@ -70,5 +108,18 @@ public String test() {
70108
public String second(@RestPath String id) {
71109
return "Hello " + id;
72110
}
111+
112+
@GET
113+
@Path("/foo/{param}")
114+
public String foo(@RestPath String param) {
115+
return "Foo " + param;
116+
}
117+
118+
@GET
119+
@Path("/foo/bar/{param}")
120+
public String fooBar(@RestPath String param) {
121+
return "FooBar " + param;
122+
}
123+
73124
}
74125
}

0 commit comments

Comments
 (0)