Skip to content

Commit 8cdf4c6

Browse files
committed
Nested data classes aren't added as component in openapi output fix #1592
1 parent 633b386 commit 8cdf4c6

File tree

9 files changed

+163
-6
lines changed

9 files changed

+163
-6
lines changed

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,26 @@ public Schema schema(Class type) {
214214
EnumSet.allOf(type).forEach(e -> schema.addEnumItem(((Enum) e).name()));
215215
return schema;
216216
}
217-
return schemas.computeIfAbsent(type.getName(), k -> {
217+
SchemaRef schemaRef = schemas.get(type.getName());
218+
if (schemaRef == null) {
218219
ResolvedSchema resolvedSchema = converters.readAllAsResolvedSchema(type);
219220
if (resolvedSchema.schema == null) {
220221
throw new IllegalArgumentException("Unsupported type: " + type);
221222
}
222-
return new SchemaRef(resolvedSchema.schema,
223+
schemaRef = new SchemaRef(resolvedSchema.schema,
223224
RefUtils.constructRef(resolvedSchema.schema.getName()));
224-
}).toSchema();
225+
schemas.put(type.getName(), schemaRef);
226+
227+
if (resolvedSchema.referencedSchemas!= null) {
228+
for (Map.Entry<String, Schema> e : resolvedSchema.referencedSchemas.entrySet()) {
229+
if (!e.getKey().equals(schemaRef.schema.getName())) {
230+
SchemaRef dependency = new SchemaRef(e.getValue(), RefUtils.constructRef(e.getValue().getName()));
231+
schemas.putIfAbsent(e.getKey(), dependency);
232+
}
233+
}
234+
}
235+
}
236+
return schemaRef.toSchema();
225237
}
226238

