Skip to content

Commit 1539616

Browse files
committed
feat(internal): Router with exact and templated indexes plus allowedMethods()
1 parent 3afdf7e commit 1539616

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.retailsvc.http.internal;
2+
3+
import com.retailsvc.http.spec.HttpMethod;
4+
import com.retailsvc.http.spec.Operation;
5+
import java.util.ArrayList;
6+
import java.util.EnumMap;
7+
import java.util.EnumSet;
8+
import java.util.LinkedHashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
import java.util.Set;
13+
14+
public final class Router {
15+
16+
public record Match(Operation operation, Map<String, String> pathParameters) {}
17+
18+
private final Map<HttpMethod, Map<String, Operation>> exact = new EnumMap<>(HttpMethod.class);
19+
private final Map<HttpMethod, List<Operation>> templated = new EnumMap<>(HttpMethod.class);
20+
21+
public Router(List<Operation> operations) {
22+
for (HttpMethod m : HttpMethod.values()) {
23+
exact.put(m, new LinkedHashMap<>());
24+
templated.put(m, new ArrayList<>());
25+
}
26+
for (Operation op : operations) {
27+
if (op.path().parameterNames().isEmpty()) {
28+
exact.get(op.method()).put(op.path().raw(), op);
29+
} else {
30+
templated.get(op.method()).add(op);
31+
}
32+
}
33+
}
34+
35+
public Optional<Match> match(HttpMethod method, String path) {
36+
Operation hit = exact.get(method).get(path);
37+
if (hit != null) return Optional.of(new Match(hit, Map.of()));
38+
for (Operation op : templated.get(method)) {
39+
Optional<Map<String, String>> params = op.path().match(path);
40+
if (params.isPresent()) return Optional.of(new Match(op, params.get()));
41+
}
42+
return Optional.empty();
43+
}
44+
45+
public Set<HttpMethod> allowedMethods(String path) {
46+
EnumSet<HttpMethod> out = EnumSet.noneOf(HttpMethod.class);
47+
for (HttpMethod m : HttpMethod.values()) {
48+
if (exact.get(m).containsKey(path)) {
49+
out.add(m);
50+
continue;
51+
}
52+
for (Operation op : templated.get(m)) {
53+
if (op.path().match(path).isPresent()) {
54+
out.add(m);
55+
break;
56+
}
57+
}
58+
}
59+
return out;
60+
}
61+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.retailsvc.http.internal;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.retailsvc.http.spec.HttpMethod;
6+
import com.retailsvc.http.spec.Operation;
7+
import com.retailsvc.http.spec.PathTemplate;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Optional;
11+
import org.junit.jupiter.api.Test;
12+
13+
class RouterTest {
14+
private Operation op(String id, HttpMethod m, String path) {
15+
return new Operation(id, m, PathTemplate.compile(path), Optional.empty(), List.of(), Map.of());
16+
}
17+
18+
@Test
19+
void exactPathMatchByMethod() {
20+
Router r =
21+
new Router(List.of(op("a", HttpMethod.GET, "/users"), op("b", HttpMethod.POST, "/users")));
22+
assertThat(r.match(HttpMethod.GET, "/users").orElseThrow().operation().operationId())
23+
.isEqualTo("a");
24+
assertThat(r.match(HttpMethod.POST, "/users").orElseThrow().operation().operationId())
25+
.isEqualTo("b");
26+
}
27+
28+
@Test
29+
void templatedPathExtractsParam() {
30+
Router r = new Router(List.of(op("g", HttpMethod.GET, "/users/{id}")));
31+
Router.Match m = r.match(HttpMethod.GET, "/users/42").orElseThrow();
32+
assertThat(m.operation().operationId()).isEqualTo("g");
33+
assertThat(m.pathParameters()).containsEntry("id", "42");
34+
}
35+
36+
@Test
37+
void unknownPathReturnsEmpty() {
38+
Router r = new Router(List.of(op("g", HttpMethod.GET, "/users")));
39+
assertThat(r.match(HttpMethod.GET, "/orders")).isEmpty();
40+
}
41+
42+
@Test
43+
void allowedMethodsForKnownPath() {
44+
Router r =
45+
new Router(List.of(op("a", HttpMethod.GET, "/users"), op("b", HttpMethod.POST, "/users")));
46+
assertThat(r.allowedMethods("/users"))
47+
.containsExactlyInAnyOrder(HttpMethod.GET, HttpMethod.POST);
48+
assertThat(r.allowedMethods("/missing")).isEmpty();
49+
}
50+
}

0 commit comments

Comments
 (0)