Skip to content

Commit 65de3c1

Browse files
committed
Merge branch 'Mattias-Sehlstedt-use-return-type-schema'
2 parents 60a1704 + 35d79d6 commit 65de3c1

6 files changed

Lines changed: 179 additions & 92 deletions

File tree

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/models/MethodAttributes.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public class MethodAttributes {
134134
/**
135135
* The Use return type schema.
136136
*/
137-
private boolean useReturnTypeSchema;
137+
private final Map<String, Boolean> useReturnTypeSchema = new LinkedHashMap<>();
138138

139139
/**
140140
* Instantiates a new Method attributes.
@@ -529,20 +529,21 @@ public Locale getLocale() {
529529
}
530530

531531
/**
532-
* Is use return type schema boolean.
532+
* Gets use return type schema.
533533
*
534-
* @return the boolean
534+
* @return the use return type schema
535535
*/
536-
public boolean isUseReturnTypeSchema() {
536+
public Map<String, Boolean> getUseReturnTypeSchema() {
537537
return useReturnTypeSchema;
538538
}
539539

540540
/**
541-
* Sets use return type schema.
541+
* Put use return type schema.
542542
*
543+
* @param responseCode the response code
543544
* @param useReturnTypeSchema the use return type schema
544545
*/
545-
public void setUseReturnTypeSchema(boolean useReturnTypeSchema) {
546-
this.useReturnTypeSchema = useReturnTypeSchema;
546+
public void putUseReturnTypeSchema(String responseCode, boolean useReturnTypeSchema) {
547+
this.useReturnTypeSchema.put(responseCode, useReturnTypeSchema);
547548
}
548549
}

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

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,9 @@
5858
import io.swagger.v3.oas.models.media.Schema;
5959
import io.swagger.v3.oas.models.responses.ApiResponse;
6060
import io.swagger.v3.oas.models.responses.ApiResponses;
61+
import kotlin.reflect.jvm.internal.impl.load.kotlin.JvmType.Object;
6162
import org.apache.commons.lang3.ArrayUtils;
6263
import org.apache.commons.lang3.StringUtils;
63-
import org.slf4j.Logger;
64-
import org.slf4j.LoggerFactory;
6564
import org.springdoc.core.models.ControllerAdviceInfo;
6665
import org.springdoc.core.models.MethodAdviceInfo;
6766
import org.springdoc.core.models.MethodAttributes;
@@ -97,7 +96,6 @@
9796
import static org.springdoc.core.utils.SpringDocAnnotationsUtils.extractSchema;
9897
import static org.springdoc.core.utils.SpringDocAnnotationsUtils.getContent;
9998
import static org.springdoc.core.utils.SpringDocAnnotationsUtils.mergeSchema;
100-
import static org.springdoc.core.utils.SpringDocUtils.cloneViaJson;
10199
import static org.springdoc.core.utils.SpringDocUtils.getParameterAnnotations;
102100

103101
/**
@@ -112,12 +110,7 @@ public class GenericResponseService implements ApplicationContextAware {
112110
* the exception classes.
113111
*/
114112
private static final String EXTENSION_EXCEPTION_CLASSES = "x-exception-class";
115-
116-
/**
117-
* The constant LOGGER.
118-
*/
119-
private static final Logger LOGGER = LoggerFactory.getLogger(GenericResponseService.class);
120-
113+
121114
/**
122115
* The Response entity exception handler class.
123116
*/
@@ -180,22 +173,22 @@ public GenericResponseService(OperationService operationService,
180173
* @param components the components
181174
* @param apiResponsesOp the api responses op
182175
* @param methodAttributes the method attributes
183-
* @param apiResponseAnnotations the api response annotations
176+
* @param apiResponseAnnotation the api response annotation
184177
* @param apiResponse the api response
185178
* @param openapi31 the openapi 31
186179
*/
187180
public static void buildContentFromDoc(Components components, ApiResponses apiResponsesOp,
188181
MethodAttributes methodAttributes,
189-
io.swagger.v3.oas.annotations.responses.ApiResponse apiResponseAnnotations,
182+
io.swagger.v3.oas.annotations.responses.ApiResponse apiResponseAnnotation,
190183
ApiResponse apiResponse, boolean openapi31) {
191184

192-
methodAttributes.setUseReturnTypeSchema(apiResponseAnnotations.useReturnTypeSchema());
193-
io.swagger.v3.oas.annotations.media.Content[] contentdoc = apiResponseAnnotations.content();
185+
methodAttributes.putUseReturnTypeSchema(apiResponseAnnotation.responseCode(), apiResponseAnnotation.useReturnTypeSchema());
186+
io.swagger.v3.oas.annotations.media.Content[] contentdoc = apiResponseAnnotation.content();
194187
Optional<Content> optionalContent = getContent(contentdoc, new String[0],
195188
methodAttributes.getMethodProduces(), null, components, methodAttributes.getJsonViewAnnotation(), openapi31);
196-
if (apiResponsesOp.containsKey(apiResponseAnnotations.responseCode())) {
189+
if (apiResponsesOp.containsKey(apiResponseAnnotation.responseCode())) {
197190
// Merge with the existing content
198-
Content existingContent = apiResponsesOp.get(apiResponseAnnotations.responseCode()).getContent();
191+
Content existingContent = apiResponsesOp.get(apiResponseAnnotation.responseCode()).getContent();
199192
if (optionalContent.isPresent()) {
200193
Content newContent = optionalContent.get();
201194
if (methodAttributes.isMethodOverloaded() && existingContent != null) {
@@ -387,17 +380,17 @@ private Map<String, ApiResponse> computeResponseFromDoc(Components components, M
387380
Set<io.swagger.v3.oas.annotations.responses.ApiResponse> responsesArray = getApiResponses(Objects.requireNonNull(methodParameter.getMethod()));
388381
if (!responsesArray.isEmpty()) {
389382
methodAttributes.setWithApiResponseDoc(true);
390-
for (io.swagger.v3.oas.annotations.responses.ApiResponse apiResponseAnnotations : responsesArray) {
391-
String httpCode = apiResponseAnnotations.responseCode();
383+
for (io.swagger.v3.oas.annotations.responses.ApiResponse apiResponseAnnotation : responsesArray) {
384+
String httpCode = apiResponseAnnotation.responseCode();
392385
ApiResponse apiResponse = new ApiResponse();
393-
if (StringUtils.isNotBlank(apiResponseAnnotations.ref())) {
394-
apiResponse.$ref(apiResponseAnnotations.ref());
395-
apiResponsesOp.addApiResponse(apiResponseAnnotations.responseCode(), apiResponse);
386+
if (StringUtils.isNotBlank(apiResponseAnnotation.ref())) {
387+
apiResponse.$ref(apiResponseAnnotation.ref());
388+
apiResponsesOp.addApiResponse(apiResponseAnnotation.responseCode(), apiResponse);
396389
continue;
397390
}
398-
apiResponse.setDescription(propertyResolverUtils.resolve(apiResponseAnnotations.description(), methodAttributes.getLocale()));
399-
buildContentFromDoc(components, apiResponsesOp, methodAttributes, apiResponseAnnotations, apiResponse, openapi31);
400-
Map<String, Object> extensions = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), apiResponseAnnotations.extensions());
391+
apiResponse.setDescription(propertyResolverUtils.resolve(apiResponseAnnotation.description(), methodAttributes.getLocale()));
392+
buildContentFromDoc(components, apiResponsesOp, methodAttributes, apiResponseAnnotation, apiResponse, openapi31);
393+
Map<String, Object> extensions = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), apiResponseAnnotation.extensions());
401394
if (!CollectionUtils.isEmpty(extensions)) {
402395
if (propertyResolverUtils.isResolveExtensionsProperties()) {
403396
Map<String, Object> extensionsResolved = propertyResolverUtils.resolveExtensions(locale, extensions);
@@ -407,7 +400,7 @@ private Map<String, ApiResponse> computeResponseFromDoc(Components components, M
407400
apiResponse.extensions(extensions);
408401
}
409402
}
410-
SpringDocAnnotationsUtils.getHeaders(apiResponseAnnotations.headers(), components, methodAttributes.getJsonViewAnnotation(), openapi31)
403+
SpringDocAnnotationsUtils.getHeaders(apiResponseAnnotation.headers(), components, methodAttributes.getJsonViewAnnotation(), openapi31)
411404
.ifPresent(apiResponse::headers);
412405
apiResponsesOp.addApiResponse(httpCode, apiResponse);
413406
}
@@ -622,8 +615,7 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent()))
622615
setDescription(httpCode, apiResponse);
623616
}
624617
}
625-
if (apiResponse.getContent() != null && (methodAttributes.isUseReturnTypeSchema() ||
626-
((isGeneric || methodAttributes.isMethodOverloaded()) && methodAttributes.isNoApiResponseDoc()))) {
618+
if (apiResponse.getContent() != null && shouldCalculateContent(methodAttributes, isGeneric, httpCode)) {
627619
// Merge with existing schema
628620
Content existingContent = apiResponse.getContent();
629621
Type type = GenericTypeResolver.resolveType(methodParameter.getGenericParameterType(), methodParameter.getContainingClass());
@@ -642,6 +634,28 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent()))
642634
apiResponsesOp.addApiResponse(httpCode, apiResponse);
643635
}
644636

637+
/**
638+
* Whether to consider calculating additional content.
639+
*
640+
* @param methodAttributes the method attributes
641+
* @param isGeneric the is generic
642+
* @param httpCode the http code
643+
*/
644+
private boolean shouldCalculateContent(MethodAttributes methodAttributes, boolean isGeneric, String httpCode) {
645+
return useReturnTypeSchema(methodAttributes, httpCode) ||
646+
((isGeneric || methodAttributes.isMethodOverloaded()) && methodAttributes.isNoApiResponseDoc());
647+
}
648+
649+
/**
650+
* Whether to use return type schema.
651+
*
652+
* @param methodAttributes the method attributes
653+
* @param httpCode the http code
654+
*/
655+
private boolean useReturnTypeSchema(MethodAttributes methodAttributes, String httpCode) {
656+
return methodAttributes.getUseReturnTypeSchema().getOrDefault(httpCode, false);
657+
}
658+
645659
/**
646660
* Evaluate response status string.
647661
*

springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app226/HelloController.java

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
import io.swagger.v3.oas.annotations.media.Content;
66
import io.swagger.v3.oas.annotations.media.ExampleObject;
7+
import io.swagger.v3.oas.annotations.media.Schema;
78
import io.swagger.v3.oas.annotations.responses.ApiResponse;
89

10+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
911
import org.springframework.web.bind.annotation.PostMapping;
1012
import org.springframework.web.bind.annotation.RequestMapping;
1113
import org.springframework.web.bind.annotation.RestController;
@@ -17,20 +19,35 @@
1719
@RequestMapping
1820
public class HelloController {
1921

22+
public record Error(String message) {
23+
24+
}
25+
2026
@PostMapping("/testBoolean")
21-
@ApiResponse(
22-
useReturnTypeSchema = true,
23-
responseCode = "200",
24-
description = "OK",
25-
content = {
26-
@Content(
27-
mediaType = "*/*",
28-
examples =
29-
@ExampleObject(
30-
name = "success",
31-
value = "..."))
32-
}
33-
)
27+
@ApiResponses(value = {
28+
@ApiResponse(
29+
useReturnTypeSchema = true,
30+
responseCode = "200",
31+
description = "OK",
32+
content = {
33+
@Content(
34+
mediaType = "*/*",
35+
examples =
36+
@ExampleObject(
37+
name = "success",
38+
value = "..."))
39+
}
40+
),
41+
@ApiResponse(
42+
responseCode = "400",
43+
description = "OK",
44+
content = {
45+
@Content(
46+
mediaType = "*/*",
47+
schema = @Schema(implementation = Error.class))
48+
}
49+
)
50+
})
3451
public Map<String, String> HelloController() {
3552
return null;
3653
}

springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app226/HelloController.java

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
import io.swagger.v3.oas.annotations.media.Content;
66
import io.swagger.v3.oas.annotations.media.ExampleObject;
7+
import io.swagger.v3.oas.annotations.media.Schema;
78
import io.swagger.v3.oas.annotations.responses.ApiResponse;
89

10+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
911
import org.springframework.web.bind.annotation.PostMapping;
1012
import org.springframework.web.bind.annotation.RequestMapping;
1113
import org.springframework.web.bind.annotation.RestController;
@@ -17,20 +19,35 @@
1719
@RequestMapping
1820
public class HelloController {
1921

22+
public record Error(String message) {
23+
24+
}
25+
2026
@PostMapping("/testBoolean")
21-
@ApiResponse(
22-
useReturnTypeSchema = true,
23-
responseCode = "200",
24-
description = "OK",
25-
content = {
26-
@Content(
27-
mediaType = "*/*",
28-
examples =
29-
@ExampleObject(
30-
name = "success",
31-
value = "..."))
32-
}
33-
)
27+
@ApiResponses(value = {
28+
@ApiResponse(
29+
useReturnTypeSchema = true,
30+
responseCode = "200",
31+
description = "OK",
32+
content = {
33+
@Content(
34+
mediaType = "*/*",
35+
examples =
36+
@ExampleObject(
37+
name = "success",
38+
value = "..."))
39+
}
40+
),
41+
@ApiResponse(
42+
responseCode = "400",
43+
description = "OK",
44+
content = {
45+
@Content(
46+
mediaType = "*/*",
47+
schema = @Schema(implementation = Error.class))
48+
}
49+
)
50+
})
3451
public Map<String, String> HelloController() {
3552
return null;
3653
}

springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app226.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,31 @@
3636
}
3737
}
3838
}
39+
},
40+
"400" : {
41+
"description" : "OK",
42+
"content" : {
43+
"*/*" : {
44+
"schema" : {
45+
"$ref" : "#/components/schemas/Error"
46+
}
47+
}
48+
}
3949
}
4050
}
4151
}
4252
}
4353
},
44-
"components": {}
54+
"components": {
55+
"schemas" : {
56+
"Error" : {
57+
"type" : "object",
58+
"properties" : {
59+
"message" : {
60+
"type" : "string"
61+
}
62+
}
63+
}
64+
}
65+
}
4566
}

0 commit comments

Comments
 (0)