Skip to content

Commit 446e1f5

Browse files
author
bnasslahsen
committed
Same operationId for overloaded methods using Groups, breaks swagger-ui collapsibles. Fixes #399
1 parent c9b5836 commit 446e1f5

File tree

10 files changed

+196
-43
lines changed

10 files changed

+196
-43
lines changed

Diff for: springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java

+12-12
Original file line numberDiff line numberDiff line change
@@ -120,25 +120,26 @@ protected synchronized OpenAPI getOpenApi() {
120120
responseBuilder.buildGenericResponse(openAPIBuilder.getComponents(), findControllerAdvice);
121121

122122
getPaths(restControllers);
123-
openApi = openAPIBuilder.getOpenAPI();
123+
openApi = openAPIBuilder.getCalculatedOpenAPI();
124124

125125
// run the optional customisers
126126
openApiCustomisers.ifPresent(apiCustomisers -> apiCustomisers.forEach(openApiCustomiser -> openApiCustomiser.customise(openApi)));
127+
computeDone = true;
128+
openAPIBuilder.setCachedOpenAPI(openApi);
129+
openAPIBuilder.resetCalculatedOpenAPI();
127130
LOGGER.info("Init duration for springdoc-openapi is: {} ms",
128131
Duration.between(start, Instant.now()).toMillis());
129-
computeDone = true;
130-
}
131-
else {
132-
openApi = openAPIBuilder.getOpenAPI();
133132
}
133+
else
134+
openApi = openAPIBuilder.getCachedOpenAPI();
134135
return openApi;
135136
}
136137

137138
protected abstract void getPaths(Map<String, Object> findRestControllers);
138139

139-
protected void calculatePath(OpenAPIBuilder openAPIBuilder, HandlerMethod handlerMethod, String operationPath,
140+
protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
140141
Set<RequestMethod> requestMethods) {
141-
OpenAPI openAPI = openAPIBuilder.getOpenAPI();
142+
OpenAPI openAPI = openAPIBuilder.getCalculatedOpenAPI();
142143
Components components = openAPIBuilder.getComponents();
143144
Paths paths = openAPIBuilder.getPaths();
144145

@@ -198,9 +199,8 @@ protected void calculatePath(OpenAPIBuilder openAPIBuilder, HandlerMethod handle
198199
methodAttributes.getMethodConsumes(), components,
199200
methodAttributes.getJsonViewAnnotationForRequestBody())
200201
.ifPresent(operation::setRequestBody);
201-
202202
// requests
203-
operation = requestBuilder.build(components, handlerMethod, requestMethod, operation, methodAttributes);
203+
operation = requestBuilder.build(components, handlerMethod, requestMethod, operation, methodAttributes, openAPI);
204204

205205
// responses
206206
ApiResponses apiResponses = responseBuilder.build(components, handlerMethod, operation, methodAttributes);
@@ -354,12 +354,12 @@ protected boolean isAdditionalRestController(Class<?> rawClass) {
354354
return ADDITIONAL_REST_CONTROLLERS.stream().anyMatch(clazz -> clazz.isAssignableFrom(rawClass));
355355
}
356356

357-
public static void addRestControllers(Class<?>... classes){
357+
public static void addRestControllers(Class<?>... classes) {
358358
ADDITIONAL_REST_CONTROLLERS.addAll(Arrays.asList(classes));
359359
}
360360

361-
protected Set getDefaultAllowedHttpMethods(){
362-
RequestMethod[] allowedRequestMethods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.OPTIONS, RequestMethod.HEAD} ;
361+
protected Set getDefaultAllowedHttpMethods() {
362+
RequestMethod[] allowedRequestMethods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.OPTIONS, RequestMethod.HEAD };
363363
return new HashSet<>(Arrays.asList(allowedRequestMethods));
364364
}
365365
}

