Skip to content

Commit a447b59

Browse files
authored
[Kotlin] fix #20231, OkHttp client can handle a field with a list of files (#20274)
* feat(issue-20231): Kotlin okhttp client handles correctly fields that are optional with multiple files. * docs(issue-20231): add docstrings * feat(issue-20231): Remove unnecessary test spec * feat(issue-20231): Kotlin okhttp client handles correctly fields that are optional with multiple files. * docs(issue-20231): add docstrings * feat(issue-20231): Remove unnecessary test spec * feat(issue-20231): Remove unnecessary if condition
1 parent dc175c5 commit a447b59

File tree

22 files changed

+1166
-330
lines changed
  • modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure
  • 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

22 files changed

+1166
-330
lines changed

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

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

117+
/**
118+
* Adds a File to a MultipartBody.Builder
119+
* Defined a helper in the requestBody method to not duplicate code
120+
* It will be used when the content is a FormDataMediaType and the body of the PartConfig is a File
121+
*
122+
* @param name The field name to add in the request
123+
* @param headers The headers that are in the PartConfig
124+
* @param file The file that will be added as the field value
125+
* @return The method returns Unit but the new Part is added to the Builder that the extension function is applying on
126+
* @see requestBody
127+
*/
128+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
129+
val partHeaders = headers.toMutableMap() +
130+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
131+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
132+
addPart(
133+
partHeaders.toHeaders(),
134+
file.asRequestBody(fileMediaType)
135+
)
136+
}
137+
138+
/**
139+
* Adds any type to a MultipartBody.Builder
140+
* Defined a helper in the requestBody method to not duplicate code
141+
* It will be used when the content is a FormDataMediaType and the body of the PartConfig is not a File.
142+
*
143+
* @param name The field name to add in the request
144+
* @param headers The headers that are in the PartConfig
145+
* @param obj The field name to add in the request
146+
* @return The method returns Unit but the new Part is added to the Builder that the extension function is applying on
147+
* @see requestBody
148+
*/
149+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
150+
val partHeaders = headers.toMutableMap() +
151+
("Content-Disposition" to "form-data; name=\"$name\"")
152+
addPart(
153+
partHeaders.toHeaders(),
154+
parameterToString(obj).toRequestBody(null)
155+
)
156+
}
157+
117158
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
118159
when {
119160
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -125,21 +166,18 @@ import com.squareup.moshi.adapter
125166
// content's type *must* be Map<String, PartConfig<*>>
126167
@Suppress("UNCHECKED_CAST")
127168
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
128-
if (part.body is File) {
129-
val partHeaders = part.headers.toMutableMap() +
130-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
131-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
132-
addPart(
133-
partHeaders.toHeaders(),
134-
part.body.asRequestBody(fileMediaType)
135-
)
136-
} else {
137-
val partHeaders = part.headers.toMutableMap() +
138-
("Content-Disposition" to "form-data; name=\"$name\"")
139-
addPart(
140-
partHeaders.toHeaders(),
141-
parameterToString(part.body).toRequestBody(null)
142-
)
169+
when (part.body) {
170+
is File -> addPartToMultiPart(name, part.headers, part.body)
171+
is List<*> -> {
172+
part.body.forEach {
173+
if (it is File) {
174+
addPartToMultiPart(name, part.headers, it)
175+
} else {
176+
addPartToMultiPart(name, part.headers, it)
177+
}
178+
}
179+
}
180+
else -> addPartToMultiPart(name, part.headers, part.body)
143181
}
144182
}
145183
}.build()

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

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

