Skip to content

Commit 1784792

Browse files
committed
feat(issue-20231): Kotlin okhttp client handles correctly fields that are optional with multiple files.
1 parent 423ba67 commit 1784792

File tree

23 files changed

+728
-330
lines changed
  • modules/openapi-generator/src
  • samples/client
    • others/kotlin-jvm-okhttp-parameter-tests/src/main/kotlin/org/openapitools/client/infrastructure
    • petstore
      • kotlin-allOff-discriminator/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-array-simple-string-jvm-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-bigdecimal-default-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-default-values-jvm-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-enum-default-value/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-explicit/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-gson/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-jackson/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-json-request-string/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-jvm-okhttp4-coroutines/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-kotlinx-datetime/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-modelMutable/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-name-parameter-mappings/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-nonpublic/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-nullable/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-string/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-threetenbp/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin-uppercase-enum/src/main/kotlin/org/openapitools/client/infrastructure
      • kotlin/src/main/kotlin/org/openapitools/client/infrastructure

23 files changed

+728
-330
lines changed

modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,26 @@ import com.squareup.moshi.adapter
113113
return contentType ?: "application/octet-stream"
114114
}
115115

116+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
117+
val partHeaders = headers.toMutableMap() +
118+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
119+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
120+
addPart(
121+
partHeaders.toHeaders(),
122+
file.asRequestBody(fileMediaType)
123+
)
124+
}
125+
126+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
127+
if (obj == null) return
128+
val partHeaders = headers.toMutableMap() +
129+
("Content-Disposition" to "form-data; name=\"$name\"")
130+
addPart(
131+
partHeaders.toHeaders(),
132+
parameterToString(obj).toRequestBody(null)
133+
)
134+
}
135+
116136
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
117137
when {
118138
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -124,21 +144,18 @@ import com.squareup.moshi.adapter
124144
// content's type *must* be Map<String, PartConfig<*>>
125145
@Suppress("UNCHECKED_CAST")
126146
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
127-
if (part.body is File) {
128-
val partHeaders = part.headers.toMutableMap() +
129-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
130-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
131-
addPart(
132-
partHeaders.toHeaders(),
133-
part.body.asRequestBody(fileMediaType)
134-
)
135-
} else {
136-
val partHeaders = part.headers.toMutableMap() +
137-
("Content-Disposition" to "form-data; name=\"$name\"")
138-
addPart(
139-
partHeaders.toHeaders(),
140-
parameterToString(part.body).toRequestBody(null)
141-
)
147+
when (part.body) {
148+
is File -> addPartToMultiPart(name, part.headers, part.body)
149+
is List<*> -> {
150+
part.body.forEach {
151+
if (it is File) {
152+
addPartToMultiPart(name, part.headers, it)
153+
} else {
154+
addPartToMultiPart(name, part.headers, it)
155+
}
156+
}
157+
}
158+
else -> addPartToMultiPart(name, part.headers, part.body)
142159
}
143160
}
144161
}.build()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
openapi: 3.1.0
2+
info:
3+
title: XXXXXXX
4+
version: latest
5+
paths:
6+
/multifile/test:
7+
post:
8+
operationId: operation
9+
requestBody:
10+
content:
11+
multipart/form-data:
12+
schema:
13+
type: object
14+
properties:
15+
attachments:
16+
type: array
17+
description: attached files
18+
items:
19+
type: string
20+
format: binary
21+
pet_id:
22+
type: string
23+
description: Pet ID
24+

samples/client/others/kotlin-jvm-okhttp-parameter-tests/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
8484
return contentType ?: "application/octet-stream"
8585
}
8686

87+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
88+
val partHeaders = headers.toMutableMap() +
89+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
90+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
91+
addPart(
92+
partHeaders.toHeaders(),
93+
file.asRequestBody(fileMediaType)
94+
)
95+
}
96+
97+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
98+
if (obj == null) return
99+
val partHeaders = headers.toMutableMap() +
100+
("Content-Disposition" to "form-data; name=\"$name\"")
101+
addPart(
102+
partHeaders.toHeaders(),
103+
parameterToString(obj).toRequestBody(null)
104+
)
105+
}
106+
87107
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
88108
when {
89109
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -95,21 +115,18 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
95115
// content's type *must* be Map<String, PartConfig<*>>
96116
@Suppress("UNCHECKED_CAST")
97117
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
98-
if (part.body is File) {
99-
val partHeaders = part.headers.toMutableMap() +
100-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
101-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
102-
addPart(
103-
partHeaders.toHeaders(),
104-
part.body.asRequestBody(fileMediaType)
105-
)
106-
} else {
107-
val partHeaders = part.headers.toMutableMap() +
108-
("Content-Disposition" to "form-data; name=\"$name\"")
109-
addPart(
110-
partHeaders.toHeaders(),
111-
parameterToString(part.body).toRequestBody(null)
112-
)
118+
when (part.body) {
119+
is File -> addPartToMultiPart(name, part.headers, part.body)
120+
is List<*> -> {
121+
part.body.forEach {
122+
if (it is File) {
123+
addPartToMultiPart(name, part.headers, it)
124+
} else {
125+
addPartToMultiPart(name, part.headers, it)
126+
}
127+
}
128+
}
129+
else -> addPartToMultiPart(name, part.headers, part.body)
113130
}
114131
}
115132
}.build()

samples/client/petstore/kotlin-allOff-discriminator/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
8484
return contentType ?: "application/octet-stream"
8585
}
8686

