Skip to content

Commit cf12547

Browse files
committed
Content-type for POST endpoints with multipart/form-data does not work since v2.4.0. Fixes #2621
1 parent 6c24eb6 commit cf12547

File tree

6 files changed

+188
-20
lines changed

6 files changed

+188
-20
lines changed

Diff for: springdoc-openapi-starter-common/src/main/java/org/springdoc/core/data/DataRestRequestService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ else if (methodParameter.getParameterAnnotation(BackendId.class) != null) {
175175
parameterInfo.setParameterModel(parameter);
176176
}
177177
if (!ArrayUtils.isEmpty(methodParameter.getParameterAnnotations()))
178-
parameter = requestBuilder.buildParams(parameterInfo, openAPI.getComponents(), requestMethod, null,
178+
parameter = requestBuilder.buildParams(parameterInfo, openAPI.getComponents(), requestMethod, methodAttributes,
179179
openAPI.getOpenapi());
180180
addParameters(openAPI, requestMethod, methodAttributes, operation, methodParameter, parameterInfo, parameter);
181181
}

Diff for: springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java

+22-19
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package org.springdoc.core.service;
2626

2727
import java.lang.annotation.Annotation;
28+
import java.lang.reflect.Field;
2829
import java.lang.reflect.Method;
2930
import java.math.BigDecimal;
3031
import java.util.ArrayList;
@@ -88,12 +89,14 @@
8889
import org.springframework.web.context.request.NativeWebRequest;
8990
import org.springframework.web.context.request.WebRequest;
9091
import org.springframework.web.method.HandlerMethod;
92+
import org.springframework.web.multipart.MultipartFile;
9193
import org.springframework.web.util.UriComponentsBuilder;
9294

9395
import static org.springdoc.core.converters.SchemaPropertyDeprecatingConverter.containsDeprecatedAnnotation;
9496
import static org.springdoc.core.service.GenericParameterService.isFile;
9597
import static org.springdoc.core.utils.Constants.OPENAPI_ARRAY_TYPE;
9698
import static org.springdoc.core.utils.Constants.OPENAPI_STRING_TYPE;
99+
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
97100

