Skip to content

Commit 1bdace6

Browse files
committed
openapi: support route inheritance fix #1586
1 parent 95a72f0 commit 1bdace6

File tree

10 files changed

+236
-11
lines changed

10 files changed

+236
-11
lines changed

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/AnnotationParser.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,26 @@ public static List<OperationExt> parse(ParserContext ctx, String prefix,
206206
private static List<OperationExt> parse(ParserContext ctx, String prefix, Type type) {
207207
List<OperationExt> result = new ArrayList<>();
208208
ClassNode classNode = ctx.classNode(type);
209-
for (MethodNode method : classNode.methods) {
210-
if (isRouter(method)) {
211-
ctx.debugHandler(method);
212-
result.addAll(routerMethod(ctx, prefix, classNode, method));
213-
}
209+
Collection<MethodNode> methods = methods(ctx, classNode).values();
210+
for (MethodNode method : methods) {
211+
ctx.debugHandler(method);
212+
result.addAll(routerMethod(ctx, prefix, classNode, method));
214213
}
215214
return result;
216215
}
217216

217+
private static Map<String, MethodNode> methods(ParserContext ctx, ClassNode node) {
218+
Map<String, MethodNode> methods = new LinkedHashMap<>();
219+
if (node.superName != null && !node.superName.equals(TypeFactory.OBJECT.getInternalName())) {
220+
methods.putAll(methods(ctx, ctx.classNode(Type.getObjectType(node.superName))));
221+
}
222+
node.methods.stream().filter(AnnotationParser::isRouter).forEach(it -> {
223+
String signature = it.name + it.desc.substring(0, it.desc.indexOf(')') + 1);
224+
methods.put(signature, it);
225+
});
226+
return methods;
227+
}
228+
218229
private static List<OperationExt> routerMethod(ParserContext ctx, String prefix,
219230
ClassNode classNode, MethodNode method) {
220231
List<OperationExt> result = new ArrayList<>();
@@ -224,7 +235,7 @@ private static List<OperationExt> routerMethod(ParserContext ctx, String prefix,
224235
ResponseExt response = returnTypes(method);
225236

226237
for (String httpMethod : httpMethod(method.visibleAnnotations)) {
227-
for (String pattern : httpPattern(classNode, method, httpMethod)) {
238+
for (String pattern : httpPattern(ctx, classNode, method, httpMethod)) {
228239
OperationExt operation = new OperationExt(
229240
method,
230241
httpMethod,
@@ -397,13 +408,18 @@ private static boolean isPrimitive(String javaType) {
397408
return false;
398409
}
399410

400-
private static List<String> httpPattern(ClassNode classNode, MethodNode method,
411+
private static List<String> httpPattern(ParserContext ctx, ClassNode classNode, MethodNode method,
401412
String httpMethod) {
402413
List<String> patterns = new ArrayList<>();
403414

404415
List<String> rootPattern = httpPattern(httpMethod, null, classNode.visibleAnnotations);
405-
if (rootPattern.size() == 0) {
406-
rootPattern = Arrays.asList("/");
416+
while (rootPattern.isEmpty() && classNode.superName != null) {
417+
classNode = ctx.classNode(Type.getObjectType(classNode.superName));
418+
rootPattern = httpPattern(httpMethod, null, classNode.visibleAnnotations);
419+
}
420+
421+
if (rootPattern.isEmpty()) {
422+
rootPattern = Collections.singletonList("/");
407423
}
408424

409425
for (String prefix : rootPattern) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package examples;
2+
3+
import io.jooby.annotations.GET;
4+
import io.jooby.annotations.Path;
5+
import io.jooby.annotations.QueryParam;
6+
7+
@Path("/base")
8+
public abstract class BaseController {
9+
10+
@GET
11+
public String base() {
12+
return "base";
13+
}
14+
15+
@GET("/withPath")
16+
public String withPath(@QueryParam String q) {
17+
return "withPath";
18+
}
19+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package examples;
2+
3+
import io.jooby.annotations.Path;
4+
5+
@Path("/override")
6+
public class EmptySubClassController extends BaseController {
7+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package examples;
2+
3+
import io.jooby.annotations.GET;
4+
import io.jooby.annotations.Path;
5+
import io.jooby.annotations.QueryParam;
6+
7+
@Path("/overrideMethod")
8+
public class OverrideMethodSubClassController extends BaseController {
9+
10+
@Override public String base() {
11+
return super.base();
12+
}
13+
14+
@GET("/newpath")
15+
@Override public String withPath(@QueryParam String q) {
16+
return super.withPath(q);
17+
}
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package examples;
2+
3+
import io.jooby.annotations.GET;
4+
5+
public class SubController extends BaseController {
6+
7+
@GET("/subPath")
8+
public String subPath() {
9+
return "subPath";
10+
}
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package issues;
2+
3+
import examples.SubController;
4+
import io.jooby.Jooby;
5+
6+
public class App1586 extends Jooby {
7+
{
8+
mvc(new SubController());
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package issues;
2+
3+
import examples.EmptySubClassController;
4+
import io.jooby.Jooby;
5+
6+
public class App1586b extends Jooby {
7+
{
8+
mvc(new EmptySubClassController());
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package issues;
2+
3+
import examples.OverrideMethodSubClassController;
4+
import io.jooby.Jooby;
5+
6+
public class App1586c extends Jooby {
7+
{
8+
mvc(new OverrideMethodSubClassController());
9+
}
10+
}

modules/jooby-openapi/src/test/java/issues/Issue1585.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ public class Issue1585 {
1111
public void shouldAddRegex(OpenAPIResult result) {
1212
assertEquals("openapi: 3.0.1\n"
1313
+ "info:\n"
14-
+ " title: 1584 API\n"
15-
+ " description: 1584 API description\n"
14+
+ " title: 1585 API\n"
15+
+ " description: 1585 API description\n"
1616
+ " version: \"1.0\"\n"
1717
+ "paths:\n"
1818
+ " /user/{id}:\n"
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package issues;
2+
3+
import io.jooby.openapi.OpenAPIResult;
4+
import io.jooby.openapi.OpenAPITest;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
8+
public class Issue1586 {
9+
10+
@OpenAPITest(App1586.class)
11+
public void shouldParseSubClass(OpenAPIResult result) {
12+
assertEquals("openapi: 3.0.1\n"
13+
+ "info:\n"
14+
+ " title: 1586 API\n"
15+
+ " description: 1586 API description\n"
16+
+ " version: \"1.0\"\n"
17+
+ "paths:\n"
18+
+ " /base:\n"
19+
+ " get:\n"
20+
+ " operationId: base\n"
21+
+ " responses:\n"
22+
+ " \"200\":\n"
23+
+ " description: Success\n"
24+
+ " content:\n"
25+
+ " application/json:\n"
26+
+ " schema:\n"
27+
+ " type: string\n"
28+
+ " /base/withPath:\n"
29+
+ " get:\n"
30+
+ " operationId: withPath\n"
31+
+ " parameters:\n"
32+
+ " - name: q\n"
33+
+ " in: query\n"
34+
+ " schema:\n"
35+
+ " type: string\n"
36+
+ " responses:\n"
37+
+ " \"200\":\n"
38+
+ " description: Success\n"
39+
+ " content:\n"
40+
+ " application/json:\n"
41+
+ " schema:\n"
42+
+ " type: string\n"
43+
+ " /base/subPath:\n"
44+
+ " get:\n"
45+
+ " operationId: subPath\n"
46+
+ " responses:\n"
47+
+ " \"200\":\n"
48+
+ " description: Success\n"
49+
+ " content:\n"
50+
+ " application/json:\n"
51+
+ " schema:\n"
52+
+ " type: string\n", result.toYaml());
53+
}
54+
55+
@OpenAPITest(App1586b.class)
56+
public void shouldOverridePathOnSubClass(OpenAPIResult result) {
57+
assertEquals("openapi: 3.0.1\n"
58+
+ "info:\n"
59+
+ " title: 1586b API\n"
60+
+ " description: 1586b API description\n"
61+
+ " version: \"1.0\"\n"
62+
+ "paths:\n"
63+
+ " /override:\n"
64+
+ " get:\n"
65+
+ " operationId: base\n"
66+
+ " responses:\n"
67+
+ " \"200\":\n"
68+
+ " description: Success\n"
69+
+ " content:\n"
70+
+ " application/json:\n"
71+
+ " schema:\n"
72+
+ " type: string\n"
73+
+ " /override/withPath:\n"
74+
+ " get:\n"
75+
+ " operationId: withPath\n"
76+
+ " parameters:\n"
77+
+ " - name: q\n"
78+
+ " in: query\n"
79+
+ " schema:\n"
80+
+ " type: string\n"
81+
+ " responses:\n"
82+
+ " \"200\":\n"
83+
+ " description: Success\n"
84+
+ " content:\n"
85+
+ " application/json:\n"
86+
+ " schema:\n"
87+
+ " type: string\n", result.toYaml());
88+
}
89+
90+
@OpenAPITest(App1586c.class)
91+
public void shouldOverrideMethodOnSubClass(OpenAPIResult result) {
92+
assertEquals("openapi: 3.0.1\n"
93+
+ "info:\n"
94+
+ " title: 1586c API\n"
95+
+ " description: 1586c API description\n"
96+
+ " version: \"1.0\"\n"
97+
+ "paths:\n"
98+
+ " /overrideMethod:\n"
99+
+ " get:\n"
100+
+ " operationId: base\n"
101+
+ " responses:\n"
102+
+ " \"200\":\n"
103+
+ " description: Success\n"
104+
+ " content:\n"
105+
+ " application/json:\n"
106+
+ " schema:\n"
107+
+ " type: string\n"
108+
+ " /overrideMethod/newpath:\n"
109+
+ " get:\n"
110+
+ " operationId: withPath\n"
111+
+ " parameters:\n"
112+
+ " - name: q\n"
113+
+ " in: query\n"
114+
+ " schema:\n"
115+
+ " type: string\n"
116+
+ " responses:\n"
117+
+ " \"200\":\n"
118+
+ " description: Success\n"
119+
+ " content:\n"
120+
+ " application/json:\n"
121+
+ " schema:\n"
122+
+ " type: string\n", result.toYaml());
123+
}
124+
}

0 commit comments

Comments
 (0)