Skip to content

Commit e84043f

Browse files
authored
Add support extra Schemas (#1610)
Move schema definition logic to different class Fixed #1598
1 parent aa9fe06 commit e84043f

33 files changed

+3747
-2972
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2017-2023 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.openapi.annotation;
17+
18+
import io.micronaut.context.annotation.AliasFor;
19+
20+
import java.lang.annotation.Documented;
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Repeatable;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.Target;
25+
26+
import static java.lang.annotation.RetentionPolicy.SOURCE;
27+
28+
/**
29+
* With this annotation, you can specify one or more groups that this endpoint will be included in,
30+
* as well as specify groups from which this endpoint should be excluded. Also, you can set
31+
* specific endpoint extensions for each group
32+
*
33+
* @since 6.12.0
34+
*/
35+
@Repeatable(OpenAPIExtraSchemas.class)
36+
@Retention(SOURCE)
37+
@Documented
38+
@Target({ElementType.PACKAGE, ElementType.TYPE})
39+
public @interface OpenAPIExtraSchema {
40+
41+
/**
42+
* @return Schema classes to include in generated Open API.
43+
*/
44+
Class<?>[] value() default {};
45+
46+
/**
47+
* @return Schema classes to include in generated Open API.
48+
*/
49+
@AliasFor(member = "value")
50+
Class<?>[] classes() default {};
51+
52+
/**
53+
* @return Schema classes to include in generated Open API.
54+
*/
55+
@AliasFor(member = "value")
56+
String[] classNames() default {};
57+
58+
/**
59+
* @return Schema classes annotated by OpenAPIExtraSchema and should be excluded.
60+
*/
61+
Class<?>[] excludeClasses() default {};
62+
63+
/**
64+
* @return Schema classes annotated by OpenAPIExtraSchema and should be excluded.
65+
*/
66+
String[] excludeClassNames() default {};
67+
68+
/**
69+
* @return packages with extra schemas should be included.
70+
* NOTE: Currently you can't use wildcard to include subpackages. Need to set every package in list
71+
*/
72+
String[] packages() default {};
73+
74+
/**
75+
* @return packages with extra schemas should be excluded.
76+
*/
77+
String[] excludePackages() default {};
78+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2017-2023 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.openapi.annotation;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.Target;
22+
23+
import static java.lang.annotation.RetentionPolicy.SOURCE;
24+
25+
/**
26+
* Allows {@link OpenAPIExtraSchema} to be repeatable.
27+
*
28+
* @since 6.12.0
29+
*/
30+
@Documented
31+
@Retention(SOURCE)
32+
@Target({ElementType.PACKAGE, ElementType.TYPE})
33+
public @interface OpenAPIExtraSchemas {
34+
35+
/**
36+
* @return A group of {@link OpenAPIExtraSchema}
37+
*/
38+
OpenAPIExtraSchema[] value() default {};
39+
}

openapi/src/main/java/io/micronaut/openapi/view/AbstractViewConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ static <T extends AbstractViewConfig> T fromProperties(T cfg, Map<String, Object
184184
if (cfg.withUrls) {
185185

186186
String primaryName = null;
187-
List<OpenApiUrl> urls = new ArrayList<>();
187+
var urls = new ArrayList<OpenApiUrl>();
188188
for (OpenApiInfo openApiInfo : cfg.openApiInfos.values()) {
189189
String groupName = openApiInfo.getGroupName();
190190
String version = openApiInfo.getVersion();

openapi/src/main/java/io/micronaut/openapi/view/OpenApiViewConfig.java

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
*/
1616
package io.micronaut.openapi.view;
1717

18+
import io.micronaut.core.annotation.NonNull;
19+
import io.micronaut.core.annotation.Nullable;
20+
import io.micronaut.core.io.scan.DefaultClassPathResourceLoader;
21+
import io.micronaut.core.util.CollectionUtils;
22+
import io.micronaut.core.util.StringUtils;
23+
import io.micronaut.inject.visitor.VisitorContext;
24+
import io.micronaut.openapi.visitor.ContextUtils;
25+
import io.micronaut.openapi.visitor.Pair;
26+
import io.micronaut.openapi.visitor.group.OpenApiInfo;
27+
1828
import java.io.BufferedReader;
1929
import java.io.BufferedWriter;
2030
import java.io.IOException;
@@ -33,22 +43,12 @@
3343
import java.util.Optional;
3444
import java.util.Properties;
3545

36-
import io.micronaut.core.annotation.NonNull;
37-
import io.micronaut.core.annotation.Nullable;
38-
import io.micronaut.core.io.scan.ClassPathResourceLoader;
39-
import io.micronaut.core.io.scan.DefaultClassPathResourceLoader;
40-
import io.micronaut.core.util.CollectionUtils;
41-
import io.micronaut.core.util.StringUtils;
42-
import io.micronaut.inject.visitor.VisitorContext;
43-
import io.micronaut.openapi.visitor.ContextUtils;
44-
import io.micronaut.openapi.visitor.Pair;
45-
import io.micronaut.openapi.visitor.group.OpenApiInfo;
46-
4746
import static io.micronaut.openapi.visitor.ConfigUtils.getConfigProperty;
4847
import static io.micronaut.openapi.visitor.ConfigUtils.getProjectPath;
4948
import static io.micronaut.openapi.visitor.ContextUtils.addGeneratedResource;
5049
import static io.micronaut.openapi.visitor.ContextUtils.info;
5150
import static io.micronaut.openapi.visitor.ContextUtils.warn;
51+
import static io.micronaut.openapi.visitor.FileUtils.readFile;
5252
import static io.micronaut.openapi.visitor.FileUtils.resolve;
5353
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_SERVER_CONTEXT_PATH;
5454
import static io.micronaut.openapi.visitor.StringUtil.COMMA;
@@ -156,10 +156,10 @@ private static Map<String, String> parse(String specification) {
156156
* @return An OpenApiViewConfig.
157157
*/
158158
public static OpenApiViewConfig fromSpecification(String specification, Map<Pair<String, String>, OpenApiInfo> openApiInfos, Properties openApiProperties, VisitorContext context) {
159-
Map<String, String> openApiMap = new HashMap<>(openApiProperties.size());
159+
var openApiMap = new HashMap<String, String>(openApiProperties.size());
160160
openApiProperties.forEach((key, value) -> openApiMap.put((String) key, (String) value));
161161
openApiMap.putAll(parse(specification));
162-
OpenApiViewConfig cfg = new OpenApiViewConfig(openApiInfos);
162+
var cfg = new OpenApiViewConfig(openApiInfos);
163163
RapiPDFConfig rapiPDFConfig = RapiPDFConfig.fromProperties(openApiMap, openApiInfos, context);
164164
if ("true".equals(openApiMap.getOrDefault("redoc.enabled", Boolean.FALSE.toString()))) {
165165
cfg.redocConfig = RedocConfig.fromProperties(openApiMap, openApiInfos, context);
@@ -306,9 +306,8 @@ private void copyResources(AbstractViewConfig cfg, Path outputDir, String templa
306306
}
307307

308308
private String readTemplateFromClasspath(String templateName) throws IOException {
309-
StringBuilder buf = new StringBuilder(1024);
310309
ClassLoader classLoader = getClass().getClassLoader();
311-
try (InputStream in = classLoader.getResourceAsStream(templateName);
310+
try (var in = classLoader.getResourceAsStream(templateName);
312311
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
313312
) {
314313
return readFile(reader);
@@ -336,13 +335,13 @@ private String readTemplateFromCustomPath(String customPathStr, @Nullable Visito
336335
} else if (customPathStr.startsWith("file:")) {
337336
customPathStr = customPathStr.substring(5);
338337
} else if (customPathStr.startsWith("classpath:")) {
339-
ClassPathResourceLoader resourceLoader = new DefaultClassPathResourceLoader(getClass().getClassLoader());
338+
var resourceLoader = new DefaultClassPathResourceLoader(getClass().getClassLoader());
340339
Optional<InputStream> inOpt = resourceLoader.getResourceAsStream(customPathStr);
341340
if (inOpt.isEmpty()) {
342341
throw new IOException("Fail to load " + customPathStr);
343342
}
344343
try (InputStream in = inOpt.get();
345-
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
344+
var reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
346345
) {
347346
return readFile(reader);
348347
} catch (IOException e) {
@@ -361,15 +360,6 @@ private String readTemplateFromCustomPath(String customPathStr, @Nullable Visito
361360
}
362361
}
363362

364-
private String readFile(BufferedReader reader) throws IOException {
365-
StringBuilder buf = new StringBuilder(1024);
366-
String line;
367-
while ((line = reader.readLine()) != null) {
368-
buf.append(line).append('\n');
369-
}
370-
return buf.toString();
371-
}
372-
373363
private void render(AbstractViewConfig cfg, Path outputDir, String templateName, @Nullable VisitorContext context) throws IOException {
374364

375365
String template;

openapi/src/main/java/io/micronaut/openapi/view/RapiPDFConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public boolean isEnabled() {
116116
* @return A RapipdfConfig.
117117
*/
118118
static RapiPDFConfig fromProperties(Map<String, String> properties, Map<Pair<String, String>, OpenApiInfo> openApiInfos, VisitorContext context) {
119-
RapiPDFConfig cfg = new RapiPDFConfig(openApiInfos);
119+
var cfg = new RapiPDFConfig(openApiInfos);
120120
cfg.enabled = "true".equals(properties.getOrDefault("rapipdf.enabled", Boolean.FALSE.toString()));
121121
return AbstractViewConfig.fromProperties(cfg, DEFAULT_OPTIONS, properties, null, context);
122122
}

openapi/src/main/java/io/micronaut/openapi/view/RapidocConfig.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ enum NavTagClick {
168168
private static final Map<String, NavTagClick> BY_CODE;
169169

170170
static {
171-
Map<String, NavTagClick> byCode = new HashMap<>(NavTagClick.values().length);
171+
var byCode = new HashMap<String, NavTagClick>(NavTagClick.values().length);
172172
for (NavTagClick navTagClick : values()) {
173173
byCode.put(navTagClick.code, navTagClick);
174174
}
@@ -207,7 +207,7 @@ enum FetchCredentials {
207207
private static final Map<String, FetchCredentials> BY_CODE;
208208

209209
static {
210-
Map<String, FetchCredentials> byCode = new HashMap<>(FetchCredentials.values().length);
210+
var byCode = new HashMap<String, FetchCredentials>(FetchCredentials.values().length);
211211
for (FetchCredentials navTagClick : values()) {
212212
byCode.put(navTagClick.code, navTagClick);
213213
}
@@ -247,7 +247,7 @@ enum ShowMethodInNavBar {
247247
private static final Map<String, ShowMethodInNavBar> BY_CODE;
248248

249249
static {
250-
Map<String, ShowMethodInNavBar> byCode = new HashMap<>(ShowMethodInNavBar.values().length);
250+
var byCode = new HashMap<String, ShowMethodInNavBar>(ShowMethodInNavBar.values().length);
251251
for (ShowMethodInNavBar showMethodInNavBar : values()) {
252252
byCode.put(showMethodInNavBar.code, showMethodInNavBar);
253253
}

openapi/src/main/java/io/micronaut/openapi/view/RedocConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ enum SideNavStyle {
104104
private static final Map<String, SideNavStyle> BY_CODE;
105105

106106
static {
107-
Map<String, SideNavStyle> byCode = new HashMap<>(SideNavStyle.values().length);
107+
var byCode = new HashMap<String, SideNavStyle>(SideNavStyle.values().length);
108108
for (SideNavStyle navTagClick : values()) {
109109
byCode.put(navTagClick.code, navTagClick);
110110
}

openapi/src/main/java/io/micronaut/openapi/view/SwaggerUIConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ enum SyntaxHighlightTheme {
133133
private static final Map<String, SyntaxHighlightTheme> BY_CODE;
134134

135135
static {
136-
Map<String, SyntaxHighlightTheme> byCode = new HashMap<>(SyntaxHighlightTheme.values().length);
136+
var byCode = new HashMap<String, SyntaxHighlightTheme>(SyntaxHighlightTheme.values().length);
137137
for (SyntaxHighlightTheme navTagClick : values()) {
138138
byCode.put(navTagClick.code, navTagClick);
139139
}
@@ -242,7 +242,7 @@ static boolean hasOauth2Option(Map<String, Object> options) {
242242
* @return A SwaggerUIConfig.
243243
*/
244244
static SwaggerUIConfig fromProperties(Map<String, String> properties, Map<Pair<String, String>, OpenApiInfo> openApiInfos, VisitorContext context) {
245-
SwaggerUIConfig cfg = new SwaggerUIConfig(openApiInfos);
245+
var cfg = new SwaggerUIConfig(openApiInfos);
246246
cfg.theme = Theme.valueOf(properties.getOrDefault(PREFIX_SWAGGER_UI + ".theme", cfg.theme.name()).toUpperCase(Locale.US));
247247

248248
String copyTheme = properties.get(cfg.prefix + "copy-theme");

openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import io.micronaut.core.util.StringUtils;
3131
import io.micronaut.core.version.annotation.Version;
3232
import io.micronaut.http.HttpMethod;
33-
import io.micronaut.http.HttpResponse;
3433
import io.micronaut.http.HttpStatus;
3534
import io.micronaut.http.MediaType;
3635
import io.micronaut.http.annotation.Body;
@@ -133,20 +132,19 @@
133132
import static io.micronaut.openapi.visitor.ElementUtils.isIgnoredParameter;
134133
import static io.micronaut.openapi.visitor.ElementUtils.isNotNullable;
135134
import static io.micronaut.openapi.visitor.ElementUtils.isNullable;
135+
import static io.micronaut.openapi.visitor.ElementUtils.isResponseType;
136+
import static io.micronaut.openapi.visitor.ElementUtils.isSingleResponseType;
136137
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ADD_ALWAYS;
137-
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_CALLBACK_URL_EXPRESSION;
138-
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXCLUDE;
139-
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_OP_ID_SUFFIX;
140-
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_PARAMETERS;
141-
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REF_DOLLAR;
138+
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ALLOW_EMPTY_VALUE;
142139
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ALLOW_RESERVED;
140+
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_CALLBACK_URL_EXPRESSION;
143141
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_CONTENT;
144142
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DEFAULT;
145143
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DEPRECATED;
146144
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DESCRIPTION;
147-
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ALLOW_EMPTY_VALUE;
148145
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXAMPLE;
149146
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXAMPLES;
147+
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXCLUDE;
150148
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXPLODE;
151149
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXTENSIONS;
152150
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_HIDDEN;
@@ -156,7 +154,10 @@
156154
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_METHOD;
157155
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_NAME;
158156
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_OPERATION;
157+
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_OP_ID_SUFFIX;
158+
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_PARAMETERS;
159159
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REF;
160+
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REF_DOLLAR;
160161
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REQUIRED;
161162
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_RESPONSE_CODE;
162163
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_SCHEMA;
@@ -172,6 +173,12 @@
172173
import static io.micronaut.openapi.visitor.StringUtil.CLOSE_BRACE;
173174
import static io.micronaut.openapi.visitor.StringUtil.OPEN_BRACE;
174175
import static io.micronaut.openapi.visitor.StringUtil.THREE_DOTS;
176+
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.bindSchemaAnnotationValue;
177+
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.bindSchemaForElement;
178+
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.processSchemaProperty;
179+
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.resolveSchema;
180+
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.toValue;
181+
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.toValueMap;
175182
import static io.micronaut.openapi.visitor.Utils.DEFAULT_MEDIA_TYPES;
176183
import static io.micronaut.openapi.visitor.Utils.getMediaType;
177184
import static io.micronaut.openapi.visitor.Utils.resolveWebhooks;
@@ -1656,20 +1663,6 @@ private void processExplode(AnnotationValue<io.swagger.v3.oas.annotations.Parame
16561663
}
16571664
}
16581665

1659-
private boolean isSingleResponseType(ClassElement returnType) {
1660-
return (returnType.isAssignable("io.reactivex.Single")
1661-
|| returnType.isAssignable("io.reactivex.rxjava3.core.Single")
1662-
|| returnType.isAssignable("org.reactivestreams.Publisher"))
1663-
&& returnType.getFirstTypeArgument().isPresent()
1664-
&& isResponseType(returnType.getFirstTypeArgument().orElse(null));
1665-
}
1666-
1667-
private boolean isResponseType(ClassElement returnType) {
1668-
return returnType != null
1669-
&& (returnType.isAssignable(HttpResponse.class)
1670-
|| returnType.isAssignable("org.springframework.http.HttpEntity"));
1671-
}
1672-
16731666
private void readApiResponses(MethodElement element, VisitorContext context, Operation swaggerOperation, @Nullable ClassElement jsonViewClass) {
16741667
var methodResponseAnns = element.getAnnotationValuesByType(io.swagger.v3.oas.annotations.responses.ApiResponse.class);
16751668
processResponses(swaggerOperation, methodResponseAnns, element, context, jsonViewClass);

0 commit comments

Comments
 (0)