98101
/**
99102
* The type Abstract request builder.
@@ -323,7 +326,7 @@ public Operation build(HandlerMethod handlerMethod, RequestMethod requestMethod,
323326
}
324327

325328
if (!isParamToIgnore(methodParameter)) {
326-
parameter = buildParams(parameterInfo, components, requestMethod, methodAttributes.getJsonViewAnnotation(), openAPI.getOpenapi());
329+
parameter = buildParams(parameterInfo, components, requestMethod, methodAttributes, openAPI.getOpenapi());
327330
// Merge with the operation parameters
328331
parameter = GenericParameterService.mergeParameter(operationParameters, parameter);
329332
List<Annotation> parameterAnnotations = Arrays.asList(methodParameter.getParameterAnnotations());
@@ -353,7 +356,7 @@ else if (!RequestMethod.GET.equals(requestMethod) || OpenApiVersion.OPENAPI_3_1.
353356
// support form-data
354357
if (defaultSupportFormData && requestBody != null
355358
&& requestBody.getContent() != null
356-
&& requestBody.getContent().containsKey(org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE)) {
359+
&& requestBody.getContent().containsKey(MULTIPART_FORM_DATA_VALUE)) {
357360
Iterator<Entry<ParameterId, Parameter>> it = map.entrySet().iterator();
358361
while (it.hasNext()) {
359362
Entry<ParameterId, Parameter> entry = it.next();
@@ -496,28 +499,28 @@ public boolean isValidParameter(Parameter parameter) {
496499
/**
497500
* Build params parameter.
498501
*
499-
* @param parameterInfo the parameter info
500-
* @param components the components
501-
* @param requestMethod the request method
502-
* @param jsonView the json view
503-
* @param openApiVersion the open api version
502+
* @param parameterInfo the parameter info
503+
* @param components the components
504+
* @param requestMethod the request method
505+
* @param methodAttributes the method attributes
506+
* @param openApiVersion the open api version
504507
* @return the parameter
505508
*/
506509
public Parameter buildParams(ParameterInfo parameterInfo, Components components,
507-
RequestMethod requestMethod, JsonView jsonView, String openApiVersion) {
510+
RequestMethod requestMethod, MethodAttributes methodAttributes, String openApiVersion) {
508511
MethodParameter methodParameter = parameterInfo.getMethodParameter();
509512
if (parameterInfo.getParamType() != null) {
510513
if (!ValueConstants.DEFAULT_NONE.equals(parameterInfo.getDefaultValue()))
511514
parameterInfo.setRequired(false);
512515
else
513516
parameterInfo.setDefaultValue(null);
514-
return this.buildParam(parameterInfo, components, jsonView);
517+
return this.buildParam(parameterInfo, components, methodAttributes.getJsonViewAnnotation());
515518
}
516519
// By default
517-
if (!isRequestBodyParam(requestMethod, parameterInfo, openApiVersion)) {
520+
if (!isRequestBodyParam(requestMethod, parameterInfo, openApiVersion, methodAttributes)) {
518521
parameterInfo.setRequired(!((DelegatingMethodParameter) methodParameter).isNotRequired() && !methodParameter.isOptional());
519522
parameterInfo.setDefaultValue(null);
520-
return this.buildParam(parameterInfo, components, jsonView);
523+
return this.buildParam(parameterInfo, components, methodAttributes.getJsonViewAnnotation());
521524
}
522525
return null;
523526
}
@@ -631,7 +634,7 @@ public RequestBodyService getRequestBodyBuilder() {
631634
public boolean isDefaultFlatParamObject() {
632635
return defaultFlatParamObject;
633636
}
634-
637+
635638
/**
636639
* Calculate size.
637640
*
@@ -722,12 +725,13 @@ private void applyValidationsToSchema(Map<String, Annotation> annos, Schema<?> s
722725
/**
723726
* Is RequestBody param boolean.
724727
*
725-
* @param requestMethod the request method
726-
* @param parameterInfo the parameter info
727-
* @param openApiVersion the open api version
728+
* @param requestMethod the request method
729+
* @param parameterInfo the parameter info
730+
* @param openApiVersion the open api version
731+
* @param methodAttributes the method attributes
728732
* @return the boolean
729733
*/
730-
private boolean isRequestBodyParam(RequestMethod requestMethod, ParameterInfo parameterInfo, String openApiVersion) {
734+
private boolean isRequestBodyParam(RequestMethod requestMethod, ParameterInfo parameterInfo, String openApiVersion, MethodAttributes methodAttributes) {
731735
MethodParameter methodParameter = parameterInfo.getMethodParameter();
732736
DelegatingMethodParameter delegatingMethodParameter = (DelegatingMethodParameter) methodParameter;
733737
boolean isBodyAllowed = !RequestMethod.GET.equals(requestMethod) || OpenApiVersion.OPENAPI_3_1.getVersion().equals(openApiVersion);
@@ -739,8 +743,7 @@ private boolean isRequestBodyParam(RequestMethod requestMethod, ParameterInfo pa
739743
|| AnnotatedElementUtils.findMergedAnnotation(Objects.requireNonNull(methodParameter.getMethod()), io.swagger.v3.oas.annotations.parameters.RequestBody.class) != null)
740744
|| checkOperationRequestBody(methodParameter)
741745
|| checkFile(methodParameter)
742-
743-
);
746+
|| Arrays.asList(methodAttributes.getMethodConsumes()).contains(MULTIPART_FORM_DATA_VALUE));
744747
}
745748

746749
/**
@@ -767,7 +770,7 @@ else if (methodParameter.getParameterAnnotation(org.springframework.web.bind.ann
767770
private boolean checkOperationRequestBody(MethodParameter methodParameter) {
768771
if (AnnotatedElementUtils.findMergedAnnotation(Objects.requireNonNull(methodParameter.getMethod()), io.swagger.v3.oas.annotations.Operation.class) != null) {
769772
io.swagger.v3.oas.annotations.Operation operation = AnnotatedElementUtils.findMergedAnnotation(Objects.requireNonNull(methodParameter.getMethod()), io.swagger.v3.oas.annotations.Operation.class);
770-
if(operation!=null){
773+
if (operation != null) {
771774
io.swagger.v3.oas.annotations.parameters.RequestBody requestBody = operation.requestBody();
772775
if (StringUtils.isNotBlank(requestBody.description()))
773776
return true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package test.org.springdoc.api.v30.app221;
2+
3+
import java.io.IOException;
4+
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
7+
import test.org.springdoc.api.v30.app221.HomeController.HelloDto;
8+
import test.org.springdoc.api.v30.app221.HomeController.HelloUploadDto;
9+
10+
import org.springframework.web.bind.annotation.PostMapping;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
13+
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
14+
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
15+
16+
@Tag(name = "Hello World Api", description = "This is a test api")
17+
@RequestMapping("api/hello")
18+
public interface HomeApi {
19+
20+
@Operation(summary = "Upload new content", description = "Upload test content")
21+
@PostMapping(produces = APPLICATION_JSON_VALUE, consumes = MULTIPART_FORM_DATA_VALUE)
22+
HelloDto uploadContent(HelloUploadDto contentUploadDto) throws IOException;
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package test.org.springdoc.api.v30.app221;
2+
3+
import java.io.IOException;
4+
5+
import jakarta.validation.Valid;
6+
import jakarta.validation.constraints.NotNull;
7+
8+
import org.springframework.web.bind.annotation.RestController;
9+
import org.springframework.web.multipart.MultipartFile;
10+
11+
@RestController
12+
public class HomeController implements HomeApi {
13+
14+
@Override
15+
public HelloDto uploadContent(@Valid HelloUploadDto uploadDto)
16+
throws IOException {
17+
var fileBytes = uploadDto.file.getBytes();
18+
return new HelloDto(uploadDto.title, fileBytes);
19+
}
20+
21+
public record HelloDto(String title, byte[] file) {}
22+
23+
public record HelloUploadDto(@NotNull String title, MultipartFile file) {}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2022 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v30.app221;
26+
27+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
28+
29+
import org.springframework.boot.autoconfigure.SpringBootApplication;
30+
31+
public class SpringDocApp221Test extends AbstractSpringDocV30Test {
32+
33+
@SpringBootApplication
34+
static class SpringDocTestApp {}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
"tags": [
14+
{
15+
"name": "Hello World Api",
16+
"description": "This is a test api"
17+
}
18+
],
19+
"paths": {
20+
"/api/hello": {
21+
"post": {
22+
"tags": [
23+
"Hello World Api"
24+
],
25+
"summary": "Upload new content",
26+
"description": "Upload test content",
27+
"operationId": "uploadContent",
28+
"requestBody": {
29+
"content": {
30+
"multipart/form-data": {
31+
"schema": {
32+
"$ref": "#/components/schemas/HelloUploadDto"
33+
}
34+
}
35+
}
36+
},
37+
"responses": {
38+
"200": {
39+
"description": "OK",
40+
"content": {
41+
"application/json": {
42+
"schema": {
43+
"$ref": "#/components/schemas/HelloDto"
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
},
52+
"components": {
53+
"schemas": {
54+
"HelloUploadDto": {
55+
"required": [
56+
"title"
57+
],
58+
"type": "object",
59+
"properties": {
60+
"title": {
61+
"type": "string"
62+
},
63+
"file": {
64+
"type": "string",
65+
"format": "binary"
66+
}
67+
}
68+
},
69+
"HelloDto": {
70+
"type": "object",
71+
"properties": {
72+
"title": {
73+
"type": "string"
74+
},
75+
"file": {
76+
"type": "string",
77+
"format": "byte"
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)