Diff for: springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import io.swagger.v3.oas.annotations.Hidden;
4545
import io.swagger.v3.oas.annotations.enums.ParameterIn;
4646
import io.swagger.v3.oas.models.Components;
47+
import io.swagger.v3.oas.models.OpenAPI;
4748
import io.swagger.v3.oas.models.Operation;
4849
import io.swagger.v3.oas.models.media.Schema;
4950
import io.swagger.v3.oas.models.parameters.Parameter;
@@ -131,10 +132,10 @@ protected AbstractRequestBuilder(GenericParameterBuilder parameterBuilder, Reque
131132

132133

133134
public Operation build(Components components, HandlerMethod handlerMethod, RequestMethod requestMethod,
134-
Operation operation, MethodAttributes methodAttributes) {
135+
Operation operation, MethodAttributes methodAttributes, OpenAPI openAPI) {
135136
// Documentation
136137
String operationId = operationBuilder.getOperationId(handlerMethod.getMethod().getName(),
137-
operation.getOperationId());
138+
operation.getOperationId(), openAPI);
138139

139140
operation.setOperationId(operationId);
140141
// requests

Diff for: springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIBuilder.java

+38-18
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ public class OpenAPIBuilder {
7171

7272
private static final Logger LOGGER = LoggerFactory.getLogger(OpenAPIBuilder.class);
7373

74-
private final OpenAPI openAPI;
74+
private OpenAPI openAPI;
75+
76+
private OpenAPI cachedOpenAPI;
77+
78+
private OpenAPI calculatedOpenAPI;
7579

7680
private final ApplicationContext context;
7781

@@ -96,11 +100,6 @@ public class OpenAPIBuilder {
96100
if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
97101
this.isServersPresent = true;
98102
}
99-
else {
100-
this.openAPI = new OpenAPI();
101-
this.openAPI.setComponents(new Components());
102-
this.openAPI.setPaths(new Paths());
103-
}
104103
this.context = context;
105104
this.securityParser = securityParser;
106105
this.springSecurityOAuth2Provider = springSecurityOAuth2Provider;
@@ -117,37 +116,42 @@ private static String splitCamelCase(String str) {
117116
.toLowerCase(Locale.ROOT);
118117
}
119118

120-
public OpenAPI getOpenAPI() {
121-
return openAPI;
122-
}
123-
124119
public Components getComponents() {
125-
return openAPI.getComponents();
120+
return calculatedOpenAPI.getComponents();
126121
}
127122

128123
public Paths getPaths() {
129-
return openAPI.getPaths();
124+
return calculatedOpenAPI.getPaths();
130125
}
131126

132127
public void build() {
133128
Optional<OpenAPIDefinition> apiDef = getOpenAPIDefinition();
129+
130+
if(openAPI==null){
131+
this.calculatedOpenAPI = new OpenAPI();
132+
this.calculatedOpenAPI.setComponents(new Components());
133+
this.calculatedOpenAPI.setPaths(new Paths());
134+
}
135+
else
136+
this.calculatedOpenAPI=openAPI;
137+
134138
if (apiDef.isPresent()) {
135-
buildOpenAPIWithOpenAPIDefinition(openAPI, apiDef.get());
139+
buildOpenAPIWithOpenAPIDefinition(calculatedOpenAPI, apiDef.get());
136140
}
137141
// Set default info
138-
else if (openAPI.getInfo() == null) {
142+
else if (calculatedOpenAPI.getInfo() == null) {
139143
Info infos = new Info().title(DEFAULT_TITLE).version(DEFAULT_VERSION);
140-
openAPI.setInfo(infos);
144+
calculatedOpenAPI.setInfo(infos);
141145
}
142146
// default server value
143-
if (CollectionUtils.isEmpty(openAPI.getServers()) || !isServersPresent) {
147+
if (CollectionUtils.isEmpty(calculatedOpenAPI.getServers()) || !isServersPresent) {
144148
Server server = new Server().url(serverBaseUrl).description(DEFAULT_SERVER_DESCRIPTION);
145149
List<Server> servers = new ArrayList();
146150
servers.add(server);
147-
openAPI.setServers(servers);
151+
calculatedOpenAPI.setServers(servers);
148152
}
149153
// add security schemes
150-
this.calculateSecuritySchemes(openAPI.getComponents());
154+
this.calculateSecuritySchemes(calculatedOpenAPI.getComponents());
151155
}
152156

153157
public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI) {
@@ -403,4 +407,20 @@ public Map<String, Object> getControllerAdviceMap() {
403407
public Optional<SecurityOAuth2Provider> getSpringSecurityOAuth2Provider() {
404408
return springSecurityOAuth2Provider;
405409
}
410+
411+
public OpenAPI getCachedOpenAPI() {
412+
return cachedOpenAPI;
413+
}
414+
415+
public void setCachedOpenAPI(OpenAPI cachedOpenAPI) {
416+
this.cachedOpenAPI = cachedOpenAPI;
417+
}
418+
419+
public OpenAPI getCalculatedOpenAPI() {
420+
return calculatedOpenAPI;
421+
}
422+
423+
public void resetCalculatedOpenAPI() {
424+
this.calculatedOpenAPI=null;
425+
}
406426
}

Diff for: springdoc-openapi-common/src/main/java/org/springdoc/core/OperationBuilder.java

+4-7
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,14 @@ public class OperationBuilder {
6565

6666
private final SecurityParser securityParser;
6767

68-
private final OpenAPIBuilder openAPIBuilder;
69-
7068
private final PropertyResolverUtils propertyResolverUtils;
7169

7270
public OperationBuilder(GenericParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
73-
SecurityParser securityParser, OpenAPIBuilder openAPIBuilder, PropertyResolverUtils propertyResolverUtils) {
71+
SecurityParser securityParser, PropertyResolverUtils propertyResolverUtils) {
7472
super();
7573
this.parameterBuilder = parameterBuilder;
7674
this.requestBodyBuilder = requestBodyBuilder;
7775
this.securityParser = securityParser;
78-
this.openAPIBuilder = openAPIBuilder;
7976
this.propertyResolverUtils = propertyResolverUtils;
8077
}
8178

@@ -401,11 +398,11 @@ private Optional<List<String>> getStringListFromStringArray(String[] array) {
401398
return Optional.of(list);
402399
}
403400

404-
public String getOperationId(String operationId, String oldOperationId) {
401+
public String getOperationId(String operationId, String oldOperationId, OpenAPI openAPI) {
405402
if (StringUtils.isNotBlank(oldOperationId))
406-
return this.getOperationId(oldOperationId, openAPIBuilder.getOpenAPI());
403+
return this.getOperationId(oldOperationId, openAPI);
407404
else
408-
return this.getOperationId(operationId, openAPIBuilder.getOpenAPI());
405+
return this.getOperationId(operationId, openAPI);
409406
}
410407

411408
}

Diff for: springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ public ModelConverterRegistrar modelConverterRegistrar(Optional<List<ModelConver
8787
@Bean
8888
@ConditionalOnWebApplication
8989
public OperationBuilder operationBuilder(GenericParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
90-
SecurityParser securityParser, OpenAPIBuilder openAPIBuilder, PropertyResolverUtils propertyResolverUtils) {
90+
SecurityParser securityParser, PropertyResolverUtils propertyResolverUtils) {
9191
return new OperationBuilder(parameterBuilder, requestBodyBuilder,
92-
securityParser, openAPIBuilder, propertyResolverUtils);
92+
securityParser, propertyResolverUtils);
9393
}
9494

9595
@Bean

Diff for: springdoc-openapi-webflux-core/src/main/java/org/springdoc/api/OpenApiResource.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ protected void getPaths(Map<String, Object> restControllers) {
105105
// default allowed requestmethods
106106
if (requestMethods.isEmpty())
107107
requestMethods = this.getDefaultAllowedHttpMethods();
108-
calculatePath(openAPIBuilder, handlerMethod, operationPath, requestMethods);
108+
calculatePath(handlerMethod, operationPath, requestMethods);
109109
}
110110
}
111111
}

Diff for: springdoc-openapi-webmvc-core/src/main/java/org/springdoc/api/OpenApiResource.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ && isPathToMatch(operationPath)) {
131131
// default allowed requestmethods
132132
if (requestMethods.isEmpty())
133133
requestMethods = this.getDefaultAllowedHttpMethods();
134-
calculatePath(openAPIBuilder, handlerMethod, operationPath, requestMethods);
134+
calculatePath(handlerMethod, operationPath, requestMethods);
135135
}
136136
}
137137
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
*
3+
* * Copyright 2019-2020 the original author or authors.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * https://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package test.org.springdoc.api.app84;
20+
21+
import org.springframework.web.bind.annotation.GetMapping;
22+
import org.springframework.web.bind.annotation.RequestMapping;
23+
import org.springframework.web.bind.annotation.RestController;
24+
25+
@RestController
26+
@RequestMapping("/api")
27+
public class HelloController {
28+
29+
@GetMapping("/persons")
30+
public String persons() {
31+
return "OK";
32+
}
33+
34+
@GetMapping("/persons1")
35+
public String persons(String toto) {
36+
return "OK";
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
*
3+
* * Copyright 2019-2020 the original author or authors.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * https://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package test.org.springdoc.api.app84;
20+
21+
import test.org.springdoc.api.AbstractSpringDocTest;
22+
23+
import org.springframework.boot.autoconfigure.SpringBootApplication;
24+
25+
public class SpringDocApp84Test extends AbstractSpringDocTest {
26+
27+
@SpringBootApplication
28+
static class SpringDocTestApp {}
29+
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/api/persons": {
15+
"get": {
16+
"tags": [
17+
"hello-controller"
18+
],
19+
"operationId": "persons",
20+
"responses": {
21+
"200": {
22+
"description": "default response",
23+
"content": {
24+
"*/*": {
25+
"schema": {
26+
"type": "string"
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
33+
},
34+
"/api/persons1": {
35+
"get": {
36+
"tags": [
37+
"hello-controller"
38+
],
39+
"operationId": "persons_1",
40+
"parameters": [
41+
{
42+
"name": "toto",
43+
"in": "query",
44+
"required": true,
45+
"schema": {
46+
"type": "string"
47+
}
48+
}
49+
],
50+
"responses": {
51+
"200": {
52+
"description": "default response",
53+
"content": {
54+
"*/*": {
55+
"schema": {
56+
"type": "string"
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}
64+
},
65+
"components": {}
66+
}

0 commit comments

Comments
 (0)