Skip to content

[BUG][Kotlin-Spring] Wrong parameter type for multipart-form-data file upload when using kotlin-spring with reactive #21548

Open
@btpnlsl

Description

@btpnlsl

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions