Skip to content

Commit 65b186a

Browse files
authored
Fix Helidon path openapi generation (#596)
* Update OpenAPIController.java * add another openAPI test to javalin * fix helidon path openAPI generation
1 parent 551a5cb commit 65b186a

File tree

8 files changed

+485
-11
lines changed

8 files changed

+485
-11
lines changed

http-generator-core/src/main/java/io/avaje/http/generator/core/openapi/MethodParamDocBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private String requestMedia(Schema<?> schema) {
8181
if (schema instanceof StringSchema) {
8282
return APP_TXT;
8383
}
84-
boolean asForm = (paramType == ParamType.FORM);
84+
boolean asForm = paramType == ParamType.FORM;
8585
return asForm ? APP_FORM : APP_JSON;
8686
}
8787

http-generator-helidon/src/main/java/io/avaje/http/generator/helidon/nima/ControllerWriter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ private void writeAddRoutes() {
9999
writeRoutes(methods);
100100
for (final ControllerMethodWriter methodWriter : methods) {
101101
methodWriter.writeHandler(isRequestScoped());
102+
if (!reader.isDocHidden()) {
103+
methodWriter.buildApiDocumentation();
104+
}
102105
}
103106
}
104107

@@ -108,9 +111,6 @@ private void writeRoutes(List<ControllerMethodWriter> methods) {
108111

109112
for (final ControllerMethodWriter methodWriter : methods) {
110113
methodWriter.writeRule();
111-
if (!reader.isDocHidden()) {
112-
methodWriter.buildApiDocumentation();
113-
}
114114
}
115115
writer.append(" }").eol().eol();
116116
}

tests/test-javalin-jsonb/src/main/java/org/example/myapp/web/test/OpenAPIController.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
import java.util.List;
44

55
import io.avaje.http.api.Controller;
6+
import io.avaje.http.api.Delete;
67
import io.avaje.http.api.Get;
8+
import io.avaje.http.api.Header;
79
import io.avaje.http.api.MediaType;
810
import io.avaje.http.api.OpenAPIResponse;
911
import io.avaje.http.api.OpenAPIResponses;
1012
import io.avaje.http.api.Path;
1113
import io.avaje.http.api.Post;
1214
import io.avaje.http.api.Produces;
1315
import io.avaje.http.api.Put;
16+
import io.avaje.http.api.QueryParam;
1417
import io.javalin.http.Context;
1518
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
1619
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
@@ -93,10 +96,16 @@ Person testPostList(List<Person> m) {
9396
@Produces(value = MediaType.TEXT_PLAIN, statusCode = 203)
9497
@OpenAPIResponse(responseCode = 204, type = String.class)
9598
String testDefaultStatus(Context ctx) {
96-
if (ctx.contentType().equals(MediaType.APPLICATION_PDF)) {
99+
if (MediaType.APPLICATION_PDF.equals(ctx.contentType())) {
97100
ctx.status(204);
98101
return "";
99102
}
100103
return "only partial info";
101104
}
105+
106+
@Delete("/delete/{type}")
107+
String testPathParam(String type, @QueryParam String lastName, @Header String header) {
108+
109+
return "only partial info";
110+
}
102111
}

tests/test-javalin-jsonb/src/test/resources/expectedOpenApi.json

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,64 @@
55
"description" : "Example Javalin controllers with Java and Maven",
66
"version" : ""
77
},
8-
"servers" : [
9-
{
10-
"url" : "localhost:8080",
11-
"description" : "local testing"
12-
}
13-
],
8+
"servers" : [
9+
{
10+
"url" : "localhost:8080",
11+
"description" : "local testing"
12+
}
13+
],
1414
"tags" : [
1515
{
1616
"name" : "tag1",
1717
"description" : "this is added to openapi tags"
1818
}
1919
],
2020
"paths" : {
21+
"/openapi/delete/{type}" : {
22+
"delete" : {
23+
"tags" : [
24+
25+
],
26+
"summary" : "",
27+
"description" : "",
28+
"parameters" : [
29+
{
30+
"name" : "type",
31+
"in" : "path",
32+
"required" : true,
33+
"schema" : {
34+
"type" : "string"
35+
}
36+
},
37+
{
38+
"name" : "lastName",
39+
"in" : "query",
40+
"schema" : {
41+
"type" : "string"
42+
}
43+
},
44+
{
45+
"name" : "header",
46+
"in" : "header",
47+
"schema" : {
48+
"type" : "string"
49+
}
50+
}
51+
],
52+
"responses" : {
53+
"200" : {
54+
"description" : "",
55+
"content" : {
56+
"application/json" : {
57+
"schema" : {
58+
"type" : "string"
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
},
2166
"/openapi/get" : {
2267
"get" : {
2368
"tags" : [

tests/test-nima-jsonb/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,24 @@
1313

1414
<properties>
1515
<maven.compiler.release>21</maven.compiler.release>
16+
<swagger.version>2.2.30</swagger.version>
1617
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1718
<surefire.useModulePath>false</surefire.useModulePath>
1819
</properties>
1920

2021
<dependencies>
22+
<dependency>
23+
<groupId>io.swagger.core.v3</groupId>
24+
<artifactId>swagger-annotations</artifactId>
25+
<version>${swagger.version}</version>
26+
</dependency>
27+
<dependency>
28+
<groupId>com.fasterxml.jackson.core</groupId>
29+
<artifactId>jackson-databind</artifactId>
30+
<version>${jackson.version}</version>
31+
</dependency>
32+
33+
2134
<dependency>
2235
<groupId>io.avaje</groupId>
2336
<artifactId>avaje-inject</artifactId>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package org.example;
2+
3+
import java.util.List;
4+
5+
import io.avaje.http.api.Controller;
6+
import io.avaje.http.api.Delete;
7+
import io.avaje.http.api.Get;
8+
import io.avaje.http.api.Header;
9+
import io.avaje.http.api.MediaType;
10+
import io.avaje.http.api.OpenAPIResponse;
11+
import io.avaje.http.api.OpenAPIResponses;
12+
import io.avaje.http.api.Path;
13+
import io.avaje.http.api.Post;
14+
import io.avaje.http.api.Produces;
15+
import io.avaje.http.api.Put;
16+
import io.avaje.http.api.QueryParam;
17+
import io.helidon.webserver.http.ServerRequest;
18+
import io.helidon.webserver.http.ServerResponse;
19+
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
20+
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
21+
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
22+
import io.swagger.v3.oas.annotations.info.Info;
23+
import io.swagger.v3.oas.annotations.security.SecurityScheme;
24+
import io.swagger.v3.oas.annotations.tags.Tag;
25+
26+
@OpenAPIDefinition(
27+
info =
28+
@Info(
29+
title = "Example service",
30+
description = "Example Helidon controllers with Java and Maven"))
31+
@Controller
32+
@Path("openapi/")
33+
@SecurityScheme(
34+
type = SecuritySchemeType.APIKEY,
35+
in = SecuritySchemeIn.QUERY,
36+
name = "JWT",
37+
paramName = "access_token",
38+
description =
39+
"JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.")
40+
public class OpenAPIController {
41+
42+
/**
43+
* Example of Open API Get (up to the first period is the summary). When using Javalin Context
44+
* only <br>
45+
* This Javadoc description is added to the generated openapi.json
46+
*
47+
* @return funny phrase (this part of the javadoc is added to the response desc)
48+
*/
49+
@Get("/get")
50+
@Produces(MediaType.TEXT_PLAIN)
51+
@OpenAPIResponse(responseCode = 200, type = String.class)
52+
void ctxEndpoint(ServerRequest req, ServerResponse res) {}
53+
54+
/**
55+
* Standard Post. uses tag annotation to add tags to openapi json
56+
*
57+
* @param b the body (this is used for generated request body desc)
58+
* @return the response body (from javadoc)
59+
*/
60+
@Post("/post")
61+
@Tag(name = "tag1", description = "this is added to openapi tags")
62+
@OpenAPIResponse(responseCode = 200, description = "overrides @return javadoc description")
63+
@OpenAPIResponse(responseCode = 201)
64+
@OpenAPIResponse(
65+
responseCode = 400,
66+
description = "User not found (Will not have an associated response schema)")
67+
@OpenAPIResponse(
68+
responseCode = 500,
69+
description = "Some other Error (Will have this error class as the response class)",
70+
type = ErrorResponse.class)
71+
Person testPost(Person b) {
72+
return new Person(0, "baby");
73+
}
74+
75+
public static class ErrorResponse {
76+
77+
public String id;
78+
public String text;
79+
}
80+
81+
/**
82+
* Standard Post. The Deprecated annotation adds "deprecacted:true" to the generated json
83+
*
84+
* @param m the body
85+
* @return the response body (from javadoc)
86+
*/
87+
@Deprecated
88+
@Post("/post1")
89+
@OpenAPIResponses({
90+
@OpenAPIResponse(responseCode = 400, description = "User not found"),
91+
@OpenAPIResponse(
92+
responseCode = 500,
93+
description = "Some other Error",
94+
type = ErrorResponse.class)
95+
})
96+
Person testPostList(List<Person> m) {
97+
return new Person(0, "baby");
98+
}
99+
100+
@Put("/put")
101+
@Produces(value = MediaType.TEXT_PLAIN, statusCode = 203)
102+
@OpenAPIResponse(responseCode = 204, type = String.class)
103+
String testDefaultStatus() {
104+
105+
return "only partial info";
106+
}
107+
108+
@Delete("/delete/{type}")
109+
String testPathParam(String type, @QueryParam String lastName, @Header String header) {
110+
111+
return "only partial info";
112+
}
113+
}

tests/test-nima-jsonb/src/test/java/io/avaje/http/generator/NimaProcessorTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.junit.jupiter.api.AfterEach;
2222
import org.junit.jupiter.api.Test;
2323

24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
2426
import io.avaje.http.generator.helidon.nima.HelidonProcessor;
2527

2628
class NimaProcessorTest {
@@ -76,4 +78,37 @@ private Iterable<JavaFileObject> getSourceFiles(String source) throws Exception
7678
final Set<Kind> fileKinds = Collections.singleton(Kind.SOURCE);
7779
return files.list(StandardLocation.SOURCE_PATH, "", fileKinds, true);
7880
}
81+
82+
@Test
83+
public void testOpenAPIGeneration() throws Exception {
84+
final var source = Paths.get("src").toAbsolutePath().toString();
85+
// OpenAPIController
86+
final var files = getSourceFiles(source);
87+
88+
Iterable<JavaFileObject> openAPIController = null;
89+
for (final var file : files) {
90+
if (file.isNameCompatible("OpenAPIController", Kind.SOURCE))
91+
openAPIController = List.of(file);
92+
}
93+
final var compiler = ToolProvider.getSystemJavaCompiler();
94+
95+
final var task =
96+
compiler.getTask(
97+
new PrintWriter(System.out),
98+
null,
99+
null,
100+
List.of("--release=21"),
101+
null,
102+
openAPIController);
103+
task.setProcessors(List.of(new HelidonProcessor()));
104+
105+
assertThat(task.call()).isTrue();
106+
107+
final var mapper = new ObjectMapper();
108+
final var expectedOpenApiJson =
109+
mapper.readTree(new File("src/test/resources/expectedOpenApi.json"));
110+
final var generatedOpenApi = mapper.readTree(new File("openapi.json"));
111+
112+
assert expectedOpenApiJson.equals(generatedOpenApi);
113+
}
79114
}

0 commit comments

Comments
 (0)