Skip to content

Commit 54b0900

Browse files
committed
- Added a new JavaDoc tag parser
1 parent 2a57ad0 commit 54b0900

File tree

12 files changed

+646
-82
lines changed

12 files changed

+646
-82
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public static void set(OperationExt operation, MethodDoc doc, List<String> param
4848
if (!doc.getExtensions().isEmpty()) {
4949
operation.setExtensions(doc.getExtensions());
5050
}
51+
doc.getSecurityRequeriments().forEach(operation::addSecurityItem);
5152
doc.getTags().forEach(operation::addTag);
5253
// Parameters
5354
for (var parameterName : parameterNames) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.jooby.Router;
1515
import io.swagger.v3.oas.models.*;
1616
import io.swagger.v3.oas.models.parameters.Parameter;
17+
import io.swagger.v3.oas.models.security.SecurityScheme;
1718

1819
public class OpenAPIExt extends OpenAPI {
1920
@JsonIgnore private List<OperationExt> operations = Collections.emptyList();
@@ -36,6 +37,15 @@ public void setSource(String classname) {
3637
this.source = classname;
3738
}
3839

40+
public void addSecuritySchemes(SecurityScheme scheme) {
41+
var components = getComponents();
42+
if (components == null) {
43+
components = new Components();
44+
setComponents(components);
45+
}
46+
components.addSecuritySchemes(scheme.getName(), scheme);
47+
}
48+
3949
@Override
4050
public void setPaths(Paths paths) {
4151
var existingPaths = this.getPaths();

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
import io.swagger.v3.oas.annotations.servers.Servers;
5757
import io.swagger.v3.oas.annotations.tags.Tag;
5858
import io.swagger.v3.oas.annotations.tags.Tags;
59-
import io.swagger.v3.oas.models.Components;
6059
import io.swagger.v3.oas.models.ExternalDocumentation;
6160
import io.swagger.v3.oas.models.examples.Example;
6261
import io.swagger.v3.oas.models.headers.Header;
@@ -117,11 +116,6 @@ private static void rootOpenApiAnnotations(OpenAPIExt openapi, ClassNode node) {
117116

118117
private static void securitySchemas(OpenAPIExt openapi, List<Map<String, Object>> schemas) {
119118
for (Map<String, Object> annotation : schemas) {
120-
Components components = openapi.getComponents();
121-
if (components == null) {
122-
components = new Components();
123-
openapi.setComponents(components);
124-
}
125119
io.swagger.v3.oas.models.security.SecurityScheme scheme =
126120
new io.swagger.v3.oas.models.security.SecurityScheme();
127121

@@ -141,7 +135,7 @@ private static void securitySchemas(OpenAPIExt openapi, List<Map<String, Object>
141135
annotationList(annotation, "extensions", values -> extensions(values, scheme::addExtension));
142136
annotationValue(annotation, "flows", flows -> flows(flows, scheme::flows));
143137

144-
components.addSecuritySchemes(scheme.getName(), scheme);
138+
openapi.addSecuritySchemes(scheme);
145139
}
146140
}
147141

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
2121
import io.swagger.v3.oas.models.info.Contact;
2222
import io.swagger.v3.oas.models.info.License;
23+
import io.swagger.v3.oas.models.security.SecurityScheme;
2324
import io.swagger.v3.oas.models.servers.Server;
2425

2526
public class ClassDoc extends JavaDocNode {
@@ -29,6 +30,7 @@ public class ClassDoc extends JavaDocNode {
2930
private final List<Server> servers;
3031
private final List<Contact> contact;
3132
private final List<License> license;
33+
private final List<SecurityScheme> securitySchemes;
3234

3335
public ClassDoc(JavaDocParser ctx, DetailAST node, DetailAST javaDoc) {
3436
super(ctx, node, javaDoc);
@@ -40,6 +42,11 @@ public ClassDoc(JavaDocParser ctx, DetailAST node, DetailAST javaDoc) {
4042
this.servers = JavaDocTag.servers(this.javadoc);
4143
this.contact = JavaDocTag.contacts(this.javadoc);
4244
this.license = JavaDocTag.license(this.javadoc);
45+
this.securitySchemes = JavaDocTag.securitySchemes(this.javadoc);
46+
}
47+
48+
public List<SecurityScheme> getSecuritySchemes() {
49+
return securitySchemes;
4350
}
4451

4552
public List<Server> getServers() {

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

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
import static io.jooby.internal.openapi.javadoc.JavaDocNode.getText;
99
import static io.jooby.internal.openapi.javadoc.JavaDocStream.*;
1010
import static io.jooby.internal.openapi.javadoc.JavaDocStream.children;
11+
import static java.util.Optional.ofNullable;
1112

1213
import java.util.*;
14+
import java.util.function.Consumer;
1315
import java.util.function.Function;
1416
import java.util.function.Predicate;
17+
import java.util.stream.Stream;
1518

1619
import com.puppycrawl.tools.checkstyle.api.DetailNode;
1720
import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
@@ -21,6 +24,7 @@
2124
import io.jooby.internal.openapi.ResponseExt;
2225
import io.swagger.v3.oas.models.info.Contact;
2326
import io.swagger.v3.oas.models.info.License;
27+
import io.swagger.v3.oas.models.security.*;
2428
import io.swagger.v3.oas.models.servers.Server;
2529
import io.swagger.v3.oas.models.tags.Tag;
2630

@@ -31,6 +35,13 @@ public class JavaDocTag {
3135
CUSTOM_TAG.and(it -> it.getText().startsWith("@tag.") || it.getText().equals("@tag"));
3236
private static final Predicate<DetailNode> SERVER =
3337
CUSTOM_TAG.and(it -> it.getText().startsWith("@server."));
38+
private static final Predicate<DetailNode> SECURITY =
39+
CUSTOM_TAG.and(
40+
it -> it.getText().equals("@security") || it.getText().equals("@securityRequirement"));
41+
private static final Predicate<DetailNode> SECURITY_REQUIREMENT =
42+
CUSTOM_TAG.and(it -> it.getText().equals("@securityRequirement"));
43+
private static final Predicate<DetailNode> SECURITY_SCHEME =
44+
CUSTOM_TAG.and(it -> it.getText().startsWith("@securityScheme."));
3445
private static final Predicate<DetailNode> CONTACT =
3546
CUSTOM_TAG.and(it -> it.getText().startsWith("@contact."));
3647
private static final Predicate<DetailNode> LICENSE =
@@ -42,6 +53,109 @@ public class JavaDocTag {
4253
private static final Predicate<DetailNode> THROWS =
4354
it -> tree(it).anyMatch(javadocToken(JavadocTokenTypes.THROWS_LITERAL));
4455

56+
public static List<SecurityRequirement> securityRequirement(DetailNode node) {
57+
return parse(node, SECURITY, null).stream()
58+
.map(
59+
hash ->
60+
hash.containsKey("security")
61+
? hash.get("security")
62+
: hash.get("securityRequirement"))
63+
.map(Object::toString)
64+
.map(String::trim)
65+
.map(
66+
value -> {
67+
// look first space
68+
var indexOf = value.indexOf(' ');
69+
String key;
70+
String scopes;
71+
if (indexOf > 0) {
72+
key = value.substring(0, indexOf).trim();
73+
scopes = value.substring(indexOf + 1).trim();
74+
} else {
75+
key = value;
76+
scopes = "";
77+
}
78+
if (scopes.startsWith("[") && scopes.endsWith("]")) {
79+
scopes = scopes.substring(1, scopes.length() - 1);
80+
}
81+
var scopeList = Stream.of(scopes.split(",")).map(String::trim).toList();
82+
var security = new SecurityRequirement();
83+
security.addList(key, scopeList);
84+
return security;
85+
})
86+
.toList();
87+
}
88+
89+
public static List<SecurityScheme> securitySchemes(DetailNode node) {
90+
return parse(node, SECURITY_SCHEME, "securityScheme").stream()
91+
.map(
92+
hash -> {
93+
var item = new SecurityScheme();
94+
item.setDescription((String) hash.get("description"));
95+
item.setName((String) hash.get("name"));
96+
ofNullable((String) hash.get("in"))
97+
.map(String::toUpperCase)
98+
.map(SecurityScheme.In::valueOf)
99+
.ifPresent(item::setIn);
100+
ofNullable((String) hash.get("type"))
101+
.map(String::toUpperCase)
102+
.map(SecurityScheme.Type::valueOf)
103+
.ifPresent(item::setType);
104+
item.setBearerFormat((String) hash.get("bearerFormat"));
105+
item.setOpenIdConnectUrl((String) hash.get("openIdConnectUrl"));
106+
item.setScheme((String) hash.get("scheme"));
107+
var objectFlows = hash.get("flows");
108+
if (objectFlows instanceof Map<?, ?> hashFlows) {
109+
OAuthFlows flows = new OAuthFlows();
110+
toOauthFlow("implicit", hashFlows, flows::setImplicit);
111+
toOauthFlow("password", hashFlows, flows::setPassword);
112+
toOauthFlow("authorizationCode", hashFlows, flows::setAuthorizationCode);
113+
toOauthFlow("clientCredentials", hashFlows, flows::setClientCredentials);
114+
item.setFlows(flows);
115+
}
116+
return item;
117+
})
118+
.toList();
119+
}
120+
121+
private static void toOauthFlow(String path, Map<?, ?> flows, Consumer<OAuthFlow> consumer) {
122+
var flowHash = flows.get(path);
123+
if (flowHash instanceof Map hash) {
124+
var oauthFlow = new OAuthFlow();
125+
oauthFlow.setAuthorizationUrl((String) hash.get("authorizationUrl"));
126+
oauthFlow.setTokenUrl((String) hash.get("tokenUrl"));
127+
oauthFlow.setRefreshUrl((String) hash.get("refreshUrl"));
128+
var scopesObject = hash.get("scopes");
129+
List<String> scopeNames;
130+
List<String> scopeDescriptions;
131+
if (scopesObject instanceof Map<?, ?> scopesHash) {
132+
scopeNames = ensureList(scopesHash.get("name"));
133+
scopeDescriptions = ensureList(scopesHash.get("description"));
134+
} else {
135+
scopeNames = ensureList(scopesObject);
136+
scopeDescriptions = List.of();
137+
}
138+
if (!scopeNames.isEmpty()) {
139+
Scopes scopes = new Scopes();
140+
for (int i = 0; i < scopeNames.size(); i++) {
141+
var description = i < scopeDescriptions.size() ? scopeDescriptions.get(i) : "";
142+
scopes.addString(scopeNames.get(i), description);
143+
}
144+
oauthFlow.setScopes(scopes);
145+
}
146+
consumer.accept(oauthFlow);
147+
}
148+
}
149+
150+
@SuppressWarnings({"unchecked", "rawtypes"})
151+
private static List<String> ensureList(Object value) {
152+
if (value == null) return List.of();
153+
if (value instanceof List list) {
154+
return list.stream().map(Objects::toString).toList();
155+
}
156+
return List.of(value.toString());
157+
}
158+
45159
public static List<Server> servers(DetailNode node) {
46160
return openApiComponent(
47161
node,
@@ -111,6 +225,21 @@ private static <T> List<T> openApiComponent(
111225
return result;
112226
}
113227

228+
private static List<Map<String, Object>> parse(
229+
DetailNode node, Predicate<DetailNode> filter, String path) {
230+
var values = new ArrayList<String>();
231+
javaDocTag(
232+
node,
233+
filter,
234+
(tag, value) -> {
235+
values.add(tag.getText().substring(1));
236+
values.add(value);
237+
});
238+
return ListToMapParser.parse(values).stream()
239+
.map(hash -> path == null ? hash : (Map<String, Object>) hash.get(path))
240+
.toList();
241+
}
242+
114243
public static Map<StatusCode, ResponseExt> throwList(DetailNode node) {
115244
var result = new LinkedHashMap<StatusCode, ResponseExt>();
116245
javaDocTag(

0 commit comments

Comments
 (0)