Description
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
The kotlin-spring generator allows users to generate Spring Boot controllers & service interfaces for both reactive (Spring WebFlux) or non-reactive (Spring MVC) servers.
The WebFlux or MVC variants of Spring have different mechanisms to support file upload using multipart-form-data.
The kotlin-spring generator is creating controllers & interfaces which are appropriate for Spring MVC, but not for Spring WebFlux, even when the reactive
option is set to true
in the generator config.
openapi-generator version
version 7.14.0
OpenAPI declaration file content or url
/upload:
post:
summary: uploads a file
operationId: uploadFile
tags:
- upload
description: upload a file
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: file to upload
responses:
201:
description: upload was successful
Generation Details
Full pom.xml & an openapi spec can be found in this Gist
The OpenAPI generator section of the Maven POM.xml file is
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator.version}</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi-spec.yaml</inputSpec>
<generatorName>kotlin-spring</generatorName>
<library>spring-boot</library>
<generateApiTests>false</generateApiTests>
<generateApiDocumentation>false</generateApiDocumentation>
<generateModelTests>false</generateModelTests>
<generateModelDocumentation>false</generateModelDocumentation>
<generateSupportingFiles>false</generateSupportingFiles>
<configOptions>
<useSpringBoot3>true</useSpringBoot3>
<reactive>true</reactive>
<serviceInterface>true</serviceInterface>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
The resulting controller method looks like this
@Operation(
summary = "uploads a file",
operationId = "uploadFile",
description = """upload a file""",
responses = [
ApiResponse(responseCode = "201", description = "upload was successful") ]
)
@RequestMapping(
method = [RequestMethod.POST],
value = ["/upload"],
consumes = ["multipart/form-data"]
)
suspend fun uploadFile(@Parameter(description = "file to upload") @Valid @RequestPart("file", required = false) file: org.springframework.web.multipart.MultipartFile?): ResponseEntity<Unit> {
return ResponseEntity(service.uploadFile(file), HttpStatus.valueOf(201))
}
and the results interface method looks like
suspend fun uploadFile(file: org.springframework.web.multipart.MultipartFile?): Unit
This would be valid for non-reactive Spring MVC but is not valid for reactive Spring WebFlux.
Steps to reproduce
mvn clean generate-sources
Related issues/PRs
There was a similar issue created for the Java Spring generator: #6993
The change to support Spring MVC was made in this PR: #20108
Suggest a fix
The Java support for reactive multipart-form-data uploads looks like it modified the following template files:
The general template logic for creating a parameter distinguishes between whether the parameters is a file or not, and if so, is it reactive or not, similar to this (from api.mustache):
{{#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}}
For some reason I don't understand the template logic is different in Java Spring's api.mustache vs apiDelegate.mustache.
On the kotlin-spring side of things it looks like the MultipartFile
class is only referenced in optionalDataType.mustache
I'm not sure that this is the right place to template File params, but I'm also unsure about what is the function of optionalDataType.mustache
in the Kotlin Spring generator. In the Java Spring generator, the same file looks like it is designed to use / not-use the Optional
class in order to do null checking. In the Kotlin Spring generator, prior to the change which added support for MultipartFile
, optionalDataType.mustache
was about whether to use the Kotlin ?
null-operator for non-required data types which did not have a default value that might be null.
I think having File / not-File & reactive / non-reactive template logic in optionalDataType.mustache
might be the wrong place and the logic should be moved to a higher level like apiDelegate.mustache
& formParams.mustache
. Additionally, optionalDataType.mustache
is used in a bunch of other template files in the Kotlin Spring generator for header, cookie, body & path parameters (for example), where the logic to use a MultipartFile
vs Flux<Part>
not may not be applicable or desirable.