Skip to content

Commit e055af8

Browse files
author
springdoc
committed
Added supports for ui with Multiple OpenAPI definitions in one Spring Boot project. Fixes #213
1 parent 06f8537 commit e055af8

File tree

10 files changed

+153
-78
lines changed

10 files changed

+153
-78
lines changed

springdoc-openapi-common/src/main/java/org/springdoc/core/Constants.java

+3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ public final class Constants {
77
public static final String DEFAULT_API_DOCS_URL = "/v3/api-docs";
88
public static final String DEFAULT_SERVER_DESCRIPTION = "Generated server url";
99
public static final String API_DOCS_URL = "${springdoc.api-docs.path:#{T(org.springdoc.core.Constants).DEFAULT_API_DOCS_URL}}";
10+
public static final String SWAGGGER_CONFIG_FILE = "swagger-config";
11+
public static final String SWAGGER_CONFIG_URL =API_DOCS_URL +DEFAULT_PATH_SEPARATOR+ SWAGGGER_CONFIG_FILE;
1012
public static final String DEFAULT_API_DOCS_URL_YAML = API_DOCS_URL + ".yaml";
1113
public static final String SPRINGDOC_ENABLED = "springdoc.api-docs.enabled";
1214
public static final String SPRINGDOC_GROUPS_ENABLED = "springdoc.api-docs.groups.enabled";
15+
public static final String SPRINGDOC_GROUPS_ENABLED_VALUE = "${" + SPRINGDOC_GROUPS_ENABLED + ":false}";
1316
public static final String SPRINGDOC_SWAGGER_UI_ENABLED = "springdoc.swagger-ui.enabled";
1417
public static final String SPRINGDOC_SHOW_ACTUATOR = "springdoc.show.actuator";
1518
public static final String SPRINGDOC_SHOW_ACTUATOR_VALUE = "${" + SPRINGDOC_SHOW_ACTUATOR + ":false}";

springdoc-openapi-common/src/main/java/org/springdoc/core/GroupedOpenApi.java

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ private GroupedOpenApi(Builder builder) {
2020
this.pathsToMatch = builder.pathsToMatch;
2121
this.packagesToScan = builder.packagesToScan;
2222
this.openApiCustomisers = Objects.requireNonNull(builder.openApiCustomisers);
23+
SwaggerUiConfigProperties.addGroup(this.group);
2324
if (CollectionUtils.isEmpty(this.pathsToMatch) && CollectionUtils.isEmpty(this.packagesToScan))
2425
throw new IllegalStateException("Packages to scan or paths to filter can not be both null for the group:"+ this.group);
2526
}

springdoc-openapi-common/src/main/java/org/springdoc/core/SwaggerUiConfigProperties.java

+71-5
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import org.springframework.context.annotation.Configuration;
77
import org.springframework.util.CollectionUtils;
88

9+
import java.util.ArrayList;
910
import java.util.List;
1011
import java.util.Map;
1112
import java.util.TreeMap;
13+
import java.util.stream.Collectors;
14+
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
1215

1316
/**
1417
* Please refer to the swagger
@@ -110,9 +113,17 @@ public class SwaggerUiConfigProperties {
110113
*/
111114
private String oauth2RedirectUrl;
112115

116+
private String url;
113117

114-
public Map<String, String> getConfigParameters() {
115-
final Map<String, String> params = new TreeMap<>();
118+
private static List<SwaggerUrl> swaggerUrls = new ArrayList<>();
119+
120+
public static void addGroup(String group) {
121+
SwaggerUrl swaggerUrl = new SwaggerUrl(group);
122+
swaggerUrls.add(swaggerUrl);
123+
}
124+
125+
public Map<String, Object> getConfigParameters() {
126+
final Map<String, Object> params = new TreeMap<>();
116127
put("layout", layout, params);
117128
put(CONFIG_URL_PROPERTY, configUrl, params);
118129
put(VALIDATOR_URL_PROPERTY, validatorUrl, params);
@@ -132,22 +143,31 @@ public Map<String, String> getConfigParameters() {
132143
if (!CollectionUtils.isEmpty(supportedSubmitMethods))
133144
put("supportedSubmitMethods", supportedSubmitMethods.toString(), params);
134145
put("oauth2RedirectUrl", oauth2RedirectUrl, params);
146+
put("url", url, params);
147+
put("urls", swaggerUrls, params);
135148
return params;
136149
}
137150

138-
protected void put(final String name, final Integer value, final Map<String, String> params) {
151+
protected void put(String urls, List<SwaggerUrl> swaggerUrls, Map<String, Object> params) {
152+
swaggerUrls = swaggerUrls.stream().filter(elt -> StringUtils.isNotEmpty(elt.getUrl())).collect(Collectors.toList());
153+
if (!CollectionUtils.isEmpty(swaggerUrls)) {
154+
params.put(urls, swaggerUrls);
155+
}
156+
}
157+
158+
protected void put(final String name, final Integer value, final Map<String, Object> params) {
139159
if (value != null) {
140160
params.put(name, value.toString());
141161
}
142162
}
143163

144-
protected void put(final String name, final Boolean value, final Map<String, String> params) {
164+
protected void put(final String name, final Boolean value, final Map<String, Object> params) {
145165
if (value != null) {
146166
params.put(name, value.toString());
147167
}
148168
}
149169

150-
protected void put(final String name, final String value, final Map<String, String> params) {
170+
protected void put(final String name, final String value, final Map<String, Object> params) {
151171
if (!StringUtils.isEmpty(value)) {
152172
params.put(name, value);
153173
}
@@ -304,4 +324,50 @@ public String getOauth2RedirectUrl() {
304324
public void setOauth2RedirectUrl(String oauth2RedirectUrl) {
305325
this.oauth2RedirectUrl = oauth2RedirectUrl;
306326
}
327+
328+
public String getUrl() {
329+
return url;
330+
}
331+
332+
public void setUrl(String url) {
333+
this.url = url;
334+
}
335+
336+
public List<SwaggerUrl> getSwaggerUrls() {
337+
return swaggerUrls;
338+
}
339+
340+
public void setSwaggerUrls(List<SwaggerUrl> swaggerUrls) {
341+
this.swaggerUrls = swaggerUrls;
342+
}
343+
344+
public static void addUrl(String url) {
345+
swaggerUrls.forEach(elt -> elt.setUrl(url + DEFAULT_PATH_SEPARATOR + elt.getName()));
346+
}
347+
348+
349+
static class SwaggerUrl {
350+
private String url;
351+
private String name;
352+
353+
public SwaggerUrl(String group) {
354+
this.name = group;
355+
}
356+
357+
public String getUrl() {
358+
return url;
359+
}
360+
361+
public void setUrl(String url) {
362+
this.url = url;
363+
}
364+
365+
public String getName() {
366+
return name;
367+
}
368+
369+
public void setName(String name) {
370+
this.name = name;
371+
}
372+
}
307373
}

springdoc-openapi-common/src/main/java/org/springdoc/core/SwaggerUiQueryParamsAppender.java

-49
This file was deleted.

springdoc-openapi-ui/src/main/java/org/springdoc/ui/SwaggerWelcome.java

+33-12
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@
55
import org.springdoc.core.SwaggerUiConfigProperties;
66
import org.springframework.beans.factory.annotation.Autowired;
77
import org.springframework.beans.factory.annotation.Value;
8+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
9+
import org.springframework.http.MediaType;
810
import org.springframework.stereotype.Controller;
911
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.ResponseBody;
1013
import org.springframework.web.util.UriComponentsBuilder;
1114

1215
import javax.servlet.http.HttpServletRequest;
1316
import java.util.Map;
1417

1518
import static org.springdoc.core.Constants.*;
16-
import static org.springdoc.core.SwaggerUiQueryParamsAppender.appendSwaggerUiQueryParams;
1719
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
1820
import static org.springframework.web.servlet.view.UrlBasedViewResolver.REDIRECT_URL_PREFIX;
1921

2022
@Controller
23+
@ConditionalOnProperty(name = SPRINGDOC_SWAGGER_UI_ENABLED, matchIfMissing = true)
2124
class SwaggerWelcome {
2225

2326
@Value(API_DOCS_URL)
@@ -32,35 +35,53 @@ class SwaggerWelcome {
3235
@Autowired
3336
private SwaggerUiConfigProperties swaggerUiConfig;
3437

38+
@Value(SPRINGDOC_GROUPS_ENABLED_VALUE)
39+
private boolean groupsEnabled;
40+
3541
@Operation(hidden = true)
3642
@GetMapping(SWAGGER_UI_PATH)
3743
public String redirectToUi(HttpServletRequest request) {
38-
String contextPath = request.getContextPath();
39-
if (StringUtils.isNotBlank(mvcServletPath))
40-
contextPath += mvcServletPath;
4144
String uiRootPath = "";
42-
if (swaggerPath.contains("/")) {
45+
if (swaggerPath.contains("/"))
4346
uiRootPath = swaggerPath.substring(0, swaggerPath.lastIndexOf('/'));
44-
}
4547
StringBuilder sbUrl = new StringBuilder();
4648
sbUrl.append(REDIRECT_URL_PREFIX);
4749
if (StringUtils.isNotBlank(mvcServletPath))
4850
sbUrl.append(mvcServletPath);
4951
sbUrl.append(uiRootPath);
5052
sbUrl.append(SWAGGER_UI_URL);
51-
53+
buildConfigUrl(request);
5254
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(sbUrl.toString());
53-
Map<String, String> swaggerUiParams = swaggerUiConfig.getConfigParameters();
54-
String url = buildUrl(contextPath, apiDocsUrl);
55+
return uriBuilder.queryParam(SwaggerUiConfigProperties.CONFIG_URL_PROPERTY, swaggerUiConfig.getConfigUrl()).build().encode().toString();
56+
}
5557

56-
return appendSwaggerUiQueryParams(uriBuilder, swaggerUiParams, url)
57-
.build().encode().toString();
58+
@Operation(hidden = true)
59+
@GetMapping(value = SWAGGER_CONFIG_URL, produces = MediaType.APPLICATION_JSON_VALUE)
60+
@ResponseBody
61+
public Map<String, Object> openapiYaml(HttpServletRequest request) {
62+
buildConfigUrl(request);
63+
return swaggerUiConfig.getConfigParameters();
5864
}
5965

60-
private String buildUrl(final String contextPath, final String docsUrl) {
66+
private String buildUrl(final HttpServletRequest request, final String docsUrl) {
67+
String contextPath = request.getContextPath();
68+
if (StringUtils.isNotBlank(mvcServletPath))
69+
contextPath += mvcServletPath;
6170
if (contextPath.endsWith(DEFAULT_PATH_SEPARATOR)) {
6271
return contextPath.substring(0, contextPath.length() - 1) + docsUrl;
6372
}
6473
return contextPath + docsUrl;
6574
}
75+
76+
private void buildConfigUrl(HttpServletRequest request) {
77+
if (StringUtils.isEmpty(swaggerUiConfig.getConfigUrl())) {
78+
String url = buildUrl(request, apiDocsUrl);
79+
String swaggerConfigUrl = url + DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE;
80+
swaggerUiConfig.setConfigUrl(swaggerConfigUrl);
81+
if (groupsEnabled)
82+
swaggerUiConfig.addUrl(url);
83+
else
84+
swaggerUiConfig.setUrl(url);
85+
}
86+
}
6687
}

springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocApp1RedirectDefaultTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void shouldRedirectWithDefaultQueryParams() throws Exception {
1616
.andExpect(status().isFound()).andReturn();
1717

1818
String locationHeader = mvcResult.getResponse().getHeader("Location");
19-
assertEquals("/swagger-ui/index.html?url=/v3/api-docs&validatorUrl=", locationHeader);
19+
assertEquals("/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config", locationHeader);
2020
}
2121

2222
}

springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocApp1RedirectWithConfigTest.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
import org.springframework.test.web.servlet.MvcResult;
1111
import test.org.springdoc.ui.AbstractSpringDocTest;
1212

13+
import static org.hamcrest.Matchers.is;
1314
import static org.junit.Assert.assertEquals;
1415
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
16+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
1517
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
1618

1719
@SpringBootTest(properties = {
@@ -26,7 +28,11 @@ public void shouldRedirectWithConfiguredParams() throws Exception {
2628
.andExpect(status().isFound()).andReturn();
2729

2830
String locationHeader = mvcResult.getResponse().getHeader("Location");
29-
assertEquals("/swagger-ui/index.html?url=/baf/batz&validatorUrl=/foo/validate", locationHeader);
31+
assertEquals("/swagger-ui/index.html?configUrl=/baf/batz/swagger-config", locationHeader);
32+
33+
mvcResult = mockMvc.perform(get("/baf/batz/swagger-config"))
34+
.andExpect(status().isOk()).andExpect(jsonPath("$.validatorUrl", is("/foo/validate"))).andReturn();
35+
3036
}
3137

3238
}

springdoc-openapi-webflux-ui/src/main/java/org/springdoc/ui/SwaggerWelcome.java

+32-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
package org.springdoc.ui;
22

3+
import org.apache.commons.lang3.StringUtils;
34
import org.springdoc.core.SwaggerUiConfigProperties;
45
import org.springframework.beans.factory.annotation.Autowired;
56
import org.springframework.beans.factory.annotation.Value;
67
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
78
import org.springframework.context.annotation.Bean;
89
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.http.MediaType;
911
import org.springframework.stereotype.Controller;
1012
import org.springframework.web.reactive.function.server.RouterFunction;
13+
import org.springframework.web.reactive.function.server.RouterFunctions;
1114
import org.springframework.web.reactive.function.server.ServerResponse;
1215
import org.springframework.web.util.UriComponentsBuilder;
1316

1417
import java.net.URI;
15-
import java.util.Map;
1618

1719
import static org.springdoc.core.Constants.*;
18-
import static org.springdoc.core.SwaggerUiQueryParamsAppender.appendSwaggerUiQueryParams;
20+
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
1921
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
22+
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
2023
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
2124

2225
@Controller
@@ -34,18 +37,39 @@ public class SwaggerWelcome {
3437
private String webJarsPrefixUrl;
3538

3639
@Autowired
37-
private SwaggerUiConfigProperties swaggerUiConfig;
40+
public SwaggerUiConfigProperties swaggerUiConfig;
41+
42+
@Value(SPRINGDOC_GROUPS_ENABLED_VALUE)
43+
private boolean groupsEnabled;
3844

3945
@Bean
4046
@ConditionalOnProperty(name = SPRINGDOC_SWAGGER_UI_ENABLED, matchIfMissing = true)
4147
RouterFunction<ServerResponse> routerFunction() {
4248
String baseUrl = webJarsPrefixUrl + SWAGGER_UI_URL;
49+
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(baseUrl);
50+
buildConfigUrl();
51+
uriBuilder.queryParam(SwaggerUiConfigProperties.CONFIG_URL_PROPERTY, swaggerUiConfig.getConfigUrl());
52+
return route(GET(uiPath),
53+
req -> ServerResponse.temporaryRedirect(URI.create(uriBuilder.build().encode().toString())).build());
54+
}
4355

44-
final Map<String, String> swaggerUiParams = swaggerUiConfig.getConfigParameters();
45-
final UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(baseUrl);
46-
final UriComponentsBuilder builder = appendSwaggerUiQueryParams(uriBuilder, swaggerUiParams, apiDocsUrl);
56+
@Bean
57+
@ConditionalOnProperty(name = SPRINGDOC_SWAGGER_UI_ENABLED, matchIfMissing = true)
58+
RouterFunction<ServerResponse> getSwaggerUiConfig() {
59+
buildConfigUrl();
60+
return RouterFunctions.route(GET(swaggerUiConfig.getConfigUrl()).and(accept(MediaType.APPLICATION_JSON)), req -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(swaggerUiConfig.getConfigParameters()));
61+
}
4762

48-
return route(GET(uiPath),
49-
req -> ServerResponse.temporaryRedirect(URI.create(builder.build().encode().toString())).build());
63+
private void buildConfigUrl() {
64+
if (StringUtils.isEmpty(swaggerUiConfig.getConfigUrl()))
65+
{
66+
String swaggerConfigUrl = apiDocsUrl + DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE;
67+
swaggerUiConfig.setConfigUrl(swaggerConfigUrl);
68+
if (groupsEnabled)
69+
swaggerUiConfig.addUrl(apiDocsUrl);
70+
else
71+
swaggerUiConfig.setUrl(apiDocsUrl);
72+
}
5073
}
74+
5175
}

springdoc-openapi-webflux-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocApp1RedirectDefaultTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public void shouldRedirectWithDefaultQueryParams() throws Exception {
2727
WebTestClient.ResponseSpec responseSpec = webTestClient.get().uri("/swagger-ui.html").exchange()
2828
.expectStatus().isTemporaryRedirect();
2929
responseSpec.expectHeader()
30-
.value("Location", Matchers.is("/webjars/swagger-ui/index.html?url=/v3/api-docs&validatorUrl="));
30+
.value("Location", Matchers.is("/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config"));
3131

3232
}
3333

0 commit comments

Comments
 (0)