Skip to content

Commit 8554fda

Browse files
committed
openapi: redo parsing + integrate parameters and operation doc
- make parser safe - simplify/cleanup code - integrate with openapi operations and parameters and schemas - ref #3733
1 parent 6700dcb commit 8554fda

26 files changed

+1015
-499
lines changed

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

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import static java.util.Arrays.asList;
1111
import static java.util.Collections.singletonList;
1212

13-
import java.io.File;
14-
import java.nio.file.Paths;
1513
import java.util.*;
1614
import java.util.concurrent.atomic.AtomicReference;
1715
import java.util.function.Consumer;
@@ -22,10 +20,7 @@
2220
import org.objectweb.asm.Type;
2321
import org.objectweb.asm.tree.*;
2422

25-
import io.jooby.Context;
26-
import io.jooby.MediaType;
27-
import io.jooby.Router;
28-
import io.jooby.Session;
23+
import io.jooby.*;
2924
import io.jooby.annotation.ContextParam;
3025
import io.jooby.annotation.CookieParam;
3126
import io.jooby.annotation.FormParam;
@@ -34,7 +29,6 @@
3429
import io.jooby.annotation.Path;
3530
import io.jooby.annotation.PathParam;
3631
import io.jooby.annotation.QueryParam;
37-
import io.jooby.internal.openapi.javadoc.JavaDocParser;
3832
import io.swagger.v3.oas.models.media.Content;
3933
import io.swagger.v3.oas.models.media.ObjectSchema;
4034
import io.swagger.v3.oas.models.media.Schema;
@@ -274,38 +268,49 @@ public static List<OperationExt> parse(ParserContext ctx, String prefix, Type ty
274268
ctx.debugHandler(method);
275269
result.addAll(routerMethod(ctx, prefix, classNode, method));
276270
}
277-
var javadocContext = ctx.javadoc();
278-
var javadocParser = new JavaDocParser(javadocContext);
271+
var javaDocParser = ctx.javadoc();
279272
for (OperationExt operationExt : result) {
280273
operationExt.setController(classNode);
281274
try {
282-
var className = operationExt.getControllerName();
283-
javadocParser
284-
.parse(toJavaPath(className))
275+
var className = operationExt.getControllerName().replace("/", ".");
276+
javaDocParser
277+
.parse(className)
285278
.ifPresent(
286279
doc -> {
287280
operationExt.setPathDescription(doc.getDescription());
288281
operationExt.setPathSummary(doc.getSummary());
289-
var args =
290-
operationExt.getParameters().stream()
291-
.map(ParameterExt.class::cast)
292-
.map(
293-
p ->
294-
Optional.ofNullable(p.getContainerType()).orElse(p.getJavaType()))
295-
.map(
296-
qualifiedName -> {
297-
var index = qualifiedName.lastIndexOf('.');
298-
return index == -1
299-
? qualifiedName
300-
: qualifiedName.substring(index + 1);
301-
})
282+
var parameterNames =
283+
Optional.ofNullable(operationExt.getNode().parameters)
284+
.orElse(List.of())
285+
.stream()
286+
.map(p -> p.name)
302287
.toList();
303-
doc.getMethod(operationExt.getOperationId(), args)
288+
doc.getMethod(operationExt.getOperationId(), parameterNames)
304289
.ifPresent(
305290
methodDoc -> {
306291
operationExt.setSummary(methodDoc.getSummary());
307292
operationExt.setDescription(methodDoc.getDescription());
308-
293+
for (var parameterName : parameterNames) {
294+
var paramExt =
295+
operationExt.getParameters().stream()
296+
.filter(p -> p.getName().equals(parameterName))
297+
.findFirst()
298+
.map(ParameterExt.class::cast)
299+
.orElse(null);
300+
var paramDoc =
301+
methodDoc.getParameterDoc(
302+
parameterName,
303+
Optional.ofNullable(paramExt)
304+
.map(ParameterExt::getContainerType)
305+
.orElse(null));
306+
if (paramDoc != null) {
307+
if (paramExt == null) {
308+
operationExt.getRequestBody().setDescription(paramDoc);
309+
} else {
310+
paramExt.setDescription(paramDoc);
311+
}
312+
}
313+
}
309314
for (var parameter : operationExt.getParameters()) {
310315
var paramExt = (ParameterExt) parameter;
311316
var paramDoc =
@@ -318,19 +323,12 @@ public static List<OperationExt> parse(ParserContext ctx, String prefix, Type ty
318323
});
319324
});
320325
} catch (Exception x) {
321-
// TODO: how to log from here
322-
x.printStackTrace();
326+
throw SneakyThrows.propagate(x);
323327
}
324328
}
325329
return result;
326330
}
327331

328-
private static java.nio.file.Path toJavaPath(String className) {
329-
var segments = className.split("/");
330-
segments[segments.length - 1] = segments[segments.length - 1] + ".java";
331-
return Paths.get(String.join(File.separator, segments));
332-
}
333-
334332
private static Map<String, MethodNode> methods(ParserContext ctx, ClassNode node) {
335333
Map<String, MethodNode> methods = new LinkedHashMap<>();
336334
if (node.superName != null && !node.superName.equals(TypeFactory.OBJECT.getInternalName())) {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import java.util.Iterator;
1111
import java.util.Set;
1212

13-
import com.fasterxml.jackson.databind.JavaType;
1413
import com.fasterxml.jackson.databind.ObjectMapper;
1514
import io.jooby.FileUpload;
1615
import io.jooby.Jooby;
@@ -37,7 +36,7 @@ public ModelConverterExt(ObjectMapper mapper) {
3736
@Override
3837
public Schema resolve(
3938
AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
40-
JavaType javaType = _mapper.getTypeFactory().constructType(type.getType());
39+
var javaType = _mapper.getTypeFactory().constructType(type.getType());
4140
if (javaType.isCollectionLikeType() || javaType.isArrayType()) {
4241
if (isFile(javaType.getContentType().getRawClass())) {
4342
return new ArraySchema().items(new FileSchema());
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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.lang.reflect.Type;
9+
import java.util.*;
10+
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
14+
import io.swagger.v3.core.converter.*;
15+
import io.swagger.v3.core.util.ReferenceTypeUtils;
16+
import io.swagger.v3.oas.models.media.Schema;
17+
18+
public class ModelConvertersExt extends ModelConverters {
19+
20+
/** Copy of {@link ModelConverterContextImpl} required for access to schemas by class name. */
21+
private static class ModelConverterContextExt implements ModelConverterContext {
22+
private static final Logger LOGGER = LoggerFactory.getLogger(ModelConverterContextExt.class);
23+
24+
private final List<ModelConverter> converters;
25+
private final Map<String, Schema> modelByName;
26+
private final HashMap<AnnotatedType, Schema> modelByType;
27+
private final Set<AnnotatedType> processedTypes;
28+
29+
public ModelConverterContextExt(List<ModelConverter> converters) {
30+
this.converters = converters;
31+
modelByName = new TreeMap<>();
32+
modelByType = new HashMap<>();
33+
processedTypes = new HashSet<>();
34+
}
35+
36+
public ModelConverterContextExt(ModelConverter converter) {
37+
this(new ArrayList<ModelConverter>());
38+
converters.add(converter);
39+
}
40+
41+
@Override
42+
public Iterator<ModelConverter> getConverters() {
43+
return converters.iterator();
44+
}
45+
46+
@Override
47+
public void defineModel(String name, Schema model) {
48+
AnnotatedType aType = null;
49+
defineModel(name, model, aType, null);
50+
}
51+
52+
@Override
53+
public void defineModel(String name, Schema model, Type type, String prevName) {
54+
defineModel(name, model, new AnnotatedType().type(type), prevName);
55+
}
56+
57+
@Override
58+
public void defineModel(String name, Schema model, AnnotatedType type, String prevName) {
59+
if (LOGGER.isTraceEnabled()) {
60+
LOGGER.trace(String.format("defineModel %s %s", name, model));
61+
}
62+
modelByName.put(name, model);
63+
64+
if (prevName != null && !prevName.isBlank() && !prevName.equals(name)) {
65+
modelByName.remove(prevName);
66+
}
67+
68+
if (type != null && type.getType() != null) {
69+
modelByType.put(type, model);
70+
}
71+
}
72+
73+
@Override
74+
public Map<String, Schema> getDefinedModels() {
75+
return Collections.unmodifiableMap(modelByName);
76+
}
77+
78+
@Override
79+
public Schema resolve(AnnotatedType type) {
80+
AnnotatedType aType = ReferenceTypeUtils.unwrapReference(type);
81+
if (aType != null) {
82+
return resolve(aType);
83+
}
84+
85+
if (processedTypes.contains(type)) {
86+
return modelByType.get(type);
87+
} else {
88+
processedTypes.add(type);
89+
}
90+
if (LOGGER.isDebugEnabled()) {
91+
LOGGER.debug(String.format("resolve %s", type.getType()));
92+
}
93+
Iterator<ModelConverter> converters = this.getConverters();
94+
Schema resolved = null;
95+
if (converters.hasNext()) {
96+
ModelConverter converter = converters.next();
97+
LOGGER.trace("trying extension {}", converter);
98+
resolved = converter.resolve(type, this, converters);
99+
}
100+
if (resolved != null) {
101+
modelByType.put(type, resolved);
102+
103+
Schema resolvedImpl = resolved;
104+
if (resolvedImpl.getName() != null) {
105+
modelByName.put(resolvedImpl.getName(), resolved);
106+
}
107+
} else {
108+
processedTypes.remove(type);
109+
}
110+
111+
return resolved;
112+
}
113+
}
114+
115+
public ModelConvertersExt() {
116+
super(false);
117+
}
118+
119+
@Override
120+
public ResolvedSchemaExt readAllAsResolvedSchema(Type type) {
121+
return (ResolvedSchemaExt) super.readAllAsResolvedSchema(type);
122+
}
123+
124+
@Override
125+
public ResolvedSchemaExt readAllAsResolvedSchema(AnnotatedType type) {
126+
return (ResolvedSchemaExt) super.readAllAsResolvedSchema(type);
127+
}
128+
129+
@Override
130+
public ResolvedSchemaExt resolveAsResolvedSchema(AnnotatedType type) {
131+
var context = new ModelConverterContextExt(getConverters());
132+
var resolvedSchema = new ResolvedSchemaExt();
133+
resolvedSchema.schema = context.resolve(type);
134+
resolvedSchema.referencedSchemas = context.getDefinedModels();
135+
resolvedSchema.referencedSchemasByType = new HashMap<>();
136+
context.modelByType.forEach(
137+
(annotatedType, schema) ->
138+
resolvedSchema.referencedSchemasByType.put(annotatedType.getType(), schema));
139+
return resolvedSchema;
140+
}
141+
}

0 commit comments

Comments
 (0)