Skip to content

Commit 6611bc7

Browse files
committed
WIP: attempt to reproduce redoc demo
- https://redocly.github.io/redoc
1 parent 2100ec0 commit 6611bc7

20 files changed

+338
-810
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,11 @@ public static List<OperationExt> parse(ParserContext ctx, String prefix, Type ty
279279
doc -> {
280280
JavaDocSetter.setPath(operationExt, doc);
281281
var parameterNames =
282-
operationExt.getNode().parameters.stream().map(p -> p.name).toList();
282+
Optional.ofNullable(operationExt.getNode().parameters)
283+
.orElse(List.of())
284+
.stream()
285+
.map(p -> p.name)
286+
.toList();
283287
var parameterTypes =
284288
Stream.of(Type.getArgumentTypes(operationExt.getNode().desc))
285289
.map(Type::getClassName)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import io.swagger.v3.core.converter.*;
1515
import io.swagger.v3.core.util.ReferenceTypeUtils;
16+
import io.swagger.v3.oas.models.SpecVersion;
1617
import io.swagger.v3.oas.models.media.Schema;
1718

1819
public class ModelConvertersExt extends ModelConverters {
@@ -112,8 +113,8 @@ public Schema resolve(AnnotatedType type) {
112113
}
113114
}
114115

115-
public ModelConvertersExt() {
116-
super(false);
116+
public ModelConvertersExt(SpecVersion specVersion) {
117+
super(specVersion == SpecVersion.V31);
117118
}
118119

119120
@Override

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import io.jooby.internal.openapi.javadoc.JavaDocParser;
7575
import io.jooby.openapi.DebugOption;
7676
import io.swagger.v3.core.util.RefUtils;
77+
import io.swagger.v3.oas.models.SpecVersion;
7778
import io.swagger.v3.oas.models.media.ArraySchema;
7879
import io.swagger.v3.oas.models.media.BinarySchema;
7980
import io.swagger.v3.oas.models.media.BooleanSchema;
@@ -97,6 +98,7 @@ public static class TypeLiteral {
9798
public TypeLiteral() {}
9899
}
99100

101+
private final SpecVersion specVersion;
100102
private String mainClass;
101103
private final ObjectMapper json;
102104
private final ObjectMapper yaml;
@@ -110,23 +112,26 @@ public TypeLiteral() {}
110112
private final JavaDocParser javadocParser;
111113

112114
public ParserContext(
115+
SpecVersion specVersion,
113116
ObjectMapper json,
114117
ObjectMapper yaml,
115118
ClassSource source,
116119
Type router,
117120
JavaDocParser javadocParser,
118121
Set<DebugOption> debug) {
119-
this(json, yaml, source, new HashMap<>(), router, javadocParser, debug);
122+
this(specVersion, json, yaml, source, new HashMap<>(), router, javadocParser, debug);
120123
}
121124

122125
private ParserContext(
126+
SpecVersion specVersion,
123127
ObjectMapper json,
124128
ObjectMapper yaml,
125129
ClassSource source,
126130
Map<Type, ClassNode> nodes,
127131
Type router,
128132
JavaDocParser javadocParser,
129133
Set<DebugOption> debug) {
134+
this.specVersion = specVersion;
130135
this.json = json;
131136
this.yaml = yaml;
132137
this.router = router;
@@ -135,9 +140,9 @@ private ParserContext(
135140
this.nodes = nodes;
136141
this.javadocParser = javadocParser;
137142

138-
var mappers = List.of(json, yaml);
143+
var mappers = List.of(json);
139144
jacksonModules(source.getClassLoader(), mappers);
140-
this.converters = new ModelConvertersExt();
145+
this.converters = new ModelConvertersExt(specVersion);
141146
mappers.stream().map(ModelConverterExt::new).forEach(converters::addConverter);
142147
}
143148

@@ -245,10 +250,10 @@ public Schema schema(Class type) {
245250
return new StringSchema().format(type.getSimpleName().toLowerCase());
246251
}
247252
if (BigInteger.class == type) {
248-
return new IntegerSchema().format(null);
253+
return new IntegerSchema().format("int64");
249254
}
250255
if (BigDecimal.class == type) {
251-
return new NumberSchema().format(null);
256+
return new NumberSchema().format("decimal");
252257
}
253258
if (Date.class == type || LocalDate.class == type) {
254259
return new DateSchema();
@@ -349,6 +354,10 @@ private void document(Class typeName, Schema schema, ResolvedSchemaExt resolvedS
349354
});
350355
} else {
351356
value.setDescription(text);
357+
var example = javadoc.getPropertyExample(key);
358+
if (example != null) {
359+
value.setExample(example);
360+
}
352361
}
353362
});
354363
}
@@ -543,7 +552,7 @@ public boolean process(AbstractInsnNode instruction) {
543552
}
544553

