Skip to content

Commit c899cb4

Browse files
committed
fix reactive multipart
1 parent e6faab7 commit c899cb4

3 files changed

Lines changed: 46 additions & 8 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@ public void setIsVoid(boolean isVoid) {
799799
prepareVersioningParameters(ops);
800800
handleImplicitHeaders(operation);
801801
convertByteArrayParamsToStringType(operation);
802+
markMultipartFormDataParameters(operation);
802803
}
803804
// The tag for the controller is the first tag of the first operation
804805
final CodegenOperation firstOperation = ops.get(0);
@@ -834,6 +835,44 @@ private void convertByteArrayParamsToStringType(CodegenOperation operation) {
834835
LOGGER.info("Converted parameters [{}] from byte[] to String in operation [{}]", convertedParams.stream().map(param -> param.paramName).collect(Collectors.toList()), operation.operationId);
835836
}
836837

838+
/**
839+
* Marks form parameters that are in multipart/form-data operations for special handling in reactive mode.
840+
* <p>
841+
* In reactive Spring WebFlux, multipart/form-data parameters must use @RequestPart instead of @RequestParam,
842+
* and non-model parameters (primitives, enums, strings) must be received as String or Flux&lt;String&gt; and
843+
* converted manually in the implementation.
844+
* </p>
845+
*
846+
* @param operation the codegen operation whose parameters will be marked if necessary
847+
**/
848+
private void markMultipartFormDataParameters(CodegenOperation operation) {
849+
if (!reactive) {
850+
return; // Only applies to reactive mode
851+
}
852+
853+
// Check if this operation consumes multipart/form-data
854+
boolean isMultipartFormData = false;
855+
if (operation.hasConsumes) {
856+
for (Map<String, String> consume : operation.consumes) {
857+
if ("multipart/form-data".equals(consume.get("mediaType"))) {
858+
isMultipartFormData = true;
859+
break;
860+
}
861+
}
862+
}
863+
864+
if (!isMultipartFormData) {
865+
return;
866+
}
867+
868+
// Mark all form parameters as multipart form data
869+
for (CodegenParameter param : operation.allParams) {
870+
if (param.isFormParam) {
871+
param.vendorExtensions.put("x-isMultipartFormData", true);
872+
}
873+
}
874+
}
875+
837876
private interface DataTypeAssigner {
838877
void setReturnType(String returnType);
839878

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} {{>beanValidationBodyParams}}@Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}{{#items.isModel}}@RequestPart{{/items.isModel}}{{^items.isModel}}@RequestParam{{/items.isModel}}{{/isArray}}{{^isArray}}{{#reactive}}@RequestParam{{/reactive}}{{^reactive}}@RequestParam{{/reactive}}{{/isArray}}{{/isModel}}(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}){{>dateTimeParam}} {{^required}}{{#useOptional}}Optional<{{/useOptional}}{{/required}}{{{dataType}}}{{^required}}{{#useOptional}}>{{/useOptional}}{{/required}} {{paramName}}{{/isFile}}{{#isByteArray}} /* base64 encoded binary */{{/isByteArray}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}} {{paramName}}{{/isFile}}{{/isFormParam}}
1+
{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} {{>beanValidationBodyParams}}@Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}{{#items.isModel}}@RequestPart{{/items.isModel}}{{^items.isModel}}{{#reactive}}{{#vendorExtensions.x-isMultipartFormData}}@RequestPart{{/vendorExtensions.x-isMultipartFormData}}{{^vendorExtensions.x-isMultipartFormData}}@RequestParam{{/vendorExtensions.x-isMultipartFormData}}{{/reactive}}{{^reactive}}@RequestParam{{/reactive}}{{/items.isModel}}{{/isArray}}{{^isArray}}{{#reactive}}{{#vendorExtensions.x-isMultipartFormData}}@RequestPart{{/vendorExtensions.x-isMultipartFormData}}{{^vendorExtensions.x-isMultipartFormData}}@RequestParam{{/vendorExtensions.x-isMultipartFormData}}{{/reactive}}{{^reactive}}@RequestParam{{/reactive}}{{/isArray}}{{/isModel}}(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}){{>dateTimeParam}} {{^required}}{{#useOptional}}Optional<{{/useOptional}}{{/required}}{{#reactive}}{{#vendorExtensions.x-isMultipartFormData}}{{#isModel}}{{{dataType}}}{{/isModel}}{{^isModel}}{{#isArray}}{{#items.isModel}}{{{dataType}}}{{/items.isModel}}{{^items.isModel}}Flux<String> /* Flux<{{{dataType}}}> */{{/items.isModel}}{{/isArray}}{{^isArray}}String /* {{{dataType}}} */{{/isArray}}{{/isModel}}{{/vendorExtensions.x-isMultipartFormData}}{{^vendorExtensions.x-isMultipartFormData}}{{{dataType}}}{{/vendorExtensions.x-isMultipartFormData}}{{/reactive}}{{^reactive}}{{{dataType}}}{{/reactive}}{{^required}}{{#useOptional}}>{{/useOptional}}{{/required}} {{paramName}}{{/isFile}}{{#isByteArray}} /* base64 encoded binary */{{/isByteArray}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}} {{paramName}}{{/isFile}}{{/isFormParam}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -779,12 +779,12 @@ public void testReactiveMultipartBoot() throws IOException {
779779

780780
// Check that api validates mixed multipart request
781781
JavaFileAssert.assertThat(files.get("MultipartMixedApi.java"))
782-
.assertMethod("multipartMixed", "MultipartMixedStatus", "Part", "MultipartMixedRequestMarker", "List<MultipartMixedRequestMarker>", "List<MultipartMixedStatus>", "ServerWebExchange")
783-
.assertParameter("status").hasType("MultipartMixedStatus")
782+
.assertMethod("multipartMixed", "String", "Part", "MultipartMixedRequestMarker", "List<MultipartMixedRequestMarker>", "Flux<String>", "ServerWebExchange")
783+
.assertParameter("status").hasType("String")
784784
.assertParameterAnnotations()
785785
.containsWithName("Valid")
786786
.containsWithNameAndAttributes("ApiParam", ImmutableMap.of("value", "\"\""))
787-
.containsWithNameAndAttributes("RequestParam", ImmutableMap.of("value", "\"status\"", "required", "true"))
787+
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"status\"", "required", "true"))
788788
.toParameter().toMethod()
789789
.assertParameter("file").hasType("Part")
790790
.assertParameterAnnotations()
@@ -799,9 +799,9 @@ public void testReactiveMultipartBoot() throws IOException {
799799
.assertParameterAnnotations()
800800
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"markerArray\"", "required", "false"))
801801
.toParameter().toMethod()
802-
.assertParameter("statusArray").hasType("List<MultipartMixedStatus>")
802+
.assertParameter("statusArray").hasType("Flux<String>")
803803
.assertParameterAnnotations()
804-
.containsWithNameAndAttributes("RequestParam", ImmutableMap.of("value", "\"statusArray\"", "required", "false"));
804+
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"statusArray\"", "required", "false"));
805805
}
806806

807807
@Test
@@ -4923,9 +4923,8 @@ public void givenMultipartForm_whenGenerateReactiveServer_thenParameterAreCreate
49234923
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
49244924

49254925
generator.opts(input).generate();
4926-
// Only file or object types would use @RequestPart
49274926
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/PetApi.java"),
4928-
"@Valid @RequestParam(value = \"additionalMetadata\", required = false) String additionalMetadata");
4927+
"@Valid @RequestPart(value = \"additionalMetadata\", required = false) String /* String */ additionalMetadata");
49294928
}
49304929

49314930
@Test

0 commit comments

Comments
 (0)