Skip to content

Commit 0adc7e2

Browse files
committed
Wrong "response" description with two controllers having its own ExceptionHandler. Fixes #1845
1 parent 3bd5ba0 commit 0adc7e2

File tree

5 files changed

+207
-13
lines changed

5 files changed

+207
-13
lines changed

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

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ private Map<String, ApiResponse> filterAndEnrichGenericMapResponseByDeclarations
174174
Set<Class<?>> genericExceptions = (Set<Class<?>>) extensions.get(EXTENSION_EXCEPTION_CLASSES);
175175
for (Class<?> declaredException : handlerMethod.getMethod().getExceptionTypes()) {
176176
if (genericExceptions.contains(declaredException)) {
177-
Map<String, String> javadocThrows = javadocProvider.getMethodJavadocThrows(handlerMethod.getMethod());
178-
String description = javadocThrows.get(declaredException.getName());
179-
if (description == null)
180-
description = javadocThrows.get(declaredException.getSimpleName());
181-
if (description != null && !description.trim().isEmpty()) {
182-
genericResponse.getValue().setDescription(description);
183-
}
177+
Map<String, String> javadocThrows = javadocProvider.getMethodJavadocThrows(handlerMethod.getMethod());
178+
String description = javadocThrows.get(declaredException.getName());
179+
if (description == null)
180+
description = javadocThrows.get(declaredException.getSimpleName());
181+
if (description != null && !description.trim().isEmpty()) {
182+
genericResponse.getValue().setDescription(description);
183+
}
184184
}
185185
}
186186
}
@@ -233,7 +233,7 @@ public void buildGenericResponse(Components components, Map<String, Object> find
233233
apiResponses.forEach(controllerAdviceInfoApiResponseMap::put);
234234
}
235235
}
236-
synchronized (this){
236+
synchronized (this) {
237237
controllerAdviceInfos.add(controllerAdviceInfo);
238238
}
239239
}
@@ -388,7 +388,8 @@ private void buildApiResponses(Components components, MethodParameter methodPara
388388
}
389389
}
390390

