Skip to content

Commit 1f47591

Browse files
authored
Merge pull request #164 from SentryMan/serializer
support @SecurityScheme/Fix OpenAPI Serializer
2 parents 9191ce2 + 9fd2c18 commit 1f47591

File tree

16 files changed

+465
-227
lines changed

16 files changed

+465
-227
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
[![Build](https://github.com/avaje/avaje-http/actions/workflows/build.yml/badge.svg)](https://github.com/avaje/avaje-http/actions/workflows/build.yml)
33
<img src="https://img.shields.io/maven-central/v/io.avaje/avaje-http-api.svg?label=Maven%20Central">
44
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/avaje/avaje-inject/blob/master/LICENSE)
5+
[![Discord](https://img.shields.io/discord/1074074312421683250?color=%237289da&label=discord)](https://discord.gg/Qcqf9R27BR)
56

67
HTTP server and client libraries via code generation.
78

http-generator-core/pom.xml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@
1111
<relativePath>..</relativePath>
1212
</parent>
1313

14-
<properties>
15-
<swagger.version>2.0.8</swagger.version>
16-
</properties>
17-
1814
<dependencies>
19-
20-
2115
<dependency>
2216
<groupId>io.avaje</groupId>
2317
<artifactId>avaje-prisms</artifactId>
@@ -34,6 +28,12 @@
3428
<scope>provided</scope>
3529
</dependency>
3630

31+
<dependency>
32+
<groupId>io.swagger.core.v3</groupId>
33+
<artifactId>swagger-annotations</artifactId>
34+
<version>${swagger.version}</version>
35+
<scope>provided</scope>
36+
</dependency>
3737
<dependency>
3838
<groupId>io.swagger.core.v3</groupId>
3939
<artifactId>swagger-models</artifactId>

http-generator-core/src/main/java/io/avaje/http/generator/core/BaseProcessor.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.io.IOException;
44
import java.util.Set;
5-
65
import javax.annotation.processing.AbstractProcessor;
76
import javax.annotation.processing.ProcessingEnvironment;
87
import javax.annotation.processing.RoundEnvironment;
@@ -41,6 +40,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
4140
if (ctx.isOpenApiAvailable()) {
4241
readOpenApiDefinition(round);
4342
readTagDefinitions(round);
43+
readSecuritySchemes(round);
4444
}
4545

4646
final Set<? extends Element> controllers =
@@ -75,7 +75,19 @@ private void readTagDefinitions(RoundEnvironment round) {
7575
ctx.doc().addTagsDefinition(element);
7676
}
7777
}
78-
78+
79+
private void readSecuritySchemes(RoundEnvironment round) {
80+
Set<? extends Element> elements = round.getElementsAnnotatedWith(ctx.typeElement(SecuritySchemePrism.PRISM_TYPE));
81+
for (Element element : elements) {
82+
ctx.doc().addSecurityScheme(element);
83+
}
84+
85+
elements = round.getElementsAnnotatedWith(ctx.typeElement(SecuritySchemesPrism.PRISM_TYPE));
86+
for (Element element : elements) {
87+
ctx.doc().addSecuritySchemes(element);
88+
}
89+
}
90+
7991
private void writeOpenAPI() {
8092
ctx.doc().writeApi();
8193
}

http-generator-core/src/main/java/io/avaje/http/generator/core/MethodReader.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
package io.avaje.http.generator.core;
22

3+
import io.avaje.http.generator.core.javadoc.Javadoc;
4+
import io.avaje.http.generator.core.openapi.MethodDocBuilder;
35
import java.util.ArrayList;
6+
import java.util.HashMap;
47
import java.util.List;
58
import java.util.Optional;
9+
import java.util.function.Consumer;
610
import java.util.function.Function;
711
import java.util.function.Predicate;
812
import java.util.stream.Collectors;
913
import java.util.stream.Stream;
10-
14+
import javax.lang.model.element.AnnotationMirror;
1115
import javax.lang.model.element.Element;
1216
import javax.lang.model.element.ExecutableElement;
1317
import javax.lang.model.element.VariableElement;
1418
import javax.lang.model.type.ExecutableType;
1519
import javax.lang.model.type.TypeKind;
1620
import javax.lang.model.type.TypeMirror;
1721

18-
import io.avaje.http.generator.core.javadoc.Javadoc;
19-
import io.avaje.http.generator.core.openapi.MethodDocBuilder;
20-
2122
public class MethodReader {
2223

2324
private final ProcessingContext ctx;
@@ -31,6 +32,7 @@ public class MethodReader {
3132
*/
3233
private final List<String> methodRoles;
3334
private final Optional<ProducesPrism> producesAnnotation;
35+
private final List<SecurityRequirementPrism> securityRequirements;
3436
private final List<OpenAPIResponsePrism> apiResponses;
3537
private final ExecutableType actualExecutable;
3638
private final List<? extends TypeMirror> actualParams;
@@ -57,6 +59,7 @@ public class MethodReader {
5759
ctx.superMethods(element.getEnclosingElement(), element.getSimpleName().toString());
5860
superMethods.forEach(m -> methodRoles.addAll(Util.findRoles(m)));
5961

62+
this.securityRequirements = readSecurityRequirements();
6063
this.apiResponses = buildApiResponses();
6164
this.javadoc = buildJavadoc(element, ctx);
6265

@@ -130,6 +133,40 @@ public Javadoc javadoc() {
130133
return javadoc;
131134
}
132135

136+
private List<SecurityRequirementPrism> readSecurityRequirements() {
137+
var list = new ArrayList<SecurityRequirementPrism>();
138+
139+
readSecurityRequirements(element, list);
140+
for (ExecutableElement superMethod : superMethods) {
141+
readSecurityRequirements(superMethod, list);
142+
}
143+
readSecurityRequirements(bean.beanType(), list);
144+
145+
var map = new HashMap<String, SecurityRequirementPrism>();
146+
for (SecurityRequirementPrism p : list) {
147+
if (!map.containsKey(p.name())) {
148+
map.put(p.name(), p);
149+
}
150+
}
151+
return List.copyOf(map.values());
152+
}
153+
154+
private void readSecurityRequirements(Element element, List<SecurityRequirementPrism> list) {
155+
Consumer<Element> f = e -> {
156+
Optional.ofNullable(SecurityRequirementsPrism.getInstanceOn(e))
157+
.map(SecurityRequirementsPrism::value)
158+
.ifPresent(list::addAll);
159+
Optional.ofNullable(SecurityRequirementPrism.getAllInstancesOn(e))
160+
.ifPresent(list::addAll);
161+
};
162+
f.accept(element);
163+
164+
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
165+
// find only one level
166+
f.accept(annotationMirror.getAnnotationType().asElement());
167+
}
168+
}
169+
133170
private List<OpenAPIResponsePrism> buildApiResponses() {
134171

135172
final var container =
@@ -260,6 +297,10 @@ public String produces() {
260297
return producesAnnotation.map(ProducesPrism::value).orElseGet(bean::produces);
261298
}
262299

300+
public List<SecurityRequirementPrism> securityRequirements() {
301+
return securityRequirements;
302+
}
303+
263304
public List<OpenAPIResponsePrism> apiResponses() {
264305
return apiResponses;
265306
}

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

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import java.io.Writer;
5+
import java.util.List;
56
import java.util.Map;
67
import java.util.TreeMap;
78

@@ -17,6 +18,8 @@
1718
import javax.tools.StandardLocation;
1819

1920
import io.avaje.http.generator.core.OpenAPIDefinitionPrism;
21+
import io.avaje.http.generator.core.SecuritySchemePrism;
22+
import io.avaje.http.generator.core.SecuritySchemesPrism;
2023
import io.avaje.http.generator.core.TagPrism;
2124
import io.avaje.http.generator.core.TagsPrism;
2225
import io.swagger.v3.oas.models.Components;
@@ -27,6 +30,7 @@
2730
import io.swagger.v3.oas.models.info.Info;
2831
import io.swagger.v3.oas.models.media.Content;
2932
import io.swagger.v3.oas.models.media.Schema;
33+
import io.swagger.v3.oas.models.security.SecurityScheme;
3034
import io.swagger.v3.oas.models.tags.Tag;
3135

3236
/** Context for building the OpenAPI documentation. */
@@ -148,10 +152,43 @@ public void addTagsDefinition(Element element) {
148152
}
149153

150154
public void addTagDefinition(Element element) {
151-
final var tag = TagPrism.getInstanceOn(element);
152-
if (tag == null) return;
153155

154-
openAPI.addTagsItem(createTagItem(tag));
156+
for (var tag : TagPrism.getAllInstancesOn(element)) {
157+
openAPI.addTagsItem(createTagItem(tag));
158+
}
159+
}
160+
161+
public void addSecurityScheme(Element element) {
162+
163+
this.addSecuritySchemes(SecuritySchemePrism.getAllInstancesOn(element));
164+
}
165+
166+
public void addSecuritySchemes(Element element) {
167+
var schemes = SecuritySchemesPrism.getInstanceOn(element);
168+
if (schemes == null) {
169+
return;
170+
}
171+
this.addSecuritySchemes(schemes.value());
172+
}
173+
174+
void addSecuritySchemes(List<SecuritySchemePrism> schemes) {
175+
for (SecuritySchemePrism p : schemes) {
176+
var ss = new SecurityScheme()
177+
.type(SecurityScheme.Type.valueOf(p.type()))
178+
.in(SecurityScheme.In.valueOf(p.in()))
179+
.name(p.paramName());
180+
181+
if (!p.description().isEmpty()) {
182+
ss.description(p.description());
183+
}
184+
if (!p.bearerFormat().isEmpty()) {
185+
ss.bearerFormat(p.bearerFormat());
186+
}
187+
if (!p.scheme().isEmpty()) {
188+
ss.scheme(p.scheme());
189+
}
190+
components().addSecuritySchemes(p.name(), ss);
191+
}
155192
}
156193

157194
public void readApiDefinition(Element element) {
@@ -167,15 +204,11 @@ public void readApiDefinition(Element element) {
167204
if (!info.version().isEmpty()) {
168205
openAPI.getInfo().setVersion(info.version());
169206
}
170-
171207
}
172208

173209
public void writeApi() {
174-
175-
final var openAPI = getApiForWriting();
176210
try (var metaWriter = createMetaWriter()) {
177-
178-
final var json = OpenAPISerializer.serialize(openAPI);
211+
final var json = OpenAPISerializer.serialize(getApiForWriting());
179212
JsonFormatter.prettyPrintJson(metaWriter, json);
180213

181214
} catch (final Exception e) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
import io.avaje.http.generator.core.HiddenPrism;
44
import io.avaje.http.generator.core.MethodParam;
55
import io.avaje.http.generator.core.MethodReader;
6+
import io.avaje.http.generator.core.SecurityRequirementPrism;
67
import io.avaje.http.generator.core.javadoc.Javadoc;
78
import io.avaje.prism.GeneratePrism;
89
import io.swagger.v3.oas.models.Operation;
910
import io.swagger.v3.oas.models.PathItem;
1011
import io.swagger.v3.oas.models.responses.ApiResponse;
1112
import io.swagger.v3.oas.models.responses.ApiResponses;
13+
import io.swagger.v3.oas.models.security.SecurityRequirement;
1214

1315
/** Build the OpenAPI documentation for a method. */
1416
@GeneratePrism(Deprecated.class)
@@ -62,6 +64,12 @@ public void build() {
6264
break;
6365
}
6466

67+
var securityRequirements = methodReader.securityRequirements();
68+
for (SecurityRequirementPrism p : securityRequirements) {
69+
var o = new SecurityRequirement().addList(p.name(), p.scopes());
70+
operation.addSecurityItem(o);
71+
}
72+
6573
for (MethodParam param : methodReader.params()) {
6674
param.buildApiDocumentation(this);
6775
}

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,21 @@ static String serialize(Object obj) throws IllegalAccessException {
5858
} else {
5959

6060
sb.append("{");
61-
if (obj instanceof String) {
62-
System.out.println();
63-
}
6461

6562
final var fields = getAllFields(cls);
6663

6764
var firstField = true;
6865
for (final Field field : fields) {
66+
67+
// skip JsonIgnored fields
68+
if ("BIND_TYPE_AND_TYPES".equals(field.getName())
69+
|| "COMPONENTS_SCHEMAS_REF".equals(field.getName())
70+
|| "exampleSetFlag".equals(field.getName())
71+
|| "types".equals(field.getName())
72+
|| "specVersion".equals(field.getName())) {
73+
continue;
74+
}
75+
6976
field.setAccessible(true);
7077
final var value = field.get(obj);
7178
if (value != null) {
@@ -168,6 +175,10 @@ static void write(StringBuilder sb, Object value) throws IllegalAccessException
168175
sb.append(value.toString().replace("\"", "\\\""));
169176
sb.append("\"");
170177
}
178+
} else if (value.getClass().isEnum()) {
179+
sb.append("\"");
180+
sb.append(value.toString().replace("\"", "\\\""));
181+
sb.append("\"");
171182
} else {
172183
// Recursively handle other object types
173184
sb.append(serialize(value));

http-generator-core/src/main/java/io/avaje/http/generator/core/package-info.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
@GeneratePrism(value = io.swagger.v3.oas.annotations.OpenAPIDefinition.class, publicAccess = true)
2020
@GeneratePrism(value = io.swagger.v3.oas.annotations.tags.Tag.class, publicAccess = true)
2121
@GeneratePrism(value = io.swagger.v3.oas.annotations.tags.Tags.class, publicAccess = true)
22+
@GeneratePrism(value = io.swagger.v3.oas.annotations.security.SecurityScheme.class, publicAccess = true)
23+
@GeneratePrism(value = io.swagger.v3.oas.annotations.security.SecuritySchemes.class, publicAccess = true)
24+
@GeneratePrism(value = io.swagger.v3.oas.annotations.security.SecurityRequirement.class, publicAccess = true)
25+
@GeneratePrism(value = io.swagger.v3.oas.annotations.security.SecurityRequirements.class, publicAccess = true)
2226
@GeneratePrism(value = io.avaje.http.api.OpenAPIResponse.class, publicAccess = true)
2327
@GeneratePrism(value = io.avaje.http.api.OpenAPIResponses.class, publicAccess = true)
2428
@GeneratePrism(value = io.swagger.v3.oas.annotations.Hidden.class, publicAccess = true)

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
<properties>
2121
<nexus.staging.autoReleaseAfterClose>true</nexus.staging.autoReleaseAfterClose>
22-
<swagger.version>2.0.8</swagger.version>
22+
<swagger.version>2.2.8</swagger.version>
23+
<jackson.version>2.14.2</jackson.version>
2324
<module-info.shade>${project.build.directory}${file.separator}module-info.shade</module-info.shade>
2425
</properties>
2526

@@ -103,7 +104,6 @@
103104
<excludes>
104105
<exclude>io/swagger/v3/oas/models/callbacks/**</exclude>
105106
<exclude>io/swagger/v3/oas/models/examples/**</exclude>
106-
<exclude>io/swagger/v3/oas/models/security/**</exclude>
107107
<exclude>io/swagger/v3/oas/models/servers/**</exclude>
108108
</excludes>
109109
</filter>

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
package org.example.myapp;
22

3-
import java.util.LinkedHashMap;
4-
import java.util.List;
5-
import java.util.Map;
6-
7-
import org.slf4j.Logger;
8-
import org.slf4j.LoggerFactory;
9-
103
import io.avaje.http.api.InvalidPathArgumentException;
114
import io.avaje.http.api.InvalidTypeArgumentException;
125
import io.avaje.http.api.ValidationException;
@@ -17,10 +10,19 @@
1710
import io.javalin.Javalin;
1811
import io.javalin.http.staticfiles.Location;
1912
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
13+
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
14+
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
2015
import io.swagger.v3.oas.annotations.info.Info;
16+
import io.swagger.v3.oas.annotations.security.SecurityScheme;
17+
import java.util.LinkedHashMap;
18+
import java.util.List;
19+
import java.util.Map;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
2122

2223
@InjectModule(name = "app", requires = Validator.class)
2324
@OpenAPIDefinition(info = @Info(title = "Example service", description = "Example Javalin controllers with Java and Maven"))
25+
@SecurityScheme(type = SecuritySchemeType.APIKEY, in = SecuritySchemeIn.QUERY, name = "JWT", paramName = "access_token", description = "JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.")
2426
public class Main {
2527

2628
private static final Logger log = LoggerFactory.getLogger(Main.class);

0 commit comments

Comments
 (0)