Skip to content

Commit 13064bc

Browse files
committed
override-with-generic-response shouldn't shallow copy. Fixes #1962
1 parent 2ae4992 commit 13064bc

File tree

8 files changed

+296
-15
lines changed

8 files changed

+296
-15
lines changed

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.lang.reflect.Type;
3232
import java.util.ArrayList;
3333
import java.util.Arrays;
34+
import java.util.Collection;
3435
import java.util.HashSet;
3536
import java.util.LinkedHashMap;
3637
import java.util.List;
@@ -43,6 +44,8 @@
4344
import java.util.stream.Stream;
4445

4546
import com.fasterxml.jackson.annotation.JsonView;
47+
import com.fasterxml.jackson.core.JsonProcessingException;
48+
import com.fasterxml.jackson.databind.ObjectMapper;
4649
import io.swagger.v3.core.util.AnnotationsUtils;
4750
import io.swagger.v3.oas.models.Components;
4851
import io.swagger.v3.oas.models.Operation;
@@ -52,11 +55,14 @@
5255
import io.swagger.v3.oas.models.responses.ApiResponses;
5356
import org.apache.commons.lang3.ArrayUtils;
5457
import org.apache.commons.lang3.StringUtils;
58+
import org.slf4j.Logger;
59+
import org.slf4j.LoggerFactory;
5560
import org.springdoc.core.models.ControllerAdviceInfo;
5661
import org.springdoc.core.models.MethodAttributes;
5762
import org.springdoc.core.parsers.ReturnTypeParser;
5863
import org.springdoc.core.properties.SpringDocConfigProperties;
5964
import org.springdoc.core.providers.JavadocProvider;
65+
import org.springdoc.core.providers.ObjectMapperProvider;
6066
import org.springdoc.core.utils.PropertyResolverUtils;
6167
import org.springdoc.core.utils.SpringDocAnnotationsUtils;
6268