87+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
88+
val partHeaders = headers.toMutableMap() +
89+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
90+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
91+
addPart(
92+
partHeaders.toHeaders(),
93+
file.asRequestBody(fileMediaType)
94+
)
95+
}
96+
97+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
98+
if (obj == null) return
99+
val partHeaders = headers.toMutableMap() +
100+
("Content-Disposition" to "form-data; name=\"$name\"")
101+
addPart(
102+
partHeaders.toHeaders(),
103+
parameterToString(obj).toRequestBody(null)
104+
)
105+
}
106+
87107
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
88108
when {
89109
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -95,21 +115,18 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
95115
// content's type *must* be Map<String, PartConfig<*>>
96116
@Suppress("UNCHECKED_CAST")
97117
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
98-
if (part.body is File) {
99-
val partHeaders = part.headers.toMutableMap() +
100-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
101-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
102-
addPart(
103-
partHeaders.toHeaders(),
104-
part.body.asRequestBody(fileMediaType)
105-
)
106-
} else {
107-
val partHeaders = part.headers.toMutableMap() +
108-
("Content-Disposition" to "form-data; name=\"$name\"")
109-
addPart(
110-
partHeaders.toHeaders(),
111-
parameterToString(part.body).toRequestBody(null)
112-
)
118+
when (part.body) {
119+
is File -> addPartToMultiPart(name, part.headers, part.body)
120+
is List<*> -> {
121+
part.body.forEach {
122+
if (it is File) {
123+
addPartToMultiPart(name, part.headers, it)
124+
} else {
125+
addPartToMultiPart(name, part.headers, it)
126+
}
127+
}
128+
}
129+
else -> addPartToMultiPart(name, part.headers, part.body)
113130
}
114131
}
115132
}.build()

samples/client/petstore/kotlin-array-simple-string-jvm-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
8484
return contentType ?: "application/octet-stream"
8585
}
8686

87+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
88+
val partHeaders = headers.toMutableMap() +
89+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
90+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
91+
addPart(
92+
partHeaders.toHeaders(),
93+
file.asRequestBody(fileMediaType)
94+
)
95+
}
96+
97+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
98+
if (obj == null) return
99+
val partHeaders = headers.toMutableMap() +
100+
("Content-Disposition" to "form-data; name=\"$name\"")
101+
addPart(
102+
partHeaders.toHeaders(),
103+
parameterToString(obj).toRequestBody(null)
104+
)
105+
}
106+
87107
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
88108
when {
89109
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -95,21 +115,18 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
95115
// content's type *must* be Map<String, PartConfig<*>>
96116
@Suppress("UNCHECKED_CAST")
97117
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
98-
if (part.body is File) {
99-
val partHeaders = part.headers.toMutableMap() +
100-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
101-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
102-
addPart(
103-
partHeaders.toHeaders(),
104-
part.body.asRequestBody(fileMediaType)
105-
)
106-
} else {
107-
val partHeaders = part.headers.toMutableMap() +
108-
("Content-Disposition" to "form-data; name=\"$name\"")
109-
addPart(
110-
partHeaders.toHeaders(),
111-
parameterToString(part.body).toRequestBody(null)
112-
)
118+
when (part.body) {
119+
is File -> addPartToMultiPart(name, part.headers, part.body)
120+
is List<*> -> {
121+
part.body.forEach {
122+
if (it is File) {
123+
addPartToMultiPart(name, part.headers, it)
124+
} else {
125+
addPartToMultiPart(name, part.headers, it)
126+
}
127+
}
128+
}
129+
else -> addPartToMultiPart(name, part.headers, part.body)
113130
}
114131
}
115132
}.build()

samples/client/petstore/kotlin-bigdecimal-default-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
8484
return contentType ?: "application/octet-stream"
8585
}
8686

87+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
88+
val partHeaders = headers.toMutableMap() +
89+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
90+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
91+
addPart(
92+
partHeaders.toHeaders(),
93+
file.asRequestBody(fileMediaType)
94+
)
95+
}
96+
97+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
98+
if (obj == null) return
99+
val partHeaders = headers.toMutableMap() +
100+
("Content-Disposition" to "form-data; name=\"$name\"")
101+
addPart(
102+
partHeaders.toHeaders(),
103+
parameterToString(obj).toRequestBody(null)
104+
)
105+
}
106+
87107
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
88108
when {
89109
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -95,21 +115,18 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
95115
// content's type *must* be Map<String, PartConfig<*>>
96116
@Suppress("UNCHECKED_CAST")
97117
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
98-
if (part.body is File) {
99-
val partHeaders = part.headers.toMutableMap() +
100-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
101-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
102-
addPart(
103-
partHeaders.toHeaders(),
104-
part.body.asRequestBody(fileMediaType)
105-
)
106-
} else {
107-
val partHeaders = part.headers.toMutableMap() +
108-
("Content-Disposition" to "form-data; name=\"$name\"")
109-
addPart(
110-
partHeaders.toHeaders(),
111-
parameterToString(part.body).toRequestBody(null)
112-
)
118+
when (part.body) {
119+
is File -> addPartToMultiPart(name, part.headers, part.body)
120+
is List<*> -> {
121+
part.body.forEach {
122+
if (it is File) {
123+
addPartToMultiPart(name, part.headers, it)
124+
} else {
125+
addPartToMultiPart(name, part.headers, it)
126+
}
127+
}
128+
}
129+
else -> addPartToMultiPart(name, part.headers, part.body)
113130
}
114131
}
115132
}.build()

0 commit comments

Comments
 (0)