Skip to content

Commit 9de0b8c

Browse files
committed
Merge branch 'Mattias-Sehlstedt-method-media-type-override-class-media-type'
2 parents b95fed0 + 2af2bc5 commit 9de0b8c

File tree

7 files changed

+454
-83
lines changed

7 files changed

+454
-83
lines changed

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,11 @@ public void calculateConsumesProduces(Method method) {
270270
RequestMapping reqMappingClass = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), RequestMapping.class);
271271

272272
if (reqMappingMethod != null && reqMappingClass != null) {
273-
fillMethods(ArrayUtils.addAll(reqMappingMethod.produces(), reqMappingClass.produces()), ArrayUtils.addAll(reqMappingMethod.consumes(), reqMappingClass.consumes()), reqMappingMethod.headers());
273+
fillMethods(
274+
calculateMethodMediaTypes(reqMappingMethod.produces(), reqMappingClass.produces()),
275+
calculateMethodMediaTypes(reqMappingMethod.consumes(), reqMappingClass.consumes()),
276+
reqMappingMethod.headers()
277+
);
274278
}
275279
else if (reqMappingMethod != null) {
276280
fillMethods(reqMappingMethod.produces(), reqMappingMethod.consumes(), reqMappingMethod.headers());
@@ -313,6 +317,21 @@ else if (ArrayUtils.isEmpty(methodConsumes)) {
313317
setHeaders(headers);
314318
}
315319

320+
/**
321+
* If there is any method type(s) present, then these will override the class type(s).
322+
* See <a href="https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-consumes">...</a> for details
323+
*
324+
* @param methodTypes the method types
325+
* @param classTypes the class types
326+
* @return the string [ ] containing the types that can be used for the method
327+
*/
328+
private String[] calculateMethodMediaTypes(@Nullable String[] methodTypes, String[] classTypes) {
329+
if (ArrayUtils.isNotEmpty(methodTypes)) {
330+
return methodTypes;
331+
}
332+
return classTypes;
333+
}
334+
316335
/**
317336
* Merge string arrays into one array with unique values
318337
*
@@ -478,7 +497,7 @@ public void setWithResponseBodySchemaDoc(boolean withResponseBodySchemaDoc) {
478497
public void calculateHeadersForClass(Class<?> declaringClass) {
479498
RequestMapping reqMappingClass = AnnotatedElementUtils.findMergedAnnotation(declaringClass, RequestMapping.class);
480499
if (reqMappingClass != null) {
481-
fillMethods(reqMappingClass.produces(), reqMappingClass.consumes(), reqMappingClass.headers());
500+
setHeaders(reqMappingClass.headers());
482501
}
483502
}
484503

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public Optional<RequestBody> buildRequestBodyFromDoc(
113113
}
114114

115115
if (requestBody.required()) {
116-
requestBodyObject.setRequired(requestBody.required());
116+
requestBodyObject.setRequired(true);
117117
isEmpty = false;
118118
}
119119
if (requestBody.extensions().length > 0) {
@@ -128,7 +128,7 @@ public Optional<RequestBody> buildRequestBodyFromDoc(
128128
if (isEmpty)
129129
return Optional.empty();
130130

131-
buildResquestBodyContent(requestBody, requestBodyOp, methodAttributes, components, jsonViewAnnotation, classConsumes, methodConsumes, requestBodyObject);
131+
buildRequestBodyContent(requestBody, requestBodyOp, methodAttributes, components, jsonViewAnnotation, classConsumes, methodConsumes, requestBodyObject);
132132

133133
return Optional.of(requestBodyObject);
134134
}
@@ -145,7 +145,10 @@ public Optional<RequestBody> buildRequestBodyFromDoc(
145145
* @param methodConsumes the method consumes
146146
* @param requestBodyObject the request body object
147147
*/
148-
private void buildResquestBodyContent(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes, Components components, JsonView jsonViewAnnotation, String[] classConsumes, String[] methodConsumes, RequestBody requestBodyObject) {
148+
private void buildRequestBodyContent(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody,
149+
RequestBody requestBodyOp, MethodAttributes methodAttributes,
150+
Components components, JsonView jsonViewAnnotation, String[] classConsumes,
151+
String[] methodConsumes, RequestBody requestBodyObject) {
149152
Optional<Content> optionalContent = SpringDocAnnotationsUtils
150153
.getContent(requestBody.content(), getConsumes(classConsumes),
151154
getConsumes(methodConsumes), null, components, jsonViewAnnotation, parameterBuilder.isOpenapi31());
Lines changed: 212 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,228 @@
11
package org.springdoc.core.model;
22

3-
import java.lang.reflect.Method;
4-
import java.util.Locale;
5-
63
import org.junit.jupiter.api.Test;
4+
import org.mockito.MockedStatic;
5+
import org.mockito.Mockito;
76
import org.springdoc.core.models.MethodAttributes;
7+
import org.springframework.core.annotation.AnnotatedElementUtils;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
10+
import java.lang.reflect.Method;
11+
import java.util.Locale;
812

913
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
14+
import static org.mockito.BDDMockito.given;
1015

1116
public class MethodAttributesTest {
1217

13-
@Test
14-
public void testMergeArrays() throws Exception {
15-
MethodAttributes methodAttributes = new MethodAttributes("application/json", "application/xml", Locale.ENGLISH);
16-
17-
String[] array1 = { "application/json", "application/xml" };
18-
String[] array2 = { "application/xml", "application/yaml" };
19-
20-
String[] expected = { "application/json", "application/xml", "application/yaml" };
21-
22-
Method mergeArraysMethod = MethodAttributes.class.getDeclaredMethod("mergeArrays", String[].class, String[].class);
23-
mergeArraysMethod.setAccessible(true);
24-
String[] result = (String[]) mergeArraysMethod.invoke(methodAttributes, (Object) array1, (Object) array2);
25-
26-
assertArrayEquals(expected, result);
27-
}
28-
29-
@Test
30-
public void testMergeArraysWithNullArray1() throws Exception {
31-
MethodAttributes methodAttributes = new MethodAttributes("application/json", "application/xml", Locale.ENGLISH);
32-
33-
String[] array1 = null;
34-
String[] array2 = { "application/xml", "application/yaml" };
35-
36-
String[] expected = { "application/xml", "application/yaml" };
37-
38-
Method mergeArraysMethod = MethodAttributes.class.getDeclaredMethod("mergeArrays", String[].class, String[].class);
39-
mergeArraysMethod.setAccessible(true);
40-
String[] result = (String[]) mergeArraysMethod.invoke(methodAttributes, (Object) array1, (Object) array2);
41-
42-
assertArrayEquals(expected, result);
43-
}
44-
45-
@Test
46-
public void testDefaultProducesMediaType() {
47-
MethodAttributes methodAttributes = new MethodAttributes("application/json", "application/xml", Locale.ENGLISH);
48-
49-
Method method = this.getClass().getDeclaredMethods()[0];
50-
methodAttributes.calculateConsumesProduces(method);
18+
private static final String APPLICATION_JSON = "application/json";
19+
private static final String APPLICATION_XML = "application/xml";
20+
private static final String APPLICATION_YAML = "application/yaml";
5121

52-
String[] expectedProduces = { "application/xml" };
53-
String[] resultProduces = methodAttributes.getMethodProduces();
22+
@Test
23+
void testMergeArrays() throws Exception {
24+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
5425

55-
assertArrayEquals(expectedProduces, resultProduces);
56-
}
26+
String[] array1 = {APPLICATION_JSON, APPLICATION_XML};
27+
String[] array2 = {APPLICATION_XML, APPLICATION_YAML};
5728

58-
@Test
59-
public void testDefaultConsumesMediaType() {
60-
MethodAttributes methodAttributes = new MethodAttributes("application/json", "application/xml", Locale.ENGLISH);
29+
String[] expected = {APPLICATION_JSON, APPLICATION_XML, APPLICATION_YAML};
6130

62-
Method method = this.getClass().getDeclaredMethods()[0];
63-
methodAttributes.calculateConsumesProduces(method);
31+
Method mergeArraysMethod = MethodAttributes.class.getDeclaredMethod("mergeArrays", String[].class, String[].class);
32+
mergeArraysMethod.setAccessible(true);
33+
String[] result = (String[]) mergeArraysMethod.invoke(methodAttributes, (Object) array1, (Object) array2);
34+
35+
assertArrayEquals(expected, result);
36+
}
37+
38+
@Test
39+
void testMergeArraysWithNullArray1() throws Exception {
40+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
6441

65-
String[] expectedConsumes = { "application/json" };
66-
String[] resultConsumes = methodAttributes.getMethodConsumes();
42+
String[] array1 = null;
43+
String[] array2 = {APPLICATION_XML, APPLICATION_YAML};
44+
45+
String[] expected = {APPLICATION_XML, APPLICATION_YAML};
6746

68-
assertArrayEquals(expectedConsumes, resultConsumes);
69-
}
47+
Method mergeArraysMethod = MethodAttributes.class.getDeclaredMethod("mergeArrays", String[].class, String[].class);
48+
mergeArraysMethod.setAccessible(true);
49+
String[] result = (String[]) mergeArraysMethod.invoke(methodAttributes, (Object) array1, (Object) array2);
50+
51+
assertArrayEquals(expected, result);
52+
}
53+
54+
@Test
55+
void testDefaultProducesMediaType() {
56+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
57+
58+
Method method = this.getClass().getDeclaredMethods()[0];
59+
methodAttributes.calculateConsumesProduces(method);
60+
61+
String[] expectedProduces = {APPLICATION_XML};
62+
String[] resultProduces = methodAttributes.getMethodProduces();
63+
64+
assertArrayEquals(expectedProduces, resultProduces);
65+
}
66+
67+
@Test
68+
void testDefaultConsumesMediaType() {
69+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
70+
71+
Method method = this.getClass().getDeclaredMethods()[0];
72+
methodAttributes.calculateConsumesProduces(method);
73+
74+
String[] expectedConsumes = {APPLICATION_JSON};
75+
String[] resultConsumes = methodAttributes.getMethodConsumes();
76+
77+
assertArrayEquals(expectedConsumes, resultConsumes);
78+
}
79+
80+
@Test
81+
void methodConsumesOverridesClassConsumes() {
82+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
83+
RequestMapping requestMapping = givenAnnotationHasMediaTypeAnnotations(
84+
new String[]{APPLICATION_JSON, APPLICATION_XML},
85+
new String[]{APPLICATION_JSON, APPLICATION_XML}
86+
);
87+
Method method = this.getClass().getDeclaredMethods()[0];
88+
try (MockedStatic<AnnotatedElementUtils> annotatedElementUtils = Mockito.mockStatic(AnnotatedElementUtils.class)) {
89+
annotatedElementUtils.when(() -> AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class))
90+
.thenReturn(requestMapping);
91+
92+
methodAttributes.setClassConsumes(new String[]{APPLICATION_YAML});
93+
methodAttributes.calculateConsumesProduces(method);
94+
95+
String[] expectedConsumes = {APPLICATION_JSON, APPLICATION_XML};
96+
String[] resultConsumes = methodAttributes.getMethodConsumes();
97+
98+
assertArrayEquals(expectedConsumes, resultConsumes);
99+
}
100+
}
101+
102+
@Test
103+
void methodProducesOverridesClassProduces() {
104+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
105+
RequestMapping requestMapping = givenAnnotationHasMediaTypeAnnotations(
106+
new String[]{APPLICATION_JSON, APPLICATION_XML},
107+
new String[]{APPLICATION_JSON, APPLICATION_XML}
108+
);
109+
Method method = this.getClass().getDeclaredMethods()[0];
110+
try (MockedStatic<AnnotatedElementUtils> annotatedElementUtils = Mockito.mockStatic(AnnotatedElementUtils.class)) {
111+
annotatedElementUtils.when(() -> AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class))
112+
.thenReturn(requestMapping);
113+
114+
methodAttributes.setClassProduces(new String[]{APPLICATION_YAML});
115+
methodAttributes.calculateConsumesProduces(method);
116+
117+
String[] expectedProduces = {APPLICATION_JSON, APPLICATION_XML};
118+
String[] resultProduces = methodAttributes.getMethodProduces();
119+
120+
assertArrayEquals(expectedProduces, resultProduces);
121+
}
122+
}
123+
124+
@Test
125+
void methodConsumesIsSetToClassConsumesIfNoMethodConsumesIsDefined() {
126+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
127+
RequestMapping requestMapping = givenAnnotationHasMediaTypeAnnotations(
128+
new String[]{APPLICATION_JSON, APPLICATION_XML},
129+
new String[]{}
130+
);
131+
Method method = this.getClass().getDeclaredMethods()[0];
132+
try (MockedStatic<AnnotatedElementUtils> annotatedElementUtils = Mockito.mockStatic(AnnotatedElementUtils.class)) {
133+
annotatedElementUtils.when(() -> AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class))
134+
.thenReturn(requestMapping);
135+
136+
String[] classConsumes = new String[]{APPLICATION_YAML};
137+
methodAttributes.setClassConsumes(classConsumes);
138+
methodAttributes.calculateConsumesProduces(method);
139+
140+
String[] resultConsumes = methodAttributes.getMethodConsumes();
141+
142+
assertArrayEquals(classConsumes, resultConsumes);
143+
}
144+
}
145+
146+
@Test
147+
void methodProducesIsSetToClassProducesIfNoMethodProducesIsDefined() {
148+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
149+
RequestMapping requestMapping = givenAnnotationHasMediaTypeAnnotations(
150+
new String[]{},
151+
new String[]{APPLICATION_JSON, APPLICATION_XML}
152+
);
153+
Method method = this.getClass().getDeclaredMethods()[0];
154+
try (MockedStatic<AnnotatedElementUtils> annotatedElementUtils = Mockito.mockStatic(AnnotatedElementUtils.class)) {
155+
annotatedElementUtils.when(() -> AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class))
156+
.thenReturn(requestMapping);
157+
158+
String[] classProduces = new String[]{APPLICATION_YAML};
159+
methodAttributes.setClassProduces(classProduces);
160+
methodAttributes.calculateConsumesProduces(method);
161+
162+
String[] resultProduces = methodAttributes.getMethodProduces();
163+
164+
assertArrayEquals(classProduces, resultProduces);
165+
}
166+
}
167+
168+
@Test
169+
void methodConsumesIsSetToClassConsumesIfNoMethodConsumesIsDefinedAndClassConsumesNotSet() {
170+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
171+
String[] classConsumes = new String[]{APPLICATION_YAML};
172+
RequestMapping requestMapping = givenAnnotationHasMediaTypeAnnotations(
173+
new String[]{APPLICATION_JSON, APPLICATION_XML},
174+
new String[]{}
175+
);
176+
RequestMapping classMapping = givenAnnotationHasMediaTypeAnnotations(
177+
new String[]{},
178+
classConsumes
179+
);
180+
Method method = this.getClass().getDeclaredMethods()[0];
181+
try (MockedStatic<AnnotatedElementUtils> annotatedElementUtils = Mockito.mockStatic(AnnotatedElementUtils.class)) {
182+
annotatedElementUtils.when(() -> AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class))
183+
.thenReturn(requestMapping);
184+
annotatedElementUtils.when(() -> AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), RequestMapping.class))
185+
.thenReturn(classMapping);
186+
187+
methodAttributes.calculateConsumesProduces(method);
188+
189+
String[] resultConsumes = methodAttributes.getMethodConsumes();
190+
191+
assertArrayEquals(classConsumes, resultConsumes);
192+
}
193+
}
194+
195+
@Test
196+
void methodProducesIsSetToClassProducesIfNoMethodProducesIsDefinedAndClassProducesNotSet() {
197+
MethodAttributes methodAttributes = new MethodAttributes(APPLICATION_JSON, APPLICATION_XML, Locale.ENGLISH);
198+
String[] classProduces = new String[]{APPLICATION_YAML};
199+
RequestMapping requestMapping = givenAnnotationHasMediaTypeAnnotations(
200+
new String[]{},
201+
new String[]{APPLICATION_JSON, APPLICATION_XML}
202+
);
203+
RequestMapping classMapping = givenAnnotationHasMediaTypeAnnotations(
204+
classProduces,
205+
new String[]{}
206+
);
207+
Method method = this.getClass().getDeclaredMethods()[0];
208+
try (MockedStatic<AnnotatedElementUtils> annotatedElementUtils = Mockito.mockStatic(AnnotatedElementUtils.class)) {
209+
annotatedElementUtils.when(() -> AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class))
210+
.thenReturn(requestMapping);
211+
annotatedElementUtils.when(() -> AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), RequestMapping.class))
212+
.thenReturn(classMapping);
213+
214+
methodAttributes.calculateConsumesProduces(method);
215+
216+
String[] resultProduces = methodAttributes.getMethodProduces();
217+
218+
assertArrayEquals(classProduces, resultProduces);
219+
}
220+
}
221+
222+
private RequestMapping givenAnnotationHasMediaTypeAnnotations(String[] produces, String[] consumes) {
223+
RequestMapping requestMapping = Mockito.mock(RequestMapping.class);
224+
given(requestMapping.produces()).willReturn(produces);
225+
given(requestMapping.consumes()).willReturn(consumes);
226+
return requestMapping;
227+
}
70228
}

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,27 @@
3131
import org.springframework.web.bind.annotation.RestController;
3232

