Skip to content

Commit 0cd571a

Browse files
committed
Set default content type for problem details object to application/problem+json. Fixes #2963
1 parent 216fab6 commit 0cd571a

File tree

3 files changed

+64
-18
lines changed

3 files changed

+64
-18
lines changed

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

+26-11
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.List;
4141
import java.util.Locale;
4242
import java.util.Map;
43+
import java.util.Map.Entry;
4344
import java.util.Objects;
4445
import java.util.Optional;
4546
import java.util.Set;
@@ -72,6 +73,7 @@
7273
import org.springdoc.core.utils.PropertyResolverUtils;
7374
import org.springdoc.core.utils.SpringDocAnnotationsUtils;
7475

76+
import org.springframework.aop.support.AopUtils;
7577
import org.springframework.beans.BeansException;
7678
import org.springframework.context.ApplicationContext;
7779
import org.springframework.context.ApplicationContextAware;
@@ -80,6 +82,8 @@
8082
import org.springframework.core.ResolvableType;
8183
import org.springframework.core.annotation.AnnotatedElementUtils;
8284
import org.springframework.http.HttpStatus;
85+
import org.springframework.http.MediaType;
86+
import org.springframework.http.ProblemDetail;
8387
import org.springframework.util.CollectionUtils;
8488
import org.springframework.util.ReflectionUtils;
8589
import org.springframework.web.bind.annotation.ControllerAdvice;
@@ -277,7 +281,7 @@ public ApiResponses build(Components components, HandlerMethod handlerMethod, Op
277281
private Map<String, ApiResponse> filterAndEnrichGenericMapResponseByDeclarations(HandlerMethod handlerMethod, Map<String, ApiResponse> genericMapResponse) {
278282
if (operationService.getJavadocProvider() != null) {
279283
JavadocProvider javadocProvider = operationService.getJavadocProvider();
280-
for (Map.Entry<String, ApiResponse> genericResponse : genericMapResponse.entrySet()) {
284+
for (Entry<String, ApiResponse> genericResponse : genericMapResponse.entrySet()) {
281285
Map<String, Object> extensions = genericResponse.getValue().getExtensions();
282286
Collection<String> genericExceptions = (Collection<String>) extensions.get(EXTENSION_EXCEPTION_CLASSES);
283287
for (Class<?> declaredException : handlerMethod.getMethod().getExceptionTypes()) {
@@ -305,13 +309,13 @@ private Map<String, ApiResponse> filterAndEnrichGenericMapResponseByDeclarations
305309
*/
306310
public void buildGenericResponse(Components components, Map<String, Object> findControllerAdvice, Locale locale) {
307311
// ControllerAdvice
308-
for (Map.Entry<String, Object> entry : findControllerAdvice.entrySet()) {
312+
for (Entry<String, Object> entry : findControllerAdvice.entrySet()) {
309313
List<Method> methods = new ArrayList<>();
310314
Object controllerAdvice = entry.getValue();
311315
// get all methods with annotation @ExceptionHandler
312316
Class<?> objClz = controllerAdvice.getClass();
313-
if (org.springframework.aop.support.AopUtils.isAopProxy(controllerAdvice))
314-
objClz = org.springframework.aop.support.AopUtils.getTargetClass(controllerAdvice);
317+
if (AopUtils.isAopProxy(controllerAdvice))
318+
objClz = AopUtils.getTargetClass(controllerAdvice);
315319
ControllerAdviceInfo controllerAdviceInfo = new ControllerAdviceInfo(controllerAdvice);
316320
Arrays.stream(ReflectionUtils.getAllDeclaredMethods(objClz))
317321
.filter(m -> m.isAnnotationPresent(ExceptionHandler.class)
@@ -422,11 +426,12 @@ private Map<String, ApiResponse> computeResponseFromDoc(Components components, M
422426
*/
423427
private void buildGenericApiResponses(Components components, MethodParameter methodParameter, ApiResponses apiResponsesOp,
424428
MethodAttributes methodAttributes) {
429+
ApiResponse apiResponse = null;
425430
if (!CollectionUtils.isEmpty(apiResponsesOp)) {
426431
// API Responses at operation and @ApiResponse annotation
427-
for (Map.Entry<String, ApiResponse> entry : apiResponsesOp.entrySet()) {
432+
for (Entry<String, ApiResponse> entry : apiResponsesOp.entrySet()) {
428433
String httpCode = entry.getKey();
429-
ApiResponse apiResponse = entry.getValue();
434+
apiResponse = entry.getValue();
430435
buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, apiResponse, true);
431436
}
432437
}
@@ -435,11 +440,21 @@ private void buildGenericApiResponses(Components components, MethodParameter met
435440
// available
436441
String httpCode = evaluateResponseStatus(methodParameter.getMethod(), Objects.requireNonNull(methodParameter.getMethod()).getClass(), true);
437442
if (Objects.nonNull(httpCode)) {
438-
ApiResponse apiResponse = methodAttributes.getGenericMapResponse().containsKey(httpCode) ? methodAttributes.getGenericMapResponse().get(httpCode)
443+
apiResponse = methodAttributes.getGenericMapResponse().containsKey(httpCode) ? methodAttributes.getGenericMapResponse().get(httpCode)
439444
: new ApiResponse();
440445
buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, apiResponse, true);
441446
}
442447
}
448+
if (apiResponse != null) {
449+
Content content = apiResponse.getContent();
450+
if (content != null) {
451+
io.swagger.v3.oas.models.media.MediaType mediaType = content.get(MediaType.ALL_VALUE);
452+
if (mediaType != null && ProblemDetail.class.isAssignableFrom(methodParameter.getParameterType())) {
453+
content.addMediaType(MediaType.APPLICATION_PROBLEM_JSON_VALUE, mediaType);
454+
content.remove(MediaType.ALL_VALUE);
455+
}
456+
}
457+
}
443458
}
444459

445460
/**
@@ -455,7 +470,7 @@ private void buildApiResponses(Components components, MethodParameter methodPara
455470
Map<String, ApiResponse> genericMapResponse = methodAttributes.getGenericMapResponse();
456471
if (!CollectionUtils.isEmpty(apiResponsesOp) && apiResponsesOp.size() > genericMapResponse.size()) {
457472
// API Responses at operation and @ApiResponse annotation
458-
for (Map.Entry<String, ApiResponse> entry : apiResponsesOp.entrySet()) {
473+
for (Entry<String, ApiResponse> entry : apiResponsesOp.entrySet()) {
459474
String httpCode = entry.getKey();
460475
boolean methodAttributesCondition = !methodAttributes.isMethodOverloaded() || (methodAttributes.isMethodOverloaded() && isValidHttpCode(httpCode, methodParameter));
461476
if (!genericMapResponse.containsKey(httpCode) && methodAttributesCondition) {
@@ -693,8 +708,8 @@ private Map<String, ApiResponse> getGenericMapResponse(HandlerMethod handlerMeth
693708
List<ControllerAdviceInfo> controllerAdviceInfosInThisBean = localExceptionHandlers.stream()
694709
.filter(controllerInfo -> {
695710
Class<?> objClz = controllerInfo.getControllerAdvice().getClass();
696-
if (org.springframework.aop.support.AopUtils.isAopProxy(controllerInfo.getControllerAdvice()))
697-
objClz = org.springframework.aop.support.AopUtils.getTargetClass(controllerInfo.getControllerAdvice());
711+
if (AopUtils.isAopProxy(controllerInfo.getControllerAdvice()))
712+
objClz = AopUtils.getTargetClass(controllerInfo.getControllerAdvice());
698713
return beanType.equals(objClz);
699714
})
700715
.toList();
@@ -772,7 +787,7 @@ private boolean isValidHttpCode(String httpCode, MethodParameter methodParameter
772787
final io.swagger.v3.oas.annotations.Operation apiOperation = AnnotatedElementUtils.findMergedAnnotation(method,
773788
io.swagger.v3.oas.annotations.Operation.class);
774789
if (apiOperation != null) {
775-
responseSet = new HashSet<>(Arrays.asList(apiOperation.responses()));
790+
responseSet = new HashSet<>(asList(apiOperation.responses()));
776791
if (isHttpCodePresent(httpCode, responseSet))
777792
result = true;
778793
}

springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app124/MyExceptionHandler.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package test.org.springdoc.api.v31.app124;
2626

2727
import org.springframework.http.HttpStatus;
28+
import org.springframework.http.ProblemDetail;
2829
import org.springframework.web.bind.annotation.ExceptionHandler;
2930
import org.springframework.web.bind.annotation.ResponseStatus;
3031
import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -34,7 +35,7 @@ public class MyExceptionHandler {
3435

3536
@ExceptionHandler(RuntimeException.class)
3637
@ResponseStatus(HttpStatus.BAD_GATEWAY)
37-
public Object gateway(RuntimeException e) {
38+
public ProblemDetail gateway(RuntimeException e) {
3839
return null;
3940
}
4041
}

springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.1.0/app124.json

+36-6
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,19 @@
3737
}
3838
],
3939
"responses": {
40+
"404": {
41+
"description": "Not here"
42+
},
4043
"502": {
4144
"description": "Bad Gateway",
4245
"content": {
43-
"*/*": {
46+
"application/problem+json": {
4447
"schema": {
45-
"type": "object"
48+
"$ref": "#/components/schemas/ProblemDetail"
4649
}
4750
}
4851
}
4952
},
50-
"404": {
51-
"description": "Not here"
52-
},
5353
"418": {
5454
"description": "I'm a teapot",
5555
"content": {
@@ -64,5 +64,35 @@
6464
}
6565
}
6666
},
67-
"components": {}
67+
"components": {
68+
"schemas": {
69+
"ProblemDetail": {
70+
"type": "object",
71+
"properties": {
72+
"type": {
73+
"type": "string",
74+
"format": "uri"
75+
},
76+
"title": {
77+
"type": "string"
78+
},
79+
"status": {
80+
"type": "integer",
81+
"format": "int32"
82+
},
83+
"detail": {
84+
"type": "string"
85+
},
86+
"instance": {
87+
"type": "string",
88+
"format": "uri"
89+
},
90+
"properties": {
91+
"type": "object",
92+
"additionalProperties": {}
93+
}
94+
}
95+
}
96+
}
97+
}
6898
}

0 commit comments

Comments
 (0)