Skip to content

Commit 633b386

Browse files
committed
OpenAPI: Parameter examples and SecurityRequirement
fix #1591
1 parent 0d987a3 commit 633b386

File tree

5 files changed

+234
-1
lines changed

5 files changed

+234
-1
lines changed

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
import io.swagger.v3.oas.annotations.media.Schema;
1616
import io.swagger.v3.oas.annotations.responses.ApiResponse;
1717
import io.swagger.v3.oas.annotations.responses.ApiResponses;
18+
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
19+
import io.swagger.v3.oas.models.examples.Example;
1820
import io.swagger.v3.oas.models.headers.Header;
1921
import io.swagger.v3.oas.models.media.ComposedSchema;
2022
import io.swagger.v3.oas.models.media.StringSchema;
23+
import io.swagger.v3.oas.models.security.SecurityRequirement;
2124
import org.objectweb.asm.Type;
2225
import org.objectweb.asm.tree.AnnotationNode;
2326
import org.objectweb.asm.tree.MethodNode;
2427

28+
import java.util.ArrayList;
2529
import java.util.Collections;
2630
import java.util.HashMap;
2731
import java.util.LinkedHashMap;
@@ -30,6 +34,7 @@
3034
import java.util.Objects;
3135
import java.util.Optional;
3236
import java.util.function.BiConsumer;
37+
import java.util.function.Consumer;
3338
import java.util.stream.Collectors;
3439

