Skip to content

Commit ba41fa9

Browse files
authored
Merge pull request #3752 from jooby-project/openapijavadocscript
WIP: openapi: javadoc for script/lambda routes
2 parents 11dfd04 + 2b1fd8a commit ba41fa9

35 files changed

+1715
-429
lines changed

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

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -277,61 +277,16 @@ public static List<OperationExt> parse(ParserContext ctx, String prefix, Type ty
277277
.parse(className)
278278
.ifPresent(
279279
doc -> {
280-
operationExt.setPathDescription(doc.getDescription());
281-
operationExt.setPathSummary(doc.getSummary());
282-
doc.getTags().forEach(operationExt::addTag);
283-
if (!doc.getExtensions().isEmpty()) {
284-
operationExt.setPathExtensions(doc.getExtensions());
285-
}
280+
JavaDocSetter.setPath(operationExt, doc);
286281
var parameterNames =
287-
Optional.ofNullable(operationExt.getNode().parameters)
288-
.orElse(List.of())
289-
.stream()
290-
.map(p -> p.name)
282+
operationExt.getNode().parameters.stream().map(p -> p.name).toList();
283+
var parameterTypes =
284+
Stream.of(Type.getArgumentTypes(operationExt.getNode().desc))
285+
.map(Type::getClassName)
291286
.toList();
292-
doc.getMethod(operationExt.getOperationId(), parameterNames)
287+
doc.getMethod(operationExt.getOperationId(), parameterTypes)
293288
.ifPresent(
294-
methodDoc -> {
295-
operationExt.setSummary(methodDoc.getSummary());
296-
operationExt.setDescription(methodDoc.getDescription());
297-
if (!methodDoc.getExtensions().isEmpty()) {
298-
operationExt.setExtensions(methodDoc.getExtensions());
299-
}
300-
methodDoc.getTags().forEach(operationExt::addTag);
301-
// Parameters
302-
for (var parameterName : parameterNames) {
303-
var paramExt =
304-
operationExt.getParameters().stream()
305-
.filter(p -> p.getName().equals(parameterName))
306-
.findFirst()
307-
.map(ParameterExt.class::cast)
308-
.orElse(null);
309-
var paramDoc = methodDoc.getParameterDoc(parameterName);
310-
if (paramDoc != null) {
311-
if (paramExt == null) {
312-
operationExt.getRequestBody().setDescription(paramDoc);
313-
} else {
314-
paramExt.setDescription(paramDoc);
315-
}
316-
}
317-
}
318-
// return types
319-
var defaultResponse = operationExt.getDefaultResponse();
320-
if (defaultResponse != null) {
321-
defaultResponse.setDescription(methodDoc.getReturnDoc());
322-
}
323-
for (var throwsDoc : methodDoc.getThrows().values()) {
324-
var response =
325-
operationExt.getResponse(
326-
Integer.toString(throwsDoc.getStatusCode().value()));
327-
if (response == null) {
328-
response =
329-
operationExt.addResponse(
330-
Integer.toString(throwsDoc.getStatusCode().value()));
331-
}
332-
response.setDescription(throwsDoc.getText());
333-
}
334-
});
289+
methodDoc -> JavaDocSetter.set(operationExt, methodDoc, parameterNames));
335290
});
336291
} catch (Exception x) {
337292
throw SneakyThrows.propagate(x);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.openapi;
7+
8+
import java.util.LinkedHashSet;
9+
import java.util.List;
10+
import java.util.Optional;
11+
import java.util.stream.Collectors;
12+
13+
import io.jooby.internal.openapi.javadoc.JavaDocNode;
14+
import io.jooby.internal.openapi.javadoc.MethodDoc;
15+
import io.jooby.internal.openapi.javadoc.ScriptDoc;
16+
import io.swagger.v3.oas.models.parameters.Parameter;
17+
18+
public class JavaDocSetter {
19+
20+
public static void setPath(OperationExt operation, JavaDocNode doc) {
21+
operation.setPathDescription(doc.getDescription());
22+
operation.setPathSummary(doc.getSummary());
23+
doc.getTags().forEach(operation::addTag);
24+
if (!doc.getExtensions().isEmpty()) {
25+
operation.setPathExtensions(doc.getExtensions());
26+
}
27+
}
28+
29+
public static void set(OperationExt operation, ScriptDoc doc) {
30+
var parameters = Optional.ofNullable(operation.getParameters()).orElse(List.of());
31+
var parameterNames = parameters.stream().map(Parameter::getName).collect(Collectors.toList());
32+
if (operation.getRequestBody() != null) {
33+
var javaDocNames = new LinkedHashSet<>(doc.getJavadocParameterNames());
34+
parameterNames.forEach(javaDocNames::remove);
35+
if (javaDocNames.size() == 1) {
36+
// just add body name on lambda/script routes.
37+
parameterNames.addAll(javaDocNames);
38+
}
39+
}
40+
set(operation, doc, parameterNames);
41+
}
42+
43+
public static void set(OperationExt operation, MethodDoc doc, List<String> parameterNames) {
44+
operation.setOperationId(
45+
Optional.ofNullable(operation.getOperationId()).orElse(doc.getOperationId()));
46+
operation.setSummary(doc.getSummary());
47+
operation.setDescription(doc.getDescription());
48+
if (!doc.getExtensions().isEmpty()) {
49+
operation.setExtensions(doc.getExtensions());
50+
}
51+
doc.getTags().forEach(operation::addTag);
52+
// Parameters
53+
for (var parameterName : parameterNames) {
54+
var paramExt =
55+
operation.getParameters().stream()
56+
.filter(p -> p.getName().equals(parameterName))
57+
.findFirst()
58+
.map(ParameterExt.class::cast)
59+
.orElse(null);
60+
var paramDoc = doc.getParameterDoc(parameterName);
61+
if (paramDoc != null) {
62+
if (paramExt == null) {
63+
var body = operation.getRequestBody();
64+
if (body != null) {
65+
body.setDescription(paramDoc);
66+
}
67+
} else {
68+
paramExt.setDescription(paramDoc);
69+
}
70+
}
71+
}
72+
// return types
73+
var defaultResponse = operation.getDefaultResponse();
74+
if (defaultResponse != null) {
75+
defaultResponse.setDescription(doc.getReturnDoc());
76+
}
77+
for (var throwsDoc : doc.getThrows().values()) {
78+
var response = operation.getResponse(throwsDoc.getCode());
79+
if (response == null) {
80+
response = operation.addResponse(throwsDoc.getCode());
81+
}
82+
response.setDescription(throwsDoc.getDescription());
83+
}
84+
}
85+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ public void setController(ClassNode controller) {
211211
this.controller = controller;
212212
}
213213

214+
@Override
215+
public void setOperationId(String operationId) {
216+
super.setOperationId(operationId);
217+
}
218+
214219
@JsonIgnore
215220
public List<AnnotationNode> getAllAnnotations() {
216221
return Stream.of(

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ public class RequestBodyExt extends RequestBody {
1515

1616
@JsonIgnore private String contentType = MediaType.JSON;
1717

18+
{
19+
setRequired(true);
20+
}
21+
1822
public String getJavaType() {
1923
return javaType;
2024
}

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

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ public List<OperationExt> parse(ParserContext ctx, OpenAPIExt openapi) {
6565
Optional.ofNullable(ctx.getMainClass()).orElse(ctx.getRouter().getClassName());
6666
ClassNode application = ctx.classNode(Type.getObjectType(applicationName.replace(".", "/")));
6767

68+
// JavaDoc
69+
addJavaDoc(ctx, ctx.getRouter().getClassName(), "", operations);
70+
6871
// swagger/openapi:
6972
for (OperationExt operation : operations) {
7073
operation.setApplication(application);
@@ -100,6 +103,29 @@ public List<OperationExt> parse(ParserContext ctx, OpenAPIExt openapi) {
100103
return result;
101104
}
102105

106+
private static void addJavaDoc(
107+
ParserContext ctx, String className, String prefix, List<OperationExt> operations) {
108+
// javadoc
109+
var offset = prefix == null || prefix.isEmpty() ? 0 : prefix.length();
110+
var javaDoc = ctx.javadoc().parse(className);
111+
for (OperationExt operation : operations) {
112+
// Script/lambda
113+
if (operation.getController() == null) {
114+
javaDoc
115+
.flatMap(
116+
doc ->
117+
doc.getScript(operation.getMethod(), operation.getPattern().substring(offset)))
118+
.ifPresent(
119+
scriptDoc -> {
120+
if (scriptDoc.getPath() != null) {
121+
JavaDocSetter.setPath(operation, scriptDoc.getPath());
122+
}
123+
JavaDocSetter.set(operation, scriptDoc);
124+
});
125+
}
126+
}
127+
}
128+
103129
private void checkResponses(ParserContext ctx, OperationExt operation) {
104130
// checkResponse(ctx, operation, 200, operation.getDefaultResponse());
105131
for (Map.Entry<String, ApiResponse> entry : operation.getResponses().entrySet()) {
@@ -591,7 +617,9 @@ private List<OperationExt> mountRouter(
591617
throw new UnsupportedOperationException(InsnSupport.toString(node));
592618
}
593619
ClassNode classNode = ctx.classNode(router);
594-
return parse(ctx.newContext(router), prefix, classNode);
620+
var operations = parse(ctx.newContext(router), prefix, classNode);
621+
addJavaDoc(ctx, router.getClassName(), prefix, operations);
622+
return operations;
595623
}
596624

597625
private List<OperationExt> installApp(
@@ -616,7 +644,9 @@ private List<OperationExt> installApp(
616644
throw new UnsupportedOperationException(InsnSupport.toString(node));
617645
}
618646
ClassNode classNode = ctx.classNode(router);
619-
return parse(ctx.newContext(router), prefix, classNode);
647+
var operations = parse(ctx.newContext(router), prefix, classNode);
648+
addJavaDoc(ctx, router.getClassName(), prefix, operations);
649+
return operations;
620650
}
621651

622652
private Type kotlinSupplier(ParserContext ctx, MethodInsnNode node, AbstractInsnNode ins) {
@@ -880,7 +910,8 @@ private OperationExt newRouteDescriptor(
880910
node.name.equals("apply")
881911
|| node.name.equals("invoke")
882912
|| node.name.startsWith("invoke$")
883-
|| node.name.contains("$lambda");
913+
|| node.name.contains("$lambda")
914+
|| node.name.startsWith("fake$");
884915
if (notSynthetic && !lambda) {
885916
operation.setOperationId(node.name);
886917
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/javadoc/ClassDoc.java

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*/
66
package io.jooby.internal.openapi.javadoc;
77

8-
import static io.jooby.internal.openapi.javadoc.JavaDocSupport.*;
8+
import static io.jooby.internal.openapi.javadoc.JavaDocStream.*;
9+
import static io.jooby.internal.openapi.javadoc.JavaDocSupport.getClassName;
910

1011
import java.util.*;
1112
import java.util.stream.Collectors;
@@ -24,6 +25,7 @@
2425
public class ClassDoc extends JavaDocNode {
2526
private final Map<String, FieldDoc> fields = new LinkedHashMap<>();
2627
private final Map<String, MethodDoc> methods = new LinkedHashMap<>();
28+
private final Map<String, ScriptDoc> scripts = new LinkedHashMap<>();
2729
private final List<Server> servers;
2830
private final List<Contact> contact;
2931
private final List<License> license;
@@ -115,7 +117,11 @@ private void defaultRecordMembers() {
115117
/* Virtual method */
116118
var method =
117119
new MethodDoc(
118-
context, createVirtualMember(name.getText(), TokenTypes.METHOD_DEF), memberDoc);
120+
context,
121+
createVirtualMember(name.getText(), TokenTypes.METHOD_DEF),
122+
memberDoc)
123+
.markAsVirtual();
124+
119125
addMethod(method);
120126
});
121127
}
@@ -140,7 +146,7 @@ private void defaultEnumMembers() {
140146
}
141147
}
142148

143-
private static DetailAstImpl createVirtualMember(String name, int tokenType) {
149+
private DetailAstImpl createVirtualMember(String name, int tokenType) {
144150
var publicMod = new DetailAstImpl();
145151
publicMod.initialize(
146152
TokenTypes.LITERAL_PUBLIC, TokenUtil.getTokenName(TokenTypes.LITERAL_PUBLIC));
@@ -160,6 +166,10 @@ public void addMethod(MethodDoc method) {
160166
this.methods.put(toMethodSignature(method), method);
161167
}
162168

169+
public void addScript(ScriptDoc method) {
170+
this.scripts.put(toScriptSignature(method), method);
171+
}
172+
163173
public void addField(FieldDoc field) {
164174
this.fields.put(field.getName(), field);
165175
}
@@ -168,48 +178,40 @@ public Optional<FieldDoc> getField(String name) {
168178
return Optional.ofNullable(fields.get(name));
169179
}
170180

171-
public Optional<MethodDoc> getMethod(String name, List<String> parameterNames) {
172-
return Optional.ofNullable(methods.get(toMethodSignature(name, parameterNames)));
181+
public Optional<MethodDoc> getMethod(String name, List<String> types) {
182+
return Optional.ofNullable(methods.get(toMethodSignature(name, types)));
183+
}
184+
185+
public Optional<ScriptDoc> getScript(String method, String pattern) {
186+
return Optional.ofNullable(scripts.get(toScriptSignature(method, pattern)));
187+
}
188+
189+
private String toScriptSignature(ScriptDoc method) {
190+
return toScriptSignature(method.getMethod(), method.getPattern());
191+
}
192+
193+
private String toScriptSignature(String method, String pattern) {
194+
return method + "/" + pattern;
173195
}
174196

175197
private String toMethodSignature(MethodDoc method) {
176-
return toMethodSignature(method.getName(), method.getParameterNames());
198+
return toMethodSignature(method.getName(), method.getParameterTypes());
177199
}
178200

179-
private String toMethodSignature(String methodName, List<String> parameterNames) {
180-
return methodName + parameterNames.stream().collect(Collectors.joining(", ", "(", ")"));
201+
private String toMethodSignature(String methodName, List<String> types) {
202+
return methodName + types.stream().collect(Collectors.joining(", ", "(", ")"));
181203
}
182204

183205
public String getSimpleName() {
184-
return getSimpleName(node);
206+
return JavaDocSupport.getSimpleName(node);
185207
}
186208

187-
protected String getSimpleName(DetailAST node) {
188-
return node.findFirstToken(TokenTypes.IDENT).getText();
209+
public String getName() {
210+
return getClassName(node);
189211
}
190212

191-
public String getName() {
192-
var classScope =
193-
Stream.concat(
194-
Stream.of(node),
195-
backward(node)
196-
.filter(
197-
tokens(
198-
TokenTypes.CLASS_DEF,
199-
TokenTypes.INTERFACE_DEF,
200-
TokenTypes.ENUM_DEF,
201-
TokenTypes.RECORD_DEF)))
202-
.map(this::getSimpleName)
203-
.toList();
204-
var packageScope =
205-
backward(node)
206-
.filter(tokens(TokenTypes.COMPILATION_UNIT))
207-
.findFirst()
208-
.flatMap(it -> tree(it).filter(tokens(TokenTypes.PACKAGE_DEF)).findFirst())
209-
.map(it -> tree(it).filter(tokens(TokenTypes.IDENT)).map(DetailAST::getText).toList())
210-
.orElse(List.of());
211-
return Stream.concat(packageScope.stream(), classScope.stream())
212-
.collect(Collectors.joining("."));
213+
public String getPackage() {
214+
return JavaDocSupport.getPackageName(node);
213215
}
214216

215217
public boolean isRecord() {

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/javadoc/FieldDoc.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import com.puppycrawl.tools.checkstyle.api.DetailAST;
99
import com.puppycrawl.tools.checkstyle.api.DetailNode;
10-
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
1110

1211
public class FieldDoc extends JavaDocNode {
1312
public FieldDoc(JavaDocParser ctx, DetailAST node, DetailAST javadoc) {
@@ -19,6 +18,6 @@ public FieldDoc(JavaDocParser ctx, DetailAST node, DetailAST javadoc) {
1918
}
2019

2120
public String getName() {
22-
return node.findFirstToken(TokenTypes.IDENT).getText();
21+
return JavaDocSupport.getSimpleName(node);
2322
}
2423
}

0 commit comments

Comments
 (0)