88+
/**
89+
* Adds a File to a MultipartBody.Builder
90+
* Defined a helper in the requestBody method to not duplicate code
91+
* It will be used when the content is a FormDataMediaType and the body of the PartConfig is a File
92+
*
93+
* @param name The field name to add in the request
94+
* @param headers The headers that are in the PartConfig
95+
* @param file The file that will be added as the field value
96+
* @return The method returns Unit but the new Part is added to the Builder that the extension function is applying on
97+
* @see requestBody
98+
*/
99+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
100+
val partHeaders = headers.toMutableMap() +
101+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
102+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
103+
addPart(
104+
partHeaders.toHeaders(),
105+
file.asRequestBody(fileMediaType)
106+
)
107+
}
108+
109+
/**
110+
* Adds any type to a MultipartBody.Builder
111+
* Defined a helper in the requestBody method to not duplicate code
112+
* It will be used when the content is a FormDataMediaType and the body of the PartConfig is not a File.
113+
*
114+
* @param name The field name to add in the request
115+
* @param headers The headers that are in the PartConfig
116+
* @param obj The field name to add in the request
117+
* @return The method returns Unit but the new Part is added to the Builder that the extension function is applying on
118+
* @see requestBody
119+
*/
120+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
121+
val partHeaders = headers.toMutableMap() +
122+
("Content-Disposition" to "form-data; name=\"$name\"")
123+
addPart(
124+
partHeaders.toHeaders(),
125+
parameterToString(obj).toRequestBody(null)
126+
)
127+
}
128+
88129
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
89130
when {
90131
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -96,21 +137,18 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
96137
// content's type *must* be Map<String, PartConfig<*>>
97138
@Suppress("UNCHECKED_CAST")
98139
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
99-
if (part.body is File) {
100-
val partHeaders = part.headers.toMutableMap() +
101-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
102-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
103-
addPart(
104-
partHeaders.toHeaders(),
105-
part.body.asRequestBody(fileMediaType)
106-
)
107-
} else {
108-
val partHeaders = part.headers.toMutableMap() +
109-
("Content-Disposition" to "form-data; name=\"$name\"")
110-
addPart(
111-
partHeaders.toHeaders(),
112-
parameterToString(part.body).toRequestBody(null)
113-
)
140+
when (part.body) {
141+
is File -> addPartToMultiPart(name, part.headers, part.body)
142+
is List<*> -> {
143+
part.body.forEach {
144+
if (it is File) {
145+
addPartToMultiPart(name, part.headers, it)
146+
} else {
147+
addPartToMultiPart(name, part.headers, it)
148+
}
149+
}
150+
}
151+
else -> addPartToMultiPart(name, part.headers, part.body)
114152
}
115153
}
116154
}.build()

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

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

88+
/**
89+
* Adds a File to a MultipartBody.Builder
90+
* Defined a helper in the requestBody method to not duplicate code
91+
* It will be used when the content is a FormDataMediaType and the body of the PartConfig is a File
92+
*
93+
* @param name The field name to add in the request
94+
* @param headers The headers that are in the PartConfig
95+
* @param file The file that will be added as the field value
96+
* @return The method returns Unit but the new Part is added to the Builder that the extension function is applying on
97+
* @see requestBody
98+
*/
99+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
100+
val partHeaders = headers.toMutableMap() +
101+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
102+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
103+
addPart(
104+
partHeaders.toHeaders(),
105+
file.asRequestBody(fileMediaType)
106+
)
107+
}
108+
109+
/**
110+
* Adds any type to a MultipartBody.Builder
111+
* Defined a helper in the requestBody method to not duplicate code
112+
* It will be used when the content is a FormDataMediaType and the body of the PartConfig is not a File.
113+
*
114+
* @param name The field name to add in the request
115+
* @param headers The headers that are in the PartConfig
116+
* @param obj The field name to add in the request
117+
* @return The method returns Unit but the new Part is added to the Builder that the extension function is applying on
118+
* @see requestBody
119+
*/
120+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
121+
val partHeaders = headers.toMutableMap() +
122+
("Content-Disposition" to "form-data; name=\"$name\"")
123+
addPart(
124+
partHeaders.toHeaders(),
125+
parameterToString(obj).toRequestBody(null)
126+
)
127+
}
128+
88129
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
89130
when {
90131
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -96,21 +137,18 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
96137
// content's type *must* be Map<String, PartConfig<*>>
97138
@Suppress("UNCHECKED_CAST")
98139
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
99-
if (part.body is File) {
100-
val partHeaders = part.headers.toMutableMap() +
101-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
102-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
103-
addPart(
104-
partHeaders.toHeaders(),
105-
part.body.asRequestBody(fileMediaType)
106-
)
107-
} else {
108-
val partHeaders = part.headers.toMutableMap() +
109-
("Content-Disposition" to "form-data; name=\"$name\"")
110-
addPart(
111-
partHeaders.toHeaders(),
112-
parameterToString(part.body).toRequestBody(null)
113-
)
140+
when (part.body) {
141+
is File -> addPartToMultiPart(name, part.headers, part.body)
142+
is List<*> -> {
143+
part.body.forEach {
144+
if (it is File) {
145+
addPartToMultiPart(name, part.headers, it)
146+
} else {
147+
addPartToMultiPart(name, part.headers, it)
148+
}
149+
}
150+
}
151+
else -> addPartToMultiPart(name, part.headers, part.body)
114152
}
115153
}
116154
}.build()

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

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

