Skip to content

Commit a60d3d4

Browse files
[Bug][java-spring] Use Flux only for multipart-form-data file parameters with multiple file uploads (#21561)
* Use Flux only for multipart-form-data file parameters with multiple files * Update samples --------- Co-authored-by: Chris Gual <[email protected]>
1 parent d69714f commit a60d3d4

File tree

16 files changed

+81
-21
lines changed

16 files changed

+81
-21
lines changed

modules/openapi-generator/src/main/resources/JavaSpring/api.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ public interface {{classname}} {
275275
}
276276

277277
// Override this method
278-
{{#jdk8-default-interface}}default {{/jdk8-default-interface}} {{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}Flux<Part>{{/reactive}}{{^reactive}}{{#isArray}}List<MultipartFile>{{/isArray}}{{^isArray}}MultipartFile{{/isArray}}{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}} {
278+
{{#jdk8-default-interface}}default {{/jdk8-default-interface}} {{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}} {
279279
{{/delegate-method}}
280280
{{^isDelegate}}
281281
{{>methodBody}}{{! prevent indent}}

modules/openapi-generator/src/main/resources/JavaSpring/apiDelegate.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public interface {{classname}}Delegate {
7171
{{#isDeprecated}}
7272
@Deprecated
7373
{{/isDeprecated}}
74-
{{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#isArray}}List<{{/isArray}}{{#reactive}}Flux<Part>{{/reactive}}{{^reactive}}{{#isFormParam}}MultipartFile{{/isFormParam}}{{^isFormParam}}{{>optionalDataType}}{{/isFormParam}}{{/reactive}}{{#isArray}}>{{/isArray}}{{/isFile}} {{paramName}}{{^-last}},
74+
{{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}{{#isFormParam}}MultipartFile{{/isFormParam}}{{^isFormParam}}{{>optionalDataType}}{{/isFormParam}}{{#isArray}}>{{/isArray}}{{/reactive}}{{/isFile}} {{paramName}}{{^-last}},
7575
{{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}},
7676
{{/hasParams}}ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} {
7777
{{>methodBody}}{{! prevent indent}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}@RequestPart{{/isArray}}{{^isArray}}{{#reactive}}@RequestPart{{/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}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#isArray}}List<{{/isArray}}{{#reactive}}Flux<Part>{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}} {{paramName}}{{/isFile}}{{/isFormParam}}
1+
{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}@RequestPart{{/isArray}}{{^isArray}}{{#reactive}}@RequestPart{{/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}}{{#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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,66 @@ public void testMultipartBoot() throws IOException {
733733
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"statusArray\"", "required", "false"));
734734
}
735735

736+
@Test
737+
public void testReactiveMultipartBoot() throws IOException {
738+
final SpringCodegen codegen = new SpringCodegen();
739+
codegen.setLibrary("spring-boot");
740+
codegen.setDelegatePattern(true);
741+
codegen.additionalProperties().put(DOCUMENTATION_PROVIDER, "springfox");
742+
codegen.additionalProperties().put(SpringCodegen.REACTIVE, "true");
743+
744+
final Map<String, File> files = generateFiles(codegen, "src/test/resources/3_0/form-multipart-binary-array.yaml");
745+
746+
// Check that the delegate handles the array
747+
JavaFileAssert.assertThat(files.get("MultipartArrayApiDelegate.java"))
748+
.assertMethod("multipartArray", "Flux<Part>", "ServerWebExchange")
749+
.assertParameter("files").hasType("Flux<Part>");
750+
751+
// Check that the api handles the array
752+
JavaFileAssert.assertThat(files.get("MultipartArrayApi.java"))
753+
.assertMethod("multipartArray", "Flux<Part>", "ServerWebExchange")
754+
.assertParameter("files").hasType("Flux<Part>")
755+
.assertParameterAnnotations()
756+
.containsWithNameAndAttributes("ApiParam", ImmutableMap.of("value", "\"Many files\""))
757+
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"files\"", "required", "false"));
758+
759+
// UPDATE: the following test has been ignored due to https://github.com/OpenAPITools/openapi-generator/pull/11081/
760+
// We will contact the contributor of the following test to see if the fix will break their use cases and
761+
// how we can fix it accordingly.
762+
//// Check that the delegate handles the single file
763+
// final File multipartSingleApiDelegate = files.get("MultipartSingleApiDelegate.java");
764+
// assertFileContains(multipartSingleApiDelegate.toPath(), "MultipartFile file");
765+
766+
// Check that the api handles the single file
767+
JavaFileAssert.assertThat(files.get("MultipartSingleApi.java"))
768+
.assertMethod("multipartSingle", "Part", "ServerWebExchange")
769+
.assertParameter("file").hasType("Part")
770+
.assertParameterAnnotations()
771+
.containsWithNameAndAttributes("ApiParam", ImmutableMap.of("value", "\"One file\""))
772+
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"file\"", "required", "false"));
773+
774+
// Check that api validates mixed multipart request
775+
JavaFileAssert.assertThat(files.get("MultipartMixedApi.java"))
776+
.assertMethod("multipartMixed", "MultipartMixedStatus", "Part", "MultipartMixedRequestMarker", "List<MultipartMixedStatus>", "ServerWebExchange")
777+
.assertParameter("status").hasType("MultipartMixedStatus")
778+
.assertParameterAnnotations()
779+
.containsWithName("Valid")
780+
.containsWithNameAndAttributes("ApiParam", ImmutableMap.of("value", "\"\""))
781+
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"status\"", "required", "true"))
782+
.toParameter().toMethod()
783+
.assertParameter("file").hasType("Part")
784+
.assertParameterAnnotations()
785+
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"file\"", "required", "true"))
786+
.toParameter().toMethod()
787+
.assertParameter("marker").hasType("MultipartMixedRequestMarker")
788+
.assertParameterAnnotations()
789+
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"marker\"", "required", "false"))
790+
.toParameter().toMethod()
791+
.assertParameter("statusArray").hasType("List<MultipartMixedStatus>")
792+
.assertParameterAnnotations()
793+
.containsWithNameAndAttributes("RequestPart", ImmutableMap.of("value", "\"statusArray\"", "required", "false"));
794+
}
795+
736796
@Test
737797
public void testAdditionalProperties_issue1466() throws IOException {
738798
final SpringCodegen codegen = new SpringCodegen();

samples/client/petstore/spring-http-interface-reactive-noResponseEntity/src/main/java/org/openapitools/api/FakeApi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Mono<Void> testEndpointParameters(
226226
@RequestPart(value = "int64", required = false) Long int64,
227227
@RequestPart(value = "float", required = false) Float _float,
228228
@RequestPart(value = "string", required = false) String string,
229-
@RequestPart(value = "binary", required = false) Flux<Part> binary,
229+
@RequestPart(value = "binary", required = false) Part binary,
230230
@RequestPart(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
231231
@RequestPart(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime dateTime,
232232
@RequestPart(value = "password", required = false) String password,

samples/client/petstore/spring-http-interface-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApi.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ Mono<Void> updatePetWithForm(
210210
Mono<ModelApiResponse> uploadFile(
211211
@PathVariable("petId") Long petId,
212212
@RequestPart(value = "additionalMetadata", required = false) String additionalMetadata,
213-
@RequestPart(value = "file", required = false) Flux<Part> file
213+
@RequestPart(value = "file", required = false) Part file
214214
);
215215

216216

@@ -232,7 +232,7 @@ Mono<ModelApiResponse> uploadFile(
232232
)
233233
Mono<ModelApiResponse> uploadFileWithRequiredFile(
234234
@PathVariable("petId") Long petId,
235-
@RequestPart(value = "requiredFile", required = true) Flux<Part> requiredFile,
235+
@RequestPart(value = "requiredFile", required = true) Part requiredFile,
236236
@RequestPart(value = "additionalMetadata", required = false) String additionalMetadata
237237
);
238238

samples/client/petstore/spring-http-interface-reactive/src/main/java/org/openapitools/api/FakeApi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ Mono<ResponseEntity<Void>> testEndpointParameters(
217217
@RequestPart(value = "int64", required = false) Long int64,
218218
@RequestPart(value = "float", required = false) Float _float,
219219
@RequestPart(value = "string", required = false) String string,
220-
@RequestPart(value = "binary", required = false) Flux<Part> binary,
220+
@RequestPart(value = "binary", required = false) Part binary,
221221
@RequestPart(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
222222
@RequestPart(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime dateTime,
223223
@RequestPart(value = "password", required = false) String password,

samples/client/petstore/spring-http-interface-reactive/src/main/java/org/openapitools/api/PetApi.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ Mono<ResponseEntity<Void>> updatePetWithForm(
201201
Mono<ResponseEntity<ModelApiResponse>> uploadFile(
202202
@PathVariable("petId") Long petId,
203203
@RequestPart(value = "additionalMetadata", required = false) String additionalMetadata,
204-
@RequestPart(value = "file", required = false) Flux<Part> file
204+
@RequestPart(value = "file", required = false) Part file
205205
);
206206

207207

@@ -222,7 +222,7 @@ Mono<ResponseEntity<ModelApiResponse>> uploadFile(
222222
)
223223
Mono<ResponseEntity<ModelApiResponse>> uploadFileWithRequiredFile(
224224
@PathVariable("petId") Long petId,
225-
@RequestPart(value = "requiredFile", required = true) Flux<Part> requiredFile,
225+
@RequestPart(value = "requiredFile", required = true) Part requiredFile,
226226
@RequestPart(value = "additionalMetadata", required = false) String additionalMetadata
227227
);
228228

samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/FakeApi.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ default Mono<Void> testEndpointParameters(
396396
@ApiParam(value = "None") @Valid @RequestPart(value = "int64", required = false) Long int64,
397397
@ApiParam(value = "None") @Valid @RequestPart(value = "float", required = false) Float _float,
398398
@ApiParam(value = "None") @Valid @RequestPart(value = "string", required = false) String string,
399-
@ApiParam(value = "None") @RequestPart(value = "binary", required = false) Flux<Part> binary,
399+
@ApiParam(value = "None") @RequestPart(value = "binary", required = false) Part binary,
400400
@ApiParam(value = "None") @Valid @RequestPart(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
401401
@ApiParam(value = "None") @Valid @RequestPart(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime dateTime,
402402
@ApiParam(value = "None") @Valid @RequestPart(value = "password", required = false) String password,
@@ -698,7 +698,7 @@ default Mono<Integer> testWithResultExample(
698698

699699
default Mono<ModelApiResponse> uploadFileWithRequiredFile(
700700
@ApiParam(value = "ID of pet to update", required = true) @PathVariable("petId") Long petId,
701-
@ApiParam(value = "file to upload", required = true) @RequestPart(value = "requiredFile", required = true) Flux<Part> requiredFile,
701+
@ApiParam(value = "file to upload", required = true) @RequestPart(value = "requiredFile", required = true) Part requiredFile,
702702
@ApiParam(value = "Additional data to pass to server") @Valid @RequestPart(value = "additionalMetadata", required = false) String additionalMetadata,
703703
@ApiIgnore final ServerWebExchange exchange
704704
) {

samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/FakeApiDelegate.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ default Mono<Void> testEndpointParameters(BigDecimal number,
238238
Long int64,
239239
Float _float,
240240
String string,
241-
Flux<Part> binary,
241+
Part binary,
242242
LocalDate date,
243243
OffsetDateTime dateTime,
244244
String password,
@@ -411,7 +411,7 @@ default Mono<Integer> testWithResultExample(ServerWebExchange exchange) {
411411
* @see FakeApi#uploadFileWithRequiredFile
412412
*/
413413
default Mono<ModelApiResponse> uploadFileWithRequiredFile(Long petId,
414-
Flux<Part> requiredFile,
414+
Part requiredFile,
415415
String additionalMetadata,
416416
ServerWebExchange exchange) {
417417
Mono<Void> result = Mono.empty();

0 commit comments

Comments
 (0)