227239
public Optional<SchemaRef> schemaRef(String type) {

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,22 @@ public static List<String> parse(ParserContext ctx, MethodNode node) {
214214
}
215215

216216
private static String fromMethodCall(ParserContext ctx, MethodInsnNode node) {
217+
if (node.owner.equals(TypeFactory.CONTEXT.getInternalName())) {
218+
// handle: return ctx.body(MyType.class)
219+
Type[] arguments = Type.getArgumentTypes(node.desc);
220+
if (arguments.length == 1 && arguments[0].getClassName().equals(Class.class.getName())) {
221+
return InsnSupport.prev(node)
222+
.filter(LdcInsnNode.class::isInstance)
223+
.findFirst()
224+
.map(LdcInsnNode.class::cast)
225+
.filter(it -> it.cst instanceof Type)
226+
.map(it -> (Type) it.cst)
227+
.orElse(TypeFactory.OBJECT)
228+
.getClassName()
229+
;
230+
}
231+
return Object.class.getName();
232+
}
217233
Type returnType = Type.getReturnType(node.desc);
218234
ClassNode classNode;
219235
try {

modules/jooby-openapi/src/test/java/examples/FilterApp.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ public class FilterApp extends Jooby {
1515

1616
});
1717

18-
get("/api/profile/{id}", ctx -> "profile ID");
18+
get("/api/profile/{id}", ctx -> ctx.path("id").value());
1919
}
2020
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,12 @@ public void shouldIncludes(OpenAPIResult result) {
510510
+ " /api/profile/{id}:\n"
511511
+ " get:\n"
512512
+ " operationId: getApiProfileId\n"
513+
+ " parameters:\n"
514+
+ " - name: id\n"
515+
+ " in: path\n"
516+
+ " required: true\n"
517+
+ " schema:\n"
518+
+ " type: string\n"
513519
+ " responses:\n"
514520
+ " \"200\":\n"
515521
+ " description: Success\n"
@@ -540,6 +546,12 @@ public void shouldExcludes(OpenAPIResult result) {
540546
+ " /api/profile/{id}:\n"
541547
+ " get:\n"
542548
+ " operationId: getApiProfileId\n"
549+
+ " parameters:\n"
550+
+ " - name: id\n"
551+
+ " in: path\n"
552+
+ " required: true\n"
553+
+ " schema:\n"
554+
+ " type: string\n"
543555
+ " responses:\n"
544556
+ " \"200\":\n"
545557
+ " description: Success\n"

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void shouldParseExampleFromParameters(OpenAPIResult result) {
2020
+ " /1591:\n"
2121
+ " get:\n"
2222
+ " summary: Example.\n"
23-
+ " operationId: missingAttributes\n"
23+
+ " operationId: parameterExample\n"
2424
+ " parameters:\n"
2525
+ " - name: arg1\n"
2626
+ " in: path\n"
@@ -45,7 +45,9 @@ public void shouldParseExampleFromParameters(OpenAPIResult result) {
4545
+ " content:\n"
4646
+ " application/json:\n"
4747
+ " schema:\n"
48-
+ " type: string\n", result.toYaml());
48+
+ " type: string\n"
49+
+ " security:\n"
50+
+ " - basicAuth: []\n", result.toYaml());
4951
}
5052

5153
@OpenAPITest(App1591b.class)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package issues;
2+
3+
import io.jooby.openapi.OpenAPIResult;
4+
import io.jooby.openapi.OpenAPITest;
5+
import issues.i1592.App1592;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
9+
public class Issue1592 {
10+
11+
@OpenAPITest(value = App1592.class)
12+
public void shouldParseNestedTypes(OpenAPIResult result) {
13+
assertEquals("openapi: 3.0.1\n"
14+
+ "info:\n"
15+
+ " title: 1592 API\n"
16+
+ " description: 1592 API description\n"
17+
+ " version: \"1.0\"\n"
18+
+ "paths:\n"
19+
+ " /nested:\n"
20+
+ " post:\n"
21+
+ " operationId: postNested\n"
22+
+ " requestBody:\n"
23+
+ " content:\n"
24+
+ " application/json:\n"
25+
+ " schema:\n"
26+
+ " $ref: '#/components/schemas/FairData'\n"
27+
+ " responses:\n"
28+
+ " \"200\":\n"
29+
+ " description: Success\n"
30+
+ " content:\n"
31+
+ " application/json:\n"
32+
+ " schema:\n"
33+
+ " $ref: '#/components/schemas/FairData'\n"
34+
+ "components:\n"
35+
+ " schemas:\n"
36+
+ " FairEmissionData:\n"
37+
+ " type: object\n"
38+
+ " properties:\n"
39+
+ " co2Emissions:\n"
40+
+ " type: array\n"
41+
+ " items:\n"
42+
+ " type: number\n"
43+
+ " format: double\n"
44+
+ " FairData:\n"
45+
+ " type: object\n"
46+
+ " properties:\n"
47+
+ " baseYear:\n"
48+
+ " type: integer\n"
49+
+ " format: int32\n"
50+
+ " finalYear:\n"
51+
+ " type: integer\n"
52+
+ " format: int32\n"
53+
+ " annualEmissions:\n"
54+
+ " $ref: '#/components/schemas/FairEmissionData'\n", result.toYaml());
55+
}
56+
57+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package issues.i1592;
2+
3+
import io.jooby.Jooby;
4+
5+
public class App1592 extends Jooby {
6+
7+
{
8+
post("/nested", ctx -> ctx.body(FairData.class));
9+
}
10+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package issues.i1592;
2+
3+
public class FairData {
4+
private int baseYear;
5+
6+
private int finalYear;
7+
8+
private FairEmissionData annualEmissions;
9+
10+
public int getBaseYear() {
11+
return baseYear;
12+
}
13+
14+
public void setBaseYear(int baseYear) {
15+
this.baseYear = baseYear;
16+
}
17+
18+
public int getFinalYear() {
19+
return finalYear;
20+
}
21+
22+
public void setFinalYear(int finalYear) {
23+
this.finalYear = finalYear;
24+
}
25+
26+
public FairEmissionData getAnnualEmissions() {
27+
return annualEmissions;
28+
}
29+
30+
public void setAnnualEmissions(FairEmissionData annualEmissions) {
31+
this.annualEmissions = annualEmissions;
32+
}
33+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package issues.i1592;
2+
3+
import java.util.List;
4+
5+
public class FairEmissionData {
6+
private List<Double> co2Emissions;
7+
8+
public List<Double> getCo2Emissions() {
9+
return co2Emissions;
10+
}
11+
12+
public void setCo2Emissions(List<Double> co2Emissions) {
13+
this.co2Emissions = co2Emissions;
14+
}
15+
}

0 commit comments

Comments
 (0)