Skip to content

Commit efd2f74

Browse files
committed
Make it possible to mark parameters with @RequestParam annotation to be sent in form instead of query. Fixes #2826
1 parent 280905b commit efd2f74

File tree

11 files changed

+431
-405
lines changed

11 files changed

+431
-405
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
@@ -226,7 +226,7 @@ private boolean isParamToIgnore(MethodParameter methodParameter) {
226226
private void addParameters(OpenAPI openAPI, RequestMethod requestMethod, MethodAttributes methodAttributes, Operation operation,
227227
MethodParameter methodParameter, ParameterInfo parameterInfo, Parameter parameter) {
228228
List<Annotation> parameterAnnotations = Arrays.asList(getParameterAnnotations(methodParameter));
229-
if (requestBuilder.isValidParameter(parameter)) {
229+
if (requestBuilder.isValidParameter(parameter,methodAttributes)) {
230230
requestBuilder.applyBeanValidatorAnnotations(parameter, parameterAnnotations);
231231
operation.addParametersItem(parameter);
232232
}

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

+39-34
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,15 @@
2626

2727
package org.springdoc.core.service;
2828

29+
import java.io.InputStream;
30+
import java.io.OutputStream;
31+
import java.io.Reader;
32+
import java.io.Writer;
2933
import java.lang.annotation.Annotation;
3034
import java.lang.reflect.Method;
3135
import java.math.BigDecimal;
36+
import java.security.Principal;
37+
import java.time.ZoneId;
3238
import java.util.ArrayList;
3339
import java.util.Arrays;
3440
import java.util.Collection;
@@ -37,22 +43,23 @@
3743
import java.util.Iterator;
3844
import java.util.LinkedHashMap;
3945
import java.util.List;
46+
import java.util.Locale;
4047
import java.util.Map;
4148
import java.util.Map.Entry;
4249
import java.util.Objects;
4350
import java.util.Optional;
4451
import java.util.Set;
52+
import java.util.TimeZone;
4553
import java.util.stream.Collectors;
4654
import java.util.stream.Stream;
4755

4856
import com.fasterxml.jackson.annotation.JsonView;
4957
import io.swagger.v3.core.util.PrimitiveType;
58+
import io.swagger.v3.oas.annotations.Parameters;
5059
import io.swagger.v3.oas.annotations.enums.ParameterIn;
5160
import io.swagger.v3.oas.models.Components;
5261
import io.swagger.v3.oas.models.OpenAPI;
5362
import io.swagger.v3.oas.models.Operation;
54-
import io.swagger.v3.oas.models.media.Content;
55-
import io.swagger.v3.oas.models.media.MediaType;
5663
import io.swagger.v3.oas.models.media.Schema;
5764
import io.swagger.v3.oas.models.media.StringSchema;
5865
import io.swagger.v3.oas.models.parameters.Parameter;
@@ -78,13 +85,16 @@
7885
import org.springframework.core.MethodParameter;
7986
import org.springframework.core.annotation.AnnotatedElementUtils;
8087
import org.springframework.http.HttpMethod;
88+
import org.springframework.ui.Model;
89+
import org.springframework.ui.ModelMap;
8190
import org.springframework.util.CollectionUtils;
8291
import org.springframework.validation.BindingResult;
8392
import org.springframework.validation.Errors;
8493
import org.springframework.web.bind.annotation.PathVariable;
8594
import org.springframework.web.bind.annotation.RequestAttribute;
8695
import org.springframework.web.bind.annotation.RequestMethod;
8796
import org.springframework.web.bind.annotation.RequestParam;
97+
import org.springframework.web.bind.annotation.RequestPart;
8898
import org.springframework.web.bind.annotation.ValueConstants;
8999
import org.springframework.web.bind.support.SessionStatus;
90100
import org.springframework.web.context.request.NativeWebRequest;
@@ -97,6 +107,7 @@
97107
import static org.springdoc.core.utils.Constants.OPENAPI_ARRAY_TYPE;
98108
import static org.springdoc.core.utils.Constants.OPENAPI_STRING_TYPE;
99109
import static org.springdoc.core.utils.SpringDocUtils.getParameterAnnotations;
110+
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
100111
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
101112

102113
/**
@@ -130,18 +141,18 @@ public abstract class AbstractRequestService {
130141
static {
131142
PARAM_TYPES_TO_IGNORE.add(WebRequest.class);
132143
PARAM_TYPES_TO_IGNORE.add(NativeWebRequest.class);
133-
PARAM_TYPES_TO_IGNORE.add(java.security.Principal.class);
144+
PARAM_TYPES_TO_IGNORE.add(Principal.class);
134145
PARAM_TYPES_TO_IGNORE.add(HttpMethod.class);
135-
PARAM_TYPES_TO_IGNORE.add(java.util.Locale.class);
136-
PARAM_TYPES_TO_IGNORE.add(java.util.TimeZone.class);
137-
PARAM_TYPES_TO_IGNORE.add(java.io.InputStream.class);
138-
PARAM_TYPES_TO_IGNORE.add(java.time.ZoneId.class);
139-
PARAM_TYPES_TO_IGNORE.add(java.io.Reader.class);
140-
PARAM_TYPES_TO_IGNORE.add(java.io.OutputStream.class);
141-
PARAM_TYPES_TO_IGNORE.add(java.io.Writer.class);
142-
PARAM_TYPES_TO_IGNORE.add(java.util.Map.class);
143-
PARAM_TYPES_TO_IGNORE.add(org.springframework.ui.Model.class);
144-
PARAM_TYPES_TO_IGNORE.add(org.springframework.ui.ModelMap.class);
146+
PARAM_TYPES_TO_IGNORE.add(Locale.class);
147+
PARAM_TYPES_TO_IGNORE.add(TimeZone.class);
148+
PARAM_TYPES_TO_IGNORE.add(InputStream.class);
149+
PARAM_TYPES_TO_IGNORE.add(ZoneId.class);
150+
PARAM_TYPES_TO_IGNORE.add(Reader.class);
151+
PARAM_TYPES_TO_IGNORE.add(OutputStream.class);
152+
PARAM_TYPES_TO_IGNORE.add(Writer.class);
153+
PARAM_TYPES_TO_IGNORE.add(Map.class);
154+
PARAM_TYPES_TO_IGNORE.add(Model.class);
155+
PARAM_TYPES_TO_IGNORE.add(ModelMap.class);
145156
PARAM_TYPES_TO_IGNORE.add(Errors.class);
146157
PARAM_TYPES_TO_IGNORE.add(BindingResult.class);
147158
PARAM_TYPES_TO_IGNORE.add(SessionStatus.class);
@@ -240,7 +251,7 @@ public static boolean isRequestTypeToIgnore(Class<?> rawClass) {
240251
*/
241252
@SuppressWarnings("unchecked")
242253
public static Collection<Parameter> getHeaders(MethodAttributes methodAttributes, Map<ParameterId, Parameter> map) {
243-
for (Map.Entry<String, String> entry : methodAttributes.getHeaders().entrySet()) {
254+
for (Entry<String, String> entry : methodAttributes.getHeaders().entrySet()) {
244255
StringSchema schema = new StringSchema();
245256
if (StringUtils.isNotEmpty(entry.getValue()))
246257
schema.addEnumItem(entry.getValue());
@@ -291,7 +302,7 @@ public Operation build(HandlerMethod handlerMethod, RequestMethod requestMethod,
291302

292303
for (MethodParameter methodParameter : parameters) {
293304
// check if query param
294-
Parameter parameter;
305+
Parameter parameter = null;
295306
io.swagger.v3.oas.annotations.Parameter parameterDoc = AnnotatedElementUtils.findMergedAnnotation(
296307
AnnotatedElementUtils.forAnnotations(methodParameter.getParameterAnnotations()),
297308
io.swagger.v3.oas.annotations.Parameter.class);
@@ -321,12 +332,10 @@ public Operation build(HandlerMethod handlerMethod, RequestMethod requestMethod,
321332

322333
if (!isParamToIgnore(methodParameter)) {
323334
parameter = buildParams(parameterInfo, components, requestMethod, methodAttributes, openAPI.getOpenapi());
324-
// Merge with the operation parameters
325-
parameter = GenericParameterService.mergeParameter(operationParameters, parameter);
326-
327335
List<Annotation> parameterAnnotations = List.of(getParameterAnnotations(methodParameter));
328-
329-
if (isValidParameter(parameter)) {
336+
if (isValidParameter(parameter,methodAttributes)) {
337+
// Merge with the operation parameters
338+
parameter = GenericParameterService.mergeParameter(operationParameters, parameter);
330339
// Add param javadoc
331340
if (StringUtils.isBlank(parameter.getDescription()) && javadocProvider != null) {
332341
String paramJavadocDescription = parameterBuilder.getParamJavadoc(javadocProvider, methodParameter);
@@ -359,7 +368,7 @@ else if (!RequestMethod.GET.equals(requestMethod) || OpenApiVersion.OPENAPI_3_1.
359368
Parameter parameter = entry.getValue();
360369
if (!ParameterIn.PATH.toString().equals(parameter.getIn()) && !ParameterIn.HEADER.toString().equals(parameter.getIn())
361370
&& !ParameterIn.COOKIE.toString().equals(parameter.getIn())) {
362-
io.swagger.v3.oas.models.media.Schema<?> itemSchema = new io.swagger.v3.oas.models.media.Schema<>();
371+
Schema<?> itemSchema = new Schema<>();
363372
itemSchema.setName(entry.getKey().getpName());
364373
itemSchema.setDescription(parameter.getDescription());
365374
itemSchema.setDeprecated(parameter.getDeprecated());
@@ -388,7 +397,7 @@ private LinkedHashMap<ParameterId, Parameter> getParameterLinkedHashMap(Componen
388397
throw new IllegalStateException(String.format("Duplicate key %s", u));
389398
}, LinkedHashMap::new));
390399

391-
for (Map.Entry<ParameterId, io.swagger.v3.oas.annotations.Parameter> entry : parametersDocMap.entrySet()) {
400+
for (Entry<ParameterId, io.swagger.v3.oas.annotations.Parameter> entry : parametersDocMap.entrySet()) {
392401
ParameterId parameterId = entry.getKey();
393402
if (parameterId != null && !map.containsKey(parameterId) && !entry.getValue().hidden()) {
394403
Parameter parameter = parameterBuilder.buildParameterFromDoc(entry.getValue(), components, methodAttributes.getJsonViewAnnotation(), methodAttributes.getLocale());
@@ -512,11 +521,12 @@ private void setParams(Operation operation, List<Parameter> operationParameters,
512521
/**
513522
* Is valid parameter boolean.
514523
*
515-
* @param parameter the parameter
524+
* @param parameter the parameter
525+
* @param methodAttributes the method attributes
516526
* @return the boolean
517527
*/
518-
public boolean isValidParameter(Parameter parameter) {
519-
return parameter != null && (parameter.getName() != null || parameter.get$ref() != null);
528+
public boolean isValidParameter(Parameter parameter, MethodAttributes methodAttributes ) {
529+
return parameter != null && (parameter.getName() != null || parameter.get$ref() != null) && !(Arrays.asList(methodAttributes.getMethodConsumes()).contains(APPLICATION_FORM_URLENCODED_VALUE) && ParameterIn.QUERY.toString().equals(parameter.getIn()));
520530
}
521531

522532
/**
@@ -640,11 +650,6 @@ public void applyBeanValidatorAnnotations(final RequestBody requestBody, final L
640650

641651
if (validationExists || (!isOptional && (springRequestBodyRequired || swaggerRequestBodyRequired)))
642652
requestBody.setRequired(true);
643-
Content content = requestBody.getContent();
644-
for (MediaType mediaType : content.values()) {
645-
Schema<?> schema = mediaType.getSchema();
646-
applyValidationsToSchema(annos, schema);
647-
}
648653
}
649654

650655
/**
@@ -703,10 +708,10 @@ else if (OPENAPI_STRING_TYPE.equals(schema.getType())) {
703708
private Map<ParameterId, io.swagger.v3.oas.annotations.Parameter> getApiParameters(Method method) {
704709
Class<?> declaringClass = method.getDeclaringClass();
705710

706-
Set<io.swagger.v3.oas.annotations.Parameters> apiParametersDoc = AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.Parameters.class);
711+
Set<Parameters> apiParametersDoc = AnnotatedElementUtils.findAllMergedAnnotations(method, Parameters.class);
707712
LinkedHashMap<ParameterId, io.swagger.v3.oas.annotations.Parameter> apiParametersMap = apiParametersDoc.stream().flatMap(x -> Stream.of(x.value())).collect(Collectors.toMap(ParameterId::new, x -> x, (e1, e2) -> e2, LinkedHashMap::new));
708713

709-
Set<io.swagger.v3.oas.annotations.Parameters> apiParametersDocDeclaringClass = AnnotatedElementUtils.findAllMergedAnnotations(declaringClass, io.swagger.v3.oas.annotations.Parameters.class);
714+
Set<Parameters> apiParametersDocDeclaringClass = AnnotatedElementUtils.findAllMergedAnnotations(declaringClass, Parameters.class);
710715
LinkedHashMap<ParameterId, io.swagger.v3.oas.annotations.Parameter> apiParametersDocDeclaringClassMap = apiParametersDocDeclaringClass.stream().flatMap(x -> Stream.of(x.value())).collect(Collectors.toMap(ParameterId::new, x -> x, (e1, e2) -> e2, LinkedHashMap::new));
711716
apiParametersMap.putAll(apiParametersDocDeclaringClassMap);
712717

@@ -803,9 +808,9 @@ private boolean checkRequestBodyAnnotation(MethodParameter methodParameter) {
803808
* @return the boolean
804809
*/
805810
private boolean checkFile(MethodParameter methodParameter) {
806-
if (methodParameter.getParameterAnnotation(org.springframework.web.bind.annotation.RequestPart.class) != null)
811+
if (methodParameter.getParameterAnnotation(RequestPart.class) != null)
807812
return true;
808-
else if (methodParameter.getParameterAnnotation(org.springframework.web.bind.annotation.RequestParam.class) != null) {
813+
else if (methodParameter.getParameterAnnotation(RequestParam.class) != null) {
809814
return isFile(methodParameter.getParameterType());
810815
}
811816
return false;

Diff for: springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app1.json

+23-18
Original file line numberDiff line numberDiff line change
@@ -81,26 +81,31 @@
8181
"schema": {
8282
"type": "string"
8383
}
84-
},
85-
{
86-
"name": "firstName",
87-
"in": "query",
88-
"description": "First Name",
89-
"required": true,
90-
"schema": {
91-
"type": "string"
92-
}
93-
},
94-
{
95-
"name": "lastName",
96-
"in": "query",
97-
"description": "Last Name",
98-
"required": true,
99-
"schema": {
100-
"type": "string"
101-
}
10284
}
10385
],
86+
"requestBody": {
87+
"content": {
88+
"application/x-www-form-urlencoded": {
89+
"schema": {
90+
"required": [
91+
"firstName",
92+
"lastName"
93+
],
94+
"type": "object",
95+
"properties": {
96+
"firstName": {
97+
"type": "string",
98+
"description": "First Name"
99+
},
100+
"lastName": {
101+
"type": "string",
102+
"description": "Last Name"
103+
}
104+
}
105+
}
106+
}
107+
}
108+
},
104109
"responses": {
105110
"500": {
106111
"description": "Internal Server Error",

Diff for: springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app105-3.json

+23-22
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@
160160
}
161161
}
162162
},
163-
"404": {
164-
"description": "Pet not found",
163+
"200": {
164+
"description": "successful operation",
165165
"content": {
166166
"application/xml": {
167167
"schema": {
@@ -175,8 +175,8 @@
175175
}
176176
}
177177
},
178-
"200": {
179-
"description": "successful operation",
178+
"404": {
179+
"description": "Pet not found",
180180
"content": {
181181
"application/xml": {
182182
"schema": {
@@ -213,26 +213,27 @@
213213
"type": "integer",
214214
"format": "int64"
215215
}
216-
},
217-
{
218-
"name": "name",
219-
"in": "query",
220-
"description": "Updated name of the pet",
221-
"required": false,
222-
"schema": {
223-
"type": "string"
224-
}
225-
},
226-
{
227-
"name": "status",
228-
"in": "query",
229-
"description": "Updated status of the pet",
230-
"required": false,
231-
"schema": {
232-
"type": "string"
233-
}
234216
}
235217
],
218+
"requestBody": {
219+
"content": {
220+
"application/x-www-form-urlencoded": {
221+
"schema": {
222+
"type": "object",
223+
"properties": {
224+
"name": {
225+
"type": "string",
226+
"description": "Updated name of the pet"
227+
},
228+
"status": {
229+
"type": "string",
230+
"description": "Updated status of the pet"
231+
}
232+
}
233+
}
234+
}
235+
}
236+
},
236237
"responses": {
237238
"400": {
238239
"description": "Bad Request",

Diff for: springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app2.json

+19-18
Original file line numberDiff line numberDiff line change
@@ -537,26 +537,27 @@
537537
"type": "integer",
538538
"format": "int64"
539539
}
540-
},
541-
{
542-
"name": "name",
543-
"in": "query",
544-
"description": "Updated name of the pet",
545-
"required": false,
546-
"schema": {
547-
"type": "string"
548-
}
549-
},
550-
{
551-
"name": "status",
552-
"in": "query",
553-
"description": "Updated status of the pet",
554-
"required": false,
555-
"schema": {
556-
"type": "string"
557-
}
558540
}
559541
],
542+
"requestBody": {
543+
"content": {
544+
"application/x-www-form-urlencoded": {
545+
"schema": {
546+
"type": "object",
547+
"properties": {
548+
"name": {
549+
"type": "string",
550+
"description": "Updated name of the pet"
551+
},
552+
"status": {
553+
"type": "string",
554+
"description": "Updated status of the pet"
555+
}
556+
}
557+
}
558+
}
559+
}
560+
},
560561
"responses": {
561562
"400": {
562563
"description": "Bad Request",

0 commit comments

Comments
 (0)