Skip to content

Commit f44bc30

Browse files
authored
[Kotlin][Spring] Fix RequestPart handling for multipart request (OpenAPITools#19058)
* [Kotlin][Spring] Fix RequestPart handling for multipart request * [Kotlin][Spring] Add sample spec for kotlin multipart request model --------- Co-authored-by: dimitar.tomov <[email protected]>
1 parent aaf3ea2 commit f44bc30

File tree

22 files changed

+610
-1
lines changed

22 files changed

+610
-1
lines changed

Diff for: .github/workflows/samples-kotlin-server.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
sample:
3030
# server
3131
- samples/server/petstore/kotlin-springboot
32+
- samples/server/petstore/kotlin-springboot-multipart-request-model
3233
- samples/server/petstore/kotlin-springboot-bigdecimal-default
3334
- samples/server/petstore/kotlin-springboot-delegate
3435
- samples/server/petstore/kotlin-springboot-modelMutable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
generatorName: kotlin-spring
2+
outputDir: samples/server/petstore/kotlin-springboot-multipart-request-model
3+
library: spring-boot
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/issue_15251_multipart_request_model.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isFormParam}}{{^isFile}}{{#swagger2AnnotationLibrary}}@Parameter(description = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}{{#defaultValue}}, schema = Schema(allowableValues = [{{#values}}"{{{.}}}"{{^-last}}, {{/-last}}{{/values}}]{{^isContainer}}, defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{/isContainer}}){{/defaultValue}}{{/allowableValues}}{{#allowableValues}}{{^defaultValue}}, schema = Schema(allowableValues = [{{#values}}"{{{.}}}"{{^-last}}, {{/-last}}{{/values}}]){{/defaultValue}}{{/allowableValues}}{{^allowableValues}}{{#defaultValue}}{{^isContainer}}, schema = Schema(defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}){{/isContainer}}{{/defaultValue}}{{/allowableValues}}){{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}}@ApiParam(value = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}, allowableValues = "{{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}"{{/allowableValues}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{/swagger1AnnotationLibrary}} @RequestParam(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{{paramName}}}: {{>optionalDataType}} {{/isFile}}{{#isFile}}{{#swagger2AnnotationLibrary}}@Parameter(description = "{{{description}}}"){{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}}@ApiParam(value = "file detail"){{/swagger1AnnotationLibrary}} {{#useBeanValidation}}@Valid{{/useBeanValidation}} @RequestPart("{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{{paramName}}}: {{>optionalDataType}}{{/isFile}}{{/isFormParam}}
1+
{{#isFormParam}}{{^isFile}}{{#swagger2AnnotationLibrary}}@Parameter(description = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}{{#defaultValue}}, schema = Schema(allowableValues = [{{#values}}"{{{.}}}"{{^-last}}, {{/-last}}{{/values}}]{{^isContainer}}, defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{/isContainer}}){{/defaultValue}}{{/allowableValues}}{{#allowableValues}}{{^defaultValue}}, schema = Schema(allowableValues = [{{#values}}"{{{.}}}"{{^-last}}, {{/-last}}{{/values}}]){{/defaultValue}}{{/allowableValues}}{{^allowableValues}}{{#defaultValue}}{{^isContainer}}, schema = Schema(defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}){{/isContainer}}{{/defaultValue}}{{/allowableValues}}){{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}}@ApiParam(value = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}, allowableValues = "{{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}"{{/allowableValues}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{/swagger1AnnotationLibrary}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}@RequestParam{{/isModel}}(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{{paramName}}}: {{>optionalDataType}} {{/isFile}}{{#isFile}}{{#swagger2AnnotationLibrary}}@Parameter(description = "{{{description}}}"){{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}}@ApiParam(value = "file detail"){{/swagger1AnnotationLibrary}} {{#useBeanValidation}}@Valid{{/useBeanValidation}} @RequestPart("{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{{paramName}}}: {{>optionalDataType}}{{/isFile}}{{/isFormParam}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
openapi: '3.0.1'
2+
info:
3+
version: 1.0.0
4+
title: MultipartFile test
5+
paths:
6+
/multipart-mixed:
7+
post:
8+
tags:
9+
- multipart
10+
description: Mixed MultipartFile test
11+
operationId: multipartMixed
12+
requestBody:
13+
content:
14+
multipart/form-data:
15+
schema:
16+
type: object
17+
required:
18+
- status
19+
- file
20+
properties:
21+
status:
22+
$ref: '#/components/schemas/MultipartMixedStatus'
23+
marker:
24+
description: "additional object"
25+
type: object
26+
properties:
27+
name:
28+
type: string
29+
file:
30+
description: "a file"
31+
type: string
32+
format: binary
33+
responses:
34+
'204':
35+
description: Successful operation
36+
components:
37+
schemas:
38+
MultipartMixedStatus:
39+
description: "additional field as Enum"
40+
type: string
41+
enum:
42+
- ALLOWED
43+
- IN_PROGRESS
44+
- REJECTED
45+
example: REJECTED
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
README.md
2+
build.gradle.kts
3+
pom.xml
4+
settings.gradle
5+
src/main/kotlin/org/openapitools/Application.kt
6+
src/main/kotlin/org/openapitools/HomeController.kt
7+
src/main/kotlin/org/openapitools/SpringDocConfiguration.kt
8+
src/main/kotlin/org/openapitools/api/ApiUtil.kt
9+
src/main/kotlin/org/openapitools/api/Exceptions.kt
10+
src/main/kotlin/org/openapitools/api/MultipartMixedApiController.kt
11+
src/main/kotlin/org/openapitools/model/MultipartMixedRequestMarker.kt
12+
src/main/kotlin/org/openapitools/model/MultipartMixedStatus.kt
13+
src/main/resources/application.yaml
14+
src/main/resources/openapi.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.8.0-SNAPSHOT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# multipartFileTest
2+
3+
This Kotlin based [Spring Boot](https://spring.io/projects/spring-boot) application has been generated using the [OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator).
4+
5+
## Getting Started
6+
7+
This document assumes you have either maven or gradle available, either via the wrapper or otherwise. This does not come with a gradle / maven wrapper checked in.
8+
9+
By default a [`pom.xml`](pom.xml) file will be generated. If you specified `gradleBuildFile=true` when generating this project, a `build.gradle.kts` will also be generated. Note this uses [Gradle Kotlin DSL](https://github.com/gradle/kotlin-dsl).
10+
11+
To build the project using maven, run:
12+
```bash
13+
mvn package && java -jar target/openapi-spring-1.0.0.jar
14+
```
15+
16+
To build the project using gradle, run:
17+
```bash
18+
gradle build && java -jar build/libs/openapi-spring-1.0.0.jar
19+
```
20+
21+
If all builds successfully, the server should run on [http://localhost:8080/](http://localhost:8080/)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2+
3+
buildscript {
4+
repositories {
5+
mavenCentral()
6+
}
7+
dependencies {
8+
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.6.7")
9+
}
10+
}
11+
12+
group = "org.openapitools"
13+
version = "1.0.0"
14+
15+
repositories {
16+
mavenCentral()
17+
}
18+
19+
tasks.withType<KotlinCompile> {
20+
kotlinOptions.jvmTarget = "1.8"
21+
}
22+
23+
plugins {
24+
val kotlinVersion = "1.6.21"
25+
id("org.jetbrains.kotlin.jvm") version kotlinVersion
26+
id("org.jetbrains.kotlin.plugin.jpa") version kotlinVersion
27+
id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion
28+
id("org.springframework.boot") version "2.6.7"
29+
id("io.spring.dependency-management") version "1.0.11.RELEASE"
30+
}
31+
32+
dependencies {
33+
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
34+
compile("org.jetbrains.kotlin:kotlin-reflect")
35+
compile("org.springframework.boot:spring-boot-starter-web")
36+
compile("org.springdoc:springdoc-openapi-ui:1.6.8")
37+
38+
compile("com.google.code.findbugs:jsr305:3.0.2")
39+
compile("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
40+
compile("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
41+
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
42+
compile("com.fasterxml.jackson.module:jackson-module-kotlin")
43+
compile("jakarta.validation:jakarta.validation-api")
44+
compile("jakarta.annotation:jakarta.annotation-api:2.1.0")
45+
46+
testCompile("org.jetbrains.kotlin:kotlin-test-junit5")
47+
testCompile("org.springframework.boot:spring-boot-starter-test") {
48+
exclude(module = "junit")
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
2+
<modelVersion>4.0.0</modelVersion>
3+
<groupId>org.openapitools</groupId>
4+
<artifactId>openapi-spring</artifactId>
5+
<packaging>jar</packaging>
6+
<name>openapi-spring</name>
7+
<version>1.0.0</version>
8+
<properties>
9+
<springdoc-openapi.version>1.6.8</springdoc-openapi.version>
10+
<findbugs-jsr305.version>3.0.2</findbugs-jsr305.version>
11+
<jakarta-annotation.version>2.1.0</jakarta-annotation.version>
12+
<kotlin-test-junit5.version>1.6.21</kotlin-test-junit5.version>
13+
14+
<kotlin.version>1.6.21</kotlin.version>
15+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16+
</properties>
17+
<parent>
18+
<groupId>org.springframework.boot</groupId>
19+
<artifactId>spring-boot-starter-parent</artifactId>
20+
<version>2.7.15</version>
21+
</parent>
22+
<build>
23+
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
24+
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
25+
<plugins>
26+
<plugin>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-maven-plugin</artifactId>
29+
<executions>
30+
<execution>
31+
<goals>
32+
<goal>repackage</goal>
33+
</goals>
34+
</execution>
35+
</executions>
36+
</plugin>
37+
<plugin>
38+
<artifactId>kotlin-maven-plugin</artifactId>
39+
<groupId>org.jetbrains.kotlin</groupId>
40+
<version>${kotlin.version}</version>
41+
<configuration>
42+
<compilerPlugins>
43+
<plugin>spring</plugin>
44+
</compilerPlugins>
45+
<jvmTarget>1.8</jvmTarget>
46+
</configuration>
47+
<executions>
48+
<execution>
49+
<id>compile</id>
50+
<phase>compile</phase>
51+
<goals>
52+
<goal>compile</goal>
53+
</goals>
54+
</execution>
55+
<execution>
56+
<id>test-compile</id>
57+
<phase>test-compile</phase>
58+
<goals>
59+
<goal>test-compile</goal>
60+
</goals>
61+
</execution>
62+
</executions>
63+
<dependencies>
64+
<dependency>
65+
<groupId>org.jetbrains.kotlin</groupId>
66+
<artifactId>kotlin-maven-allopen</artifactId>
67+
<version>${kotlin.version}</version>
68+
</dependency>
69+
</dependencies>
70+
</plugin>
71+
</plugins>
72+
</build>
73+
<dependencies>
74+
<dependency>
75+
<groupId>org.jetbrains.kotlin</groupId>
76+
<artifactId>kotlin-stdlib-jdk8</artifactId>
77+
<version>${kotlin.version}</version>
78+
</dependency>
79+
<dependency>
80+
<groupId>org.jetbrains.kotlin</groupId>
81+
<artifactId>kotlin-reflect</artifactId>
82+
<version>${kotlin.version}</version>
83+
</dependency>
84+
<dependency>
85+
<groupId>org.springframework.boot</groupId>
86+
<artifactId>spring-boot-starter-web</artifactId>
87+
</dependency>
88+
89+
<!--SpringDoc dependencies -->
90+
<dependency>
91+
<groupId>org.springdoc</groupId>
92+
<artifactId>springdoc-openapi-ui</artifactId>
93+
<version>${springdoc-openapi.version}</version>
94+
</dependency>
95+
96+
<!-- @Nullable annotation -->
97+
<dependency>
98+
<groupId>com.google.code.findbugs</groupId>
99+
<artifactId>jsr305</artifactId>
100+
<version>${findbugs-jsr305.version}</version>
101+
</dependency>
102+
<dependency>
103+
<groupId>com.fasterxml.jackson.dataformat</groupId>
104+
<artifactId>jackson-dataformat-yaml</artifactId>
105+
</dependency>
106+
<dependency>
107+
<groupId>com.fasterxml.jackson.dataformat</groupId>
108+
<artifactId>jackson-dataformat-xml</artifactId>
109+
</dependency>
110+
<dependency>
111+
<groupId>com.fasterxml.jackson.datatype</groupId>
112+
<artifactId>jackson-datatype-jsr310</artifactId>
113+
</dependency>
114+
<dependency>
115+
<groupId>com.fasterxml.jackson.module</groupId>
116+
<artifactId>jackson-module-kotlin</artifactId>
117+
</dependency>
118+
<!-- Bean Validation API support -->
119+
<dependency>
120+
<groupId>jakarta.validation</groupId>
121+
<artifactId>jakarta.validation-api</artifactId>
122+
</dependency>
123+
<dependency>
124+
<groupId>jakarta.annotation</groupId>
125+
<artifactId>jakarta.annotation-api</artifactId>
126+
<version>${jakarta-annotation.version}</version>
127+
<scope>provided</scope>
128+
</dependency>
129+
<dependency>
130+
<groupId>org.jetbrains.kotlin</groupId>
131+
<artifactId>kotlin-test-junit5</artifactId>
132+
<version>${kotlin-test-junit5.version}</version>
133+
<scope>test</scope>
134+
</dependency>
135+
</dependencies>
136+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
pluginManagement {
2+
repositories {
3+
maven { url = uri("https://repo.spring.io/snapshot") }
4+
maven { url = uri("https://repo.spring.io/milestone") }
5+
gradlePluginPortal()
6+
}
7+
resolutionStrategy {
8+
eachPlugin {
9+
if (requested.id.id == "org.springframework.boot") {
10+
useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}")
11+
}
12+
}
13+
}
14+
}
15+
rootProject.name = "openapi-spring"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.openapitools
2+
3+
import org.springframework.boot.runApplication
4+
import org.springframework.boot.autoconfigure.SpringBootApplication
5+
import org.springframework.context.annotation.ComponentScan
6+
7+
@SpringBootApplication
8+
@ComponentScan(basePackages = ["org.openapitools", "org.openapitools.api", "org.openapitools.model"])
9+
class Application
10+
11+
fun main(args: Array<String>) {
12+
runApplication<Application>(*args)
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.openapitools
2+
3+
import org.springframework.context.annotation.Bean
4+
import org.springframework.stereotype.Controller
5+
import org.springframework.web.bind.annotation.RequestMapping
6+
import org.springframework.web.bind.annotation.ResponseBody
7+
import org.springframework.web.bind.annotation.GetMapping
8+
9+
/**
10+
* Home redirection to OpenAPI api documentation
11+
*/
12+
@Controller
13+
class HomeController {
14+
15+
@RequestMapping("/")
16+
fun index(): String = "redirect:swagger-ui.html"
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.openapitools
2+
3+
import org.springframework.context.annotation.Bean
4+
import org.springframework.context.annotation.Configuration
5+
6+
import io.swagger.v3.oas.models.OpenAPI
7+
import io.swagger.v3.oas.models.info.Info
8+
import io.swagger.v3.oas.models.info.Contact
9+
import io.swagger.v3.oas.models.info.License
10+
import io.swagger.v3.oas.models.Components
11+
import io.swagger.v3.oas.models.security.SecurityScheme
12+
13+
@Configuration
14+
class SpringDocConfiguration {
15+
16+
@Bean
17+
fun apiInfo(): OpenAPI {
18+
return OpenAPI()
19+
.info(
20+
Info()
21+
.title("MultipartFile test")
22+
.description("No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)")
23+
.version("1.0.0")
24+
)
25+
}
26+
}

0 commit comments

Comments
 (0)