3540
import static io.jooby.internal.openapi.AsmUtils.boolValue;
@@ -49,6 +54,22 @@ public static void parse(ParserContext ctx, MethodNode method, OperationExt oper
4954
.findFirst()
5055
.ifPresent(a -> swaggerOperation(ctx, operation, toMap(a)));
5156

57+
/** SecurityRequirements: */
58+
findAnnotationByType(method.visibleAnnotations,
59+
singletonList(SecurityRequirements.class.getName()))
60+
.stream()
61+
.map(a ->
62+
(List<AnnotationNode>) toMap(a).getOrDefault("value", emptyList())
63+
)
64+
.forEach(a -> securityRequirements(operation, a));
65+
66+
/** SecurityRequirement: */
67+
findAnnotationByType(method.visibleAnnotations,
68+
singletonList(io.swagger.v3.oas.annotations.security.SecurityRequirement.class.getName()))
69+
.stream()
70+
.findFirst()
71+
.ifPresent(a -> securityRequirements(operation, Collections.singletonList(a)));
72+
5273
/** @ApiResponses: */
5374
findAnnotationByType(method.visibleAnnotations, singletonList(ApiResponses.class.getName()))
5475
.stream()
@@ -97,6 +118,9 @@ private static void swaggerOperation(ParserContext ctx, OperationExt operation,
97118
List<String> tags = (List<String>) annotation.getOrDefault("tags", emptyList());
98119
tags.forEach(operation::addTagsItem);
99120

121+
securityRequirements(operation,
122+
(List<AnnotationNode>) annotation.getOrDefault("security", Collections.emptyList()));
123+
100124
parameters(ctx, operation,
101125
(List<AnnotationNode>) annotation.getOrDefault("parameters", Collections.emptyList()));
102126

@@ -105,6 +129,24 @@ private static void swaggerOperation(ParserContext ctx, OperationExt operation,
105129
responses(ctx, operation, annotation);
106130
}
107131

132+
private static void securityRequirements(OperationExt operation,
133+
List<AnnotationNode> securityRequirements) {
134+
List<SecurityRequirement> requirements = new ArrayList<>();
135+
for (AnnotationNode annotation : securityRequirements) {
136+
Map<String, Object> securityMap = toMap(annotation);
137+
String name = (String) securityMap.get("name");
138+
List<String> scopes = (List<String>) securityMap
139+
.getOrDefault("scopes", Collections.emptyList());
140+
SecurityRequirement requirement = new SecurityRequirement();
141+
requirement.addList(name, scopes);
142+
143+
requirements.add(requirement);
144+
}
145+
if (requirements.size() > 0) {
146+
operation.setSecurity(requirements);
147+
}
148+
}
149+
108150
private static void requestBody(ParserContext ctx, OperationExt operation,
109151
Map<String, Object> annotation) {
110152
if (annotation.size() > 0) {
@@ -166,6 +208,31 @@ private static void parameters(ParserContext ctx, OperationExt operation,
166208
enumValue(parameterMap, "explode", value -> parameter.setExample(Boolean.valueOf(value)));
167209
stringValue(parameterMap, "ref", ref -> parameter.set$ref(RefUtils.constructRef(ref)));
168210
arrayOrSchema(ctx, parameterMap).ifPresent(parameter::setSchema);
211+
examples(parameterMap, parameter::setExample, parameter::setExamples);
212+
}
213+
}
214+
215+
private static void examples(Map<String, Object> annotation, Consumer<String> exampleConsumer,
216+
Consumer<Map<String, Example>> consumer) {
217+
List<Map<String, Object>> annotations = ((List<AnnotationNode>) annotation
218+
.getOrDefault("examples", emptyList())).stream()
219+
.map(AsmUtils::toMap)
220+
.collect(Collectors.toList());
221+
222+
Map<String, Example> examples = new LinkedHashMap<>();
223+
stringValue(annotation, "example", exampleConsumer);
224+
225+
for (Map<String, Object> e : annotations) {
226+
Example example = new Example();
227+
stringValue(e, "summary", example::setSummary);
228+
stringValue(e, "description", example::setDescription);
229+
stringValue(e, "value", example::setValue);
230+
stringValue(e, "externalValue", example::setExternalValue);
231+
String key = (String) e.getOrDefault("name", "example" + examples.size());
232+
examples.put(key, example);
233+
}
234+
if (examples.size() > 0) {
235+
consumer.accept(examples);
169236
}
170237
}
171238

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public RouteIterator iterator(boolean ignoreArgs) {
2323
}
2424

2525
public String toYaml() {
26-
return toYaml(false);
26+
return toYaml(true);
2727
}
2828

2929
public String toYaml(boolean validate) {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package issues;
2+
3+
import io.jooby.openapi.OpenAPIResult;
4+
import io.jooby.openapi.OpenAPITest;
5+
import issues.i1591.App1591;
6+
import issues.i1591.App1591b;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
10+
public class Issue1591 {
11+
12+
@OpenAPITest(App1591.class)
13+
public void shouldParseExampleFromParameters(OpenAPIResult result) {
14+
assertEquals("openapi: 3.0.1\n"
15+
+ "info:\n"
16+
+ " title: 1591 API\n"
17+
+ " description: 1591 API description\n"
18+
+ " version: \"1.0\"\n"
19+
+ "paths:\n"
20+
+ " /1591:\n"
21+
+ " get:\n"
22+
+ " summary: Example.\n"
23+
+ " operationId: missingAttributes\n"
24+
+ " parameters:\n"
25+
+ " - name: arg1\n"
26+
+ " in: path\n"
27+
+ " description: Arg 1\n"
28+
+ " required: true\n"
29+
+ " schema:\n"
30+
+ " type: string\n"
31+
+ " example: ex1\n"
32+
+ " - name: arg2\n"
33+
+ " in: path\n"
34+
+ " required: true\n"
35+
+ " schema:\n"
36+
+ " type: string\n"
37+
+ " examples:\n"
38+
+ " example0:\n"
39+
+ " value: ex2\n"
40+
+ " example1:\n"
41+
+ " value: ex3\n"
42+
+ " responses:\n"
43+
+ " \"200\":\n"
44+
+ " description: Success\n"
45+
+ " content:\n"
46+
+ " application/json:\n"
47+
+ " schema:\n"
48+
+ " type: string\n", result.toYaml());
49+
}
50+
51+
@OpenAPITest(App1591b.class)
52+
public void shouldParseSecurityRequirement(OpenAPIResult result) {
53+
assertEquals("openapi: 3.0.1\n"
54+
+ "info:\n"
55+
+ " title: 1591b API\n"
56+
+ " description: 1591b API description\n"
57+
+ " version: \"1.0\"\n"
58+
+ "paths:\n"
59+
+ " /securityRequirements:\n"
60+
+ " get:\n"
61+
+ " operationId: securityRequirements\n"
62+
+ " responses:\n"
63+
+ " \"200\":\n"
64+
+ " description: Success\n"
65+
+ " content:\n"
66+
+ " application/json:\n"
67+
+ " schema:\n"
68+
+ " type: string\n"
69+
+ " security:\n"
70+
+ " - myOauth1:\n"
71+
+ " - 'write: read'\n"
72+
+ " /securityRequirement:\n"
73+
+ " get:\n"
74+
+ " operationId: securityRequirement\n"
75+
+ " responses:\n"
76+
+ " \"200\":\n"
77+
+ " description: Success\n"
78+
+ " content:\n"
79+
+ " application/json:\n"
80+
+ " schema:\n"
81+
+ " type: string\n"
82+
+ " security:\n"
83+
+ " - myOauth2:\n"
84+
+ " - 'write: read'\n"
85+
+ " /operationSecurityRequirement:\n"
86+
+ " get:\n"
87+
+ " operationId: operationSecurityRequirement\n"
88+
+ " responses:\n"
89+
+ " \"200\":\n"
90+
+ " description: Success\n"
91+
+ " content:\n"
92+
+ " application/json:\n"
93+
+ " schema:\n"
94+
+ " type: string\n"
95+
+ " security:\n"
96+
+ " - myOauth3:\n"
97+
+ " - 'write: read'\n", result.toYaml());
98+
}
99+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package issues.i1591;
2+
3+
import io.jooby.Context;
4+
import io.jooby.Jooby;
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import io.swagger.v3.oas.annotations.Parameter;
7+
import io.swagger.v3.oas.annotations.enums.ParameterIn;
8+
import io.swagger.v3.oas.annotations.media.ExampleObject;
9+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
10+
11+
public class App1591 extends Jooby {
12+
{
13+
get("/1591", this::parameterExample);
14+
}
15+
16+
@Operation(
17+
//this value does get respected
18+
summary = "Example.",
19+
security = @SecurityRequirement(name = "basicAuth"),
20+
//this value doesn't get respected
21+
parameters = {
22+
@Parameter(name = "arg1", description = "Arg 1", example = "ex1", in = ParameterIn.PATH),
23+
@Parameter(name = "arg2",
24+
examples = {@ExampleObject("ex2"), @ExampleObject("ex3")}, in = ParameterIn.PATH
25+
)
26+
}
27+
)
28+
private String parameterExample(Context context) {
29+
String arg1 = context.path("arg1").value();
30+
String arg2 = context.path("arg2").value();
31+
return null;
32+
}
33+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package issues.i1591;
2+
3+
import io.jooby.Context;
4+
import io.jooby.Jooby;
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
7+
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
8+
9+
public class App1591b extends Jooby {
10+
{
11+
get("/securityRequirements", this::securityRequirements);
12+
get("/securityRequirement", this::securityRequirement);
13+
get("/operationSecurityRequirement", this::operationSecurityRequirement);
14+
}
15+
16+
@SecurityRequirements(
17+
@SecurityRequirement(name = "myOauth1", scopes = "write: read")
18+
)
19+
private String securityRequirements(Context context) {
20+
return null;
21+
}
22+
23+
@SecurityRequirement(name = "myOauth2", scopes = "write: read")
24+
private String securityRequirement(Context context) {
25+
return null;
26+
}
27+
28+
@Operation(
29+
security = @SecurityRequirement(name = "myOauth3", scopes = "write: read")
30+
)
31+
private String operationSecurityRequirement(Context context) {
32+
return null;
33+
}
34+
}

0 commit comments

Comments
 (0)