88+
/**
89+
* Adds a File to a MultipartBody.Builder
90+
* Defined a helper in the requestBody method to not duplicate code
91+
* It will be used when the content is a FormDataMediaType and the body of the PartConfig is a File
92+
*
93+
* @param name The field name to add in the request
94+
* @param headers The headers that are in the PartConfig
95+
* @param file The file that will be added as the field value
96+
* @return The method returns Unit but the new Part is added to the Builder that the extension function is applying on
97+
* @see requestBody
98+
*/
99+
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
100+
val partHeaders = headers.toMutableMap() +
101+
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
102+
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
103+
addPart(
104+
partHeaders.toHeaders(),
105+
file.asRequestBody(fileMediaType)
106+
)
107+
}
108+
109+
/**
110+
* Adds any type to a MultipartBody.Builder
111+
* Defined a helper in the requestBody method to not duplicate code
112+
* It will be used when the content is a FormDataMediaType and the body of the PartConfig is not a File.
113+
*
114+
* @param name The field name to add in the request
115+
* @param headers The headers that are in the PartConfig
116+
* @param obj The field name to add in the request
117+
* @return The method returns Unit but the new Part is added to the Builder that the extension function is applying on
118+
* @see requestBody
119+
*/
120+
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
121+
val partHeaders = headers.toMutableMap() +
122+
("Content-Disposition" to "form-data; name=\"$name\"")
123+
addPart(
124+
partHeaders.toHeaders(),
125+
parameterToString(obj).toRequestBody(null)
126+
)
127+
}
128+
88129
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
89130
when {
90131
content is ByteArray -> content.toRequestBody((mediaType ?: guessContentTypeFromByteArray(content)).toMediaTypeOrNull())
@@ -96,21 +137,18 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
96137
// content's type *must* be Map<String, PartConfig<*>>
97138
@Suppress("UNCHECKED_CAST")
98139
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
99-
if (part.body is File) {
100-
val partHeaders = part.headers.toMutableMap() +
101-
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
102-
val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
103-
addPart(
104-
partHeaders.toHeaders(),
105-
part.body.asRequestBody(fileMediaType)
106-
)
107-
} else {
108-
val partHeaders = part.headers.toMutableMap() +
109-
("Content-Disposition" to "form-data; name=\"$name\"")
110-
addPart(
111-
partHeaders.toHeaders(),
112-
parameterToString(part.body).toRequestBody(null)
113-
)
140+
when (part.body) {
141+
is File -> addPartToMultiPart(name, part.headers, part.body)
142+
is List<*> -> {
143+
part.body.forEach {
144+
if (it is File) {
145+
addPartToMultiPart(name, part.headers, it)
146+
} else {
147+
addPartToMultiPart(name, part.headers, it)
148+
}
149+
}
150+
}
151+
else -> addPartToMultiPart(name, part.headers, part.body)
114152
}
115153
}
116154
}.build()

0 commit comments

Comments
 (0)