391-
} else {
391+
}
392+
else {
392393
String httpCode = evaluateResponseStatus(methodParameter.getMethod(), Objects.requireNonNull(methodParameter.getMethod()).getClass(), false);
393394
if (Objects.nonNull(httpCode))
394395
buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, new ApiResponse(), false);
@@ -565,7 +566,8 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent()))
565566
exceptions.add(parameter.getType());
566567
}
567568
}
568-
} else {
569+
}
570+
else {
569571
exceptions.addAll(asList(exceptionHandler.value()));
570572
}
571573
apiResponse.addExtension(EXTENSION_EXCEPTION_CLASSES, exceptions);
@@ -634,10 +636,30 @@ else if (returnType instanceof ParameterizedType) {
634636
* @return the generic map response
635637
*/
636638
private synchronized Map<String, ApiResponse> getGenericMapResponse(Class<?> beanType) {
637-
return controllerAdviceInfos.stream()
638-
.filter(controllerAdviceInfo -> new ControllerAdviceBean(controllerAdviceInfo.getControllerAdvice()).isApplicableToBeanType(beanType))
639-
.map(ControllerAdviceInfo::getApiResponseMap)
639+
List<ControllerAdviceInfo> controllerAdviceInfosInThisBean = controllerAdviceInfos.stream()
640+
.filter(controllerAdviceInfo ->
641+
new ControllerAdviceBean(controllerAdviceInfo.getControllerAdvice()).isApplicableToBeanType(beanType))
642+
.filter(controllerAdviceInfo -> beanType.equals(controllerAdviceInfo.getControllerAdvice().getClass()))
643+
.collect(Collectors.toList());
644+
645+
Map<String, ApiResponse> genericApiResponseMap = controllerAdviceInfosInThisBean.stream()
646+
.map(ControllerAdviceInfo::getApiResponseMap)
640647
.collect(LinkedHashMap::new, Map::putAll, Map::putAll);
648+
649+
List<ControllerAdviceInfo> controllerAdviceInfosNotInThisBean = controllerAdviceInfos.stream()
650+
.filter(controllerAdviceInfo ->
651+
new ControllerAdviceBean(controllerAdviceInfo.getControllerAdvice()).isApplicableToBeanType(beanType))
652+
.filter(controllerAdviceInfo -> !beanType.equals(controllerAdviceInfo.getControllerAdvice().getClass()))
653+
.collect(Collectors.toList());
654+
655+
for (ControllerAdviceInfo controllerAdviceInfo : controllerAdviceInfosNotInThisBean) {
656+
controllerAdviceInfo.getApiResponseMap().forEach((key, apiResponse) -> {
657+
if(!genericApiResponseMap.containsKey(key))
658+
genericApiResponseMap.put(key, apiResponse);
659+
});
660+
}
661+
662+
return genericApiResponseMap;
641663
}
642664

643665
/**
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package test.org.springdoc.api.v30.app195;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
5+
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.web.bind.annotation.ExceptionHandler;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.ResponseStatus;
11+
import org.springframework.web.bind.annotation.RestController;
12+
13+
/**
14+
* @author bnasslahsen
15+
*/
16+
@RestController
17+
@RequestMapping("/example2")
18+
public class Example2Controller {
19+
20+
@ExceptionHandler(Exception.class)
21+
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
22+
@ApiResponse(responseCode = "500", description = "ExceptionHandler in example2")
23+
public String customControllerException() {
24+
return "example2";
25+
}
26+
27+
@GetMapping("/500")
28+
@Operation(
29+
tags = "example2",
30+
summary = "Example2 method",
31+
description = "This method is an example2"
32+
)
33+
public void test() {
34+
throw new RuntimeException();
35+
}
36+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package test.org.springdoc.api.v30.app195;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
5+
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.web.bind.annotation.ExceptionHandler;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.ResponseStatus;
11+
import org.springframework.web.bind.annotation.RestController;
12+
13+
/**
14+
* @author bnasslahsen
15+
*/
16+
@RestController
17+
@RequestMapping("/example")
18+
public class ExampleController {
19+
@ExceptionHandler(Exception.class)
20+
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
21+
@ApiResponse(responseCode = "500", description = "ExceptionHandler in example")
22+
public String customControllerException() {
23+
return "example";
24+
}
25+
26+
@GetMapping("/500")
27+
@Operation(
28+
tags = {"example"},
29+
summary = "Example method",
30+
description = "This method is an example"
31+
)
32+
public void test() {
33+
throw new RuntimeException();
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * * Copyright 2019-2022 the original author or authors.
6+
* * * *
7+
* * * * Licensed under the Apache License, Version 2.0 (the "License");
8+
* * * * you may not use this file except in compliance with the License.
9+
* * * * You may obtain a copy of the License at
10+
* * * *
11+
* * * * https://www.apache.org/licenses/LICENSE-2.0
12+
* * * *
13+
* * * * Unless required by applicable law or agreed to in writing, software
14+
* * * * distributed under the License is distributed on an "AS IS" BASIS,
15+
* * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* * * * See the License for the specific language governing permissions and
17+
* * * * limitations under the License.
18+
* * *
19+
* *
20+
*
21+
*/
22+
23+
package test.org.springdoc.api.v30.app195;
24+
25+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
26+
27+
import org.springframework.boot.autoconfigure.SpringBootApplication;
28+
import org.springframework.test.context.TestPropertySource;
29+
30+
public class SpringDocApp195Test extends AbstractSpringDocV30Test {
31+
32+
@SpringBootApplication
33+
static class SpringDocTestApp {}
34+
35+
}
Lines changed: 66 additions & 0 deletions
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+
"/example2/500": {
15+
"get": {
16+
"tags": [
17+
"example2"
18+
],
19+
"summary": "Example2 method",
20+
"description": "This method is an example2",
21+
"operationId": "test",
22+
"responses": {
23+
"500": {
24+
"description": "ExceptionHandler in example2",
25+
"content": {
26+
"*/*": {
27+
"schema": {
28+
"type": "string"
29+
}
30+
}
31+
}
32+
},
33+
"200": {
34+
"description": "OK"
35+
}
36+
}
37+
}
38+
},
39+
"/example/500": {
40+
"get": {
41+
"tags": [
42+
"example"
43+
],
44+
"summary": "Example method",
45+
"description": "This method is an example",
46+
"operationId": "test_1",
47+
"responses": {
48+
"500": {
49+
"description": "ExceptionHandler in example",
50+
"content": {
51+
"*/*": {
52+
"schema": {
53+
"type": "string"
54+
}
55+
}
56+
}
57+
},
58+
"200": {
59+
"description": "OK"
60+
}
61+
}
62+
}
63+
}
64+
},
65+
"components": {}
66+
}

0 commit comments

Comments
 (0)