3333
@RestController
34-
@RequestMapping(value = "/api", produces = { "application/xml" }, consumes = { "application/json" })
34+
@RequestMapping(value = "/api", produces = {"application/xml"}, consumes = {"application/json"})
3535
public class HelloController {
3636

37-
@RequestMapping(value = "/testpost", method = RequestMethod.POST, produces = { "application/json" },
38-
consumes = { "application/json;charset=UTF-8", "application/json; charset=UTF-8" })
39-
public ResponseEntity<TestObject> testpost(@RequestBody TestObject dto) {
40-
return ResponseEntity.ok(dto);
41-
}
37+
@RequestMapping(value = "/testpost", method = RequestMethod.POST, produces = {"application/json"},
38+
consumes = {"application/json;charset=UTF-8", "application/json; charset=UTF-8"})
39+
public ResponseEntity<TestObject> postWithProducesAndConsumes(@RequestBody TestObject dto) {
40+
return ResponseEntity.ok(dto);
41+
}
42+
43+
@RequestMapping(value = "/testpost2", method = RequestMethod.POST, consumes = {"application/json;charset=UTF-8"})
44+
public ResponseEntity<TestObject> postWithConsumes(@RequestBody TestObject dto) {
45+
return ResponseEntity.ok(dto);
46+
}
47+
48+
@RequestMapping(value = "/testpost3", method = RequestMethod.POST, produces = {"application/json"})
49+
public ResponseEntity<TestObject> postWithProduces(@RequestBody TestObject dto) {
50+
return ResponseEntity.ok(dto);
51+
}
52+
53+
@RequestMapping(value = "/testpost4", method = RequestMethod.POST)
54+
public ResponseEntity<TestObject> post(@RequestBody TestObject dto) {
55+
return ResponseEntity.ok(dto);
56+
}
4257
}

0 commit comments

Comments
 (0)