@@ -127,6 +133,10 @@ public class GenericResponseService {
127133
*/
128134
private final List<ControllerAdviceInfo> localExceptionHandlers = new ArrayList<>();
129135

136+
/**
137+
* The constant LOGGER.
138+
*/
139+
private static final Logger LOGGER = LoggerFactory.getLogger(GenericResponseService.class);
130140
/**
131141
* Instantiates a new Generic response builder.
132142
*
@@ -251,9 +261,9 @@ private Map<String, ApiResponse> filterAndEnrichGenericMapResponseByDeclarations
251261
JavadocProvider javadocProvider = operationService.getJavadocProvider();
252262
for (Map.Entry<String, ApiResponse> genericResponse : genericMapResponse.entrySet()) {
253263
Map<String, Object> extensions = genericResponse.getValue().getExtensions();
254-
Set<Class<?>> genericExceptions = (Set<Class<?>>) extensions.get(EXTENSION_EXCEPTION_CLASSES);
264+
Collection<String> genericExceptions = (Collection<String>) extensions.get(EXTENSION_EXCEPTION_CLASSES);
255265
for (Class<?> declaredException : handlerMethod.getMethod().getExceptionTypes()) {
256-
if (genericExceptions.contains(declaredException)) {
266+
if (genericExceptions.contains(declaredException.getName())) {
257267
Map<String, String> javadocThrows = javadocProvider.getMethodJavadocThrows(handlerMethod.getMethod());
258268
String description = javadocThrows.get(declaredException.getName());
259269
if (description == null)
@@ -683,7 +693,16 @@ private synchronized Map<String, ApiResponse> getGenericMapResponse(Class<?> bea
683693
});
684694
}
685695

686-
return genericApiResponseMap;
696+
LinkedHashMap<String, ApiResponse> genericApiResponsesClone;
697+
try {
698+
ObjectMapper objectMapper = ObjectMapperProvider.createJson(springDocConfigProperties);
699+
genericApiResponsesClone = objectMapper.readValue(objectMapper.writeValueAsString(genericApiResponseMap), ApiResponses.class);
700+
return genericApiResponsesClone;
701+
}
702+
catch (JsonProcessingException e) {
703+
LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage());
704+
return genericApiResponseMap;
705+
}
687706
}
688707

689708
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package test.org.springdoc.api.v30.app199;
2+
3+
import org.springframework.web.bind.annotation.ControllerAdvice;
4+
import org.springframework.web.bind.annotation.ExceptionHandler;
5+
import org.springframework.web.bind.annotation.ResponseBody;
6+
import org.springframework.web.bind.annotation.ResponseStatus;
7+
8+
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
9+
10+
@ControllerAdvice
11+
public class CustomExceptionHandler {
12+
13+
static public class MyInternalException extends Exception {}
14+
15+
@ResponseStatus( value = INTERNAL_SERVER_ERROR )
16+
@ExceptionHandler( MyInternalException.class )
17+
@ResponseBody
18+
public ErrorDto handleMyInternalException( final MyInternalException ex ) {
19+
return new ErrorDto( ex.getMessage() );
20+
}
21+
22+
23+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package test.org.springdoc.api.v30.app199;
2+
3+
public class ErrorDto {
4+
private String message;
5+
6+
public ErrorDto( final String message ) {
7+
this.message = message;
8+
}
9+
10+
public String getMessage() { return message; }
11+
public void setMessage( final String message ) { this.message = message; }
12+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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.app199;
24+
25+
26+
import java.util.LinkedHashMap;
27+
import java.util.Map;
28+
29+
import com.fasterxml.jackson.databind.ObjectMapper;
30+
import io.swagger.v3.oas.models.Operation;
31+
import io.swagger.v3.oas.models.examples.Example;
32+
import org.springdoc.core.customizers.OperationCustomizer;
33+
34+
import org.springframework.beans.factory.annotation.Autowired;
35+
import org.springframework.context.annotation.Bean;
36+
import org.springframework.web.bind.annotation.GetMapping;
37+
import org.springframework.web.bind.annotation.RestController;
38+
import org.springframework.web.method.HandlerMethod;
39+
40+
import static org.springframework.http.MediaType.ALL_VALUE;
41+
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
42+
43+
@RestController
44+
public class HelloController {
45+
46+
@Autowired
47+
ObjectMapper defaultObjectMapper;
48+
49+
@GetMapping(
50+
value = "/first",
51+
produces = APPLICATION_JSON_VALUE
52+
)
53+
public void first() {}
54+
55+
@GetMapping(
56+
value = "/second",
57+
produces = APPLICATION_JSON_VALUE
58+
)
59+
public void second() {}
60+
61+
@Bean
62+
public OperationCustomizer operationCustomizer()
63+
{
64+
return ( Operation operation, HandlerMethod handlerMethod ) ->
65+
{
66+
final io.swagger.v3.oas.models.media.MediaType mediaType = operation
67+
.getResponses()
68+
.get( "500" )
69+
.getContent()
70+
.get( ALL_VALUE );
71+
72+
mediaType.setExamples( mediaType.getExamples() != null
73+
? mediaType.getExamples()
74+
: new LinkedHashMap<>() );
75+
76+
final Map<String, Example> examples = mediaType.getExamples();
77+
78+
switch ( handlerMethod.getMethod().getName() ) {
79+
80+
case "first" :
81+
examples.put(
82+
"First case example",
83+
new Example().value(
84+
defaultObjectMapper.valueToTree(
85+
new ErrorDto( "An ErrorDto sample specific to /first endpoint" )
86+
)
87+
)
88+
);
89+
break;
90+
91+
case "second" :
92+
examples.put(
93+
"Second case example",
94+
new Example().value(
95+
defaultObjectMapper.valueToTree(
96+
new ErrorDto( "An ErrorDto sample specific to /second endpoint" )
97+
)
98+
)
99+
);
100+
break;
101+
}
102+
103+
return operation;
104+
};
105+
}
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.app199;
24+
25+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
26+
27+
import org.springframework.boot.autoconfigure.SpringBootApplication;
28+
29+
public class SpringDocApp199Test extends AbstractSpringDocV30Test {
30+
31+
@SpringBootApplication
32+
static class SpringDocTestApp {}
33+
34+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
"/second": {
15+
"get": {
16+
"tags": [
17+
"hello-controller"
18+
],
19+
"operationId": "second",
20+
"responses": {
21+
"500": {
22+
"description": "Internal Server Error",
23+
"content": {
24+
"*/*": {
25+
"schema": {
26+
"$ref": "#/components/schemas/ErrorDto"
27+
},
28+
"examples": {
29+
"Second case example": {
30+
"value": {
31+
"message": "An ErrorDto sample specific to /second endpoint"
32+
}
33+
}
34+
}
35+
}
36+
}
37+
},
38+
"200": {
39+
"description": "OK"
40+
}
41+
}
42+
}
43+
},
44+
"/first": {
45+
"get": {
46+
"tags": [
47+
"hello-controller"
48+
],
49+
"operationId": "first",
50+
"responses": {
51+
"500": {
52+
"description": "Internal Server Error",
53+
"content": {
54+
"*/*": {
55+
"schema": {
56+
"$ref": "#/components/schemas/ErrorDto"
57+
},
58+
"examples": {
59+
"First case example": {
60+
"value": {
61+
"message": "An ErrorDto sample specific to /first endpoint"
62+
}
63+
}
64+
}
65+
}
66+
}
67+
},
68+
"200": {
69+
"description": "OK"
70+
}
71+
}
72+
}
73+
}
74+
},
75+
"components": {
76+
"schemas": {
77+
"ErrorDto": {
78+
"type": "object",
79+
"properties": {
80+
"message": {
81+
"type": "string"
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)