545554
public ParserContext newContext(Type router) {
546-
return new ParserContext(json, yaml, source, nodes, router, javadocParser, debug);
555+
return new ParserContext(specVersion, json, yaml, source, nodes, router, javadocParser, debug);
547556
}
548557

549558
public String getMainClass() {

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ public boolean isEnum() {
229229
return tree(node).anyMatch(tokens(TokenTypes.ENUM_DEF));
230230
}
231231

232-
public String getPropertyDoc(String name) {
232+
private String propertyDoc(String name) {
233233
var getterDoc =
234234
Stream.of(name, getterName(name))
235235
.map(n -> methods.get(toMethodSignature(n, List.of())))
@@ -244,6 +244,15 @@ public String getPropertyDoc(String name) {
244244
return getterDoc;
245245
}
246246

247+
public String getPropertyDoc(String name) {
248+
var doc = propertyDoc(name);
249+
return doc == null ? null : doc.replace(exampleCode(doc), "").trim();
250+
}
251+
252+
public Object getPropertyExample(String name) {
253+
return toExamples(exampleCode(propertyDoc(name)));
254+
}
255+
247256
private String getterName(String name) {
248257
return "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
249258
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,35 @@ public String getText() {
9898
return getText(JavaDocStream.forward(javadoc, JAVADOC_TAG).toList(), false);
9999
}
100100

101+
protected String exampleCode(String text) {
102+
if (text == null) {
103+
return "";
104+
}
105+
var start = text.indexOf("`");
106+
if (start == -1) {
107+
return "";
108+
}
109+
var end = text.indexOf("`", start + 1);
110+
if (end == -1) {
111+
return "";
112+
}
113+
return text.substring(start, end + 1);
114+
}
115+
116+
protected Object toExamples(String text) {
117+
var codeExample = exampleCode(text);
118+
if (codeExample.isEmpty()) {
119+
return null;
120+
}
121+
var clean = codeExample.substring(1, codeExample.length() - 1);
122+
var result = JavaDocObjectParser.parseJson(clean);
123+
if (result.equals(codeExample)) {
124+
// Like a primitive/basic example
125+
return List.of(result);
126+
}
127+
return result;
128+
}
129+
101130
protected static String getText(List<DetailNode> nodes, boolean stripLeading) {
102131
var builder = new StringBuilder();
103132
var visited = new HashSet<DetailNode>();

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.LinkedHashMap;
1010
import java.util.List;
1111
import java.util.Map;
12+
import java.util.regex.Pattern;
1213

1314
/**
1415
* A utility class to parse a list of strings into a nested map structure. It supports multiple data
@@ -31,6 +32,7 @@ public static class UnquotedJsonParser {
3132

3233
private String text;
3334
private int index = 0;
35+
private static final Pattern WS = Pattern.compile("\\s");
3436

3537
/**
3638
* Parses the given unquoted JSON-like string into a Map.
@@ -50,7 +52,8 @@ public Object parse(String input) {
5052
} else if (text.startsWith("[") && text.endsWith("]")) {
5153
return parseArray();
5254
}
53-
return input;
55+
// generate a literal only for value without spaces
56+
return WS.matcher(input).find() ? input : parseLiteral();
5457
}
5558

5659
/**
@@ -146,15 +149,26 @@ private Object parseValue() {
146149
* Parses a literal value as a string. The literal ends at the next comma ',', closing brace
147150
* '}', or closing bracket ']'.
148151
*/
149-
private String parseLiteral() {
152+
private Object parseLiteral() {
150153
int start = index;
151154
while (index < text.length()
152155
&& text.charAt(index) != ','
153156
&& text.charAt(index) != '}'
154157
&& text.charAt(index) != ']') {
155158
index++;
156159
}
157-
return text.substring(start, index).trim();
160+
var literal = text.substring(start, index).trim();
161+
try {
162+
return Long.parseLong(literal);
163+
} catch (NumberFormatException ignored) {
164+
try {
165+
return Double.parseDouble(literal);
166+
} catch (NumberFormatException ignored2) {
167+
return "true".equals(literal)
168+
? Boolean.TRUE
169+
: "false".equals(literal) ? Boolean.FALSE : literal;
170+
}
171+
}
158172
}
159173

160174
// --- Utility Methods ---

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ public static Map<StatusCode, ResponseExt> throwList(DetailNode node) {
295295
.findFirst())
296296
.orElse(null);
297297
if (statusCode != null) {
298-
if (text == null) {
298+
if (text == null || text.trim().isEmpty()) {
299299
text = statusCode.reason();
300300
} else {
301301
text = statusCode.reason() + ": " + text;

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

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -105,38 +105,10 @@ public Object getParameterExample(String name) {
105105
return toExamples(exampleCode(parameters.get(name)));
106106
}
107107

108-
private String exampleCode(String text) {
109-
if (text == null) {
110-
return "";
111-
}
112-
var start = text.indexOf("`");
113-
if (start == -1) {
114-
return "";
115-
}
116-
var end = text.indexOf("`", start + 1);
117-
if (end == -1) {
118-
return "";
119-
}
120-
return text.substring(start, end + 1);
121-
}
122-
123-
private Object toExamples(String text) {
124-
var codeExample = exampleCode(text);
125-
if (codeExample.isEmpty()) {
126-
return null;
127-
}
128-
var clean = codeExample.substring(1, codeExample.length() - 1);
129-
var result = JavaDocObjectParser.parseJson(clean);
130-
if (result.equals(codeExample)) {
131-
// Like a primitive/basic example
132-
return List.of(result);
133-
}
134-
return result;
135-
}
136-
137108
public String getReturnDoc() {
138109
if (returnDoc != null) {
139-
return returnDoc.replace(exampleCode(returnDoc), "").trim();
110+
var result = returnDoc.replace(exampleCode(returnDoc), "").trim();
111+
return result.isEmpty() ? null : result;
140112
}
141113
return null;
142114
}

modules/jooby-openapi/src/main/java/io/jooby/openapi/OpenAPIGenerator.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@
2222
import io.jooby.SneakyThrows;
2323
import io.jooby.internal.openapi.*;
2424
import io.jooby.internal.openapi.javadoc.JavaDocParser;
25-
import io.swagger.v3.core.util.Json;
26-
import io.swagger.v3.core.util.Json31;
27-
import io.swagger.v3.core.util.Yaml;
28-
import io.swagger.v3.core.util.Yaml31;
25+
import io.swagger.v3.core.util.*;
2926
import io.swagger.v3.oas.models.OpenAPI;
3027
import io.swagger.v3.oas.models.PathItem;
3128
import io.swagger.v3.oas.models.Paths;
@@ -183,7 +180,8 @@ public String toString(OpenAPIGenerator tool, OpenAPI result) {
183180
RouteParser routes = new RouteParser();
184181
var json = jsonMapper();
185182
var yaml = yamlMapper();
186-
ParserContext ctx = new ParserContext(json, yaml, source, mainType, javadoc, debug);
183+
ParserContext ctx =
184+
new ParserContext(specVersion, json, yaml, source, mainType, javadoc, debug);
187185
List<OperationExt> operations = routes.parse(ctx, openapi);
188186

189187
String contextPath = ContextPathParser.parse(ctx);
@@ -246,6 +244,11 @@ public String toString(OpenAPIGenerator tool, OpenAPI result) {
246244
openapi.setOperations(operations);
247245
openapi.setPaths(paths);
248246

247+
if (SpecVersion.V31 == openapi.getSpecVersion()) {
248+
new OpenAPI30To31().process(openapi);
249+
openapi.setJsonSchemaDialect(null);
250+
}
251+
249252
return openapi;
250253
}
251254

modules/jooby-openapi/src/test/java/io/jooby/openapi/OpenAPIExtension.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte
4343
: EnumSet.copyOf(Arrays.asList(metadata.debug()));
4444

4545
OpenAPIGenerator tool = newTool(debugOptions);
46+
tool.setSpecVersion(metadata.version());
4647
String templateName = metadata.templateName();
4748
if (templateName.isEmpty()) {
4849
templateName = classname.replace(".", "/").toLowerCase() + ".yaml";

0 commit comments

Comments
 (0)