mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-12-11 08:22:45 +00:00
[kotlin][client] fix encoding of individual parts of a multipart request (#11911)
* [kotlin][client] fix encoding (and Content-Type headers) of individual parts of a multipart request * [kotlin][client] fix incorrect handling of binary downloads * [kotlin][client] update samples
This commit is contained in:
@@ -27,6 +27,7 @@ src/main/kotlin/org/openapitools/client/infrastructure/Errors.kt
|
||||
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt
|
||||
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt
|
||||
src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt
|
||||
src/main/kotlin/org/openapitools/client/infrastructure/PartConfig.kt
|
||||
src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt
|
||||
src/main/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt
|
||||
src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.openapitools.client.infrastructure.ClientError
|
||||
import org.openapitools.client.infrastructure.ServerException
|
||||
import org.openapitools.client.infrastructure.ServerError
|
||||
import org.openapitools.client.infrastructure.MultiValueMap
|
||||
import org.openapitools.client.infrastructure.PartConfig
|
||||
import org.openapitools.client.infrastructure.RequestConfig
|
||||
import org.openapitools.client.infrastructure.RequestMethod
|
||||
import org.openapitools.client.infrastructure.ResponseType
|
||||
@@ -532,7 +533,7 @@ class PetApi(basePath: kotlin.String = defaultBasePath) : ApiClient(basePath) {
|
||||
fun updatePetWithFormWithHttpInfo(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : ApiResponse<Unit?> {
|
||||
val localVariableConfig = updatePetWithFormRequestConfig(petId = petId, name = name, status = status)
|
||||
|
||||
return request<Map<String, Any?>, Unit>(
|
||||
return request<Map<String, PartConfig<*>>, Unit>(
|
||||
localVariableConfig
|
||||
)
|
||||
}
|
||||
@@ -545,8 +546,10 @@ class PetApi(basePath: kotlin.String = defaultBasePath) : ApiClient(basePath) {
|
||||
* @param status Updated status of the pet (optional)
|
||||
* @return RequestConfig
|
||||
*/
|
||||
fun updatePetWithFormRequestConfig(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : RequestConfig<Map<String, Any?>> {
|
||||
val localVariableBody = mapOf("name" to name, "status" to status)
|
||||
fun updatePetWithFormRequestConfig(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : RequestConfig<Map<String, PartConfig<*>>> {
|
||||
val localVariableBody = mapOf(
|
||||
"name" to PartConfig(body = name, headers = mutableMapOf()),
|
||||
"status" to PartConfig(body = status, headers = mutableMapOf()),)
|
||||
val localVariableQuery: MultiValueMap = mutableMapOf()
|
||||
val localVariableHeaders: MutableMap<String, String> = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded")
|
||||
|
||||
@@ -607,7 +610,7 @@ class PetApi(basePath: kotlin.String = defaultBasePath) : ApiClient(basePath) {
|
||||
fun uploadFileWithHttpInfo(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse<ModelApiResponse?> {
|
||||
val localVariableConfig = uploadFileRequestConfig(petId = petId, additionalMetadata = additionalMetadata, file = file)
|
||||
|
||||
return request<Map<String, Any?>, ModelApiResponse>(
|
||||
return request<Map<String, PartConfig<*>>, ModelApiResponse>(
|
||||
localVariableConfig
|
||||
)
|
||||
}
|
||||
@@ -620,8 +623,10 @@ class PetApi(basePath: kotlin.String = defaultBasePath) : ApiClient(basePath) {
|
||||
* @param file file to upload (optional)
|
||||
* @return RequestConfig
|
||||
*/
|
||||
fun uploadFileRequestConfig(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : RequestConfig<Map<String, Any?>> {
|
||||
val localVariableBody = mapOf("additionalMetadata" to additionalMetadata, "file" to file)
|
||||
fun uploadFileRequestConfig(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : RequestConfig<Map<String, PartConfig<*>>> {
|
||||
val localVariableBody = mapOf(
|
||||
"additionalMetadata" to PartConfig(body = additionalMetadata, headers = mutableMapOf()),
|
||||
"file" to PartConfig(body = file, headers = mutableMapOf()),)
|
||||
val localVariableQuery: MultiValueMap = mutableMapOf()
|
||||
val localVariableHeaders: MutableMap<String, String> = mutableMapOf("Content-Type" to "multipart/form-data")
|
||||
localVariableHeaders["Accept"] = "application/json"
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.openapitools.client.infrastructure.ClientError
|
||||
import org.openapitools.client.infrastructure.ServerException
|
||||
import org.openapitools.client.infrastructure.ServerError
|
||||
import org.openapitools.client.infrastructure.MultiValueMap
|
||||
import org.openapitools.client.infrastructure.PartConfig
|
||||
import org.openapitools.client.infrastructure.RequestConfig
|
||||
import org.openapitools.client.infrastructure.RequestMethod
|
||||
import org.openapitools.client.infrastructure.ResponseType
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.openapitools.client.infrastructure.ClientError
|
||||
import org.openapitools.client.infrastructure.ServerException
|
||||
import org.openapitools.client.infrastructure.ServerError
|
||||
import org.openapitools.client.infrastructure.MultiValueMap
|
||||
import org.openapitools.client.infrastructure.PartConfig
|
||||
import org.openapitools.client.infrastructure.RequestConfig
|
||||
import org.openapitools.client.infrastructure.RequestMethod
|
||||
import org.openapitools.client.infrastructure.ResponseType
|
||||
|
||||
@@ -10,6 +10,7 @@ import okhttp3.ResponseBody
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
@@ -65,53 +66,46 @@ open class ApiClient(val baseUrl: String) {
|
||||
return contentType ?: "application/octet-stream"
|
||||
}
|
||||
|
||||
protected inline fun <reified T> requestBody(content: T, mediaType: String = JsonMediaType): RequestBody =
|
||||
protected inline fun <reified T> requestBody(content: T, mediaType: String?): RequestBody =
|
||||
when {
|
||||
content is File -> content.asRequestBody(mediaType.toMediaTypeOrNull())
|
||||
mediaType == FormDataMediaType -> {
|
||||
mediaType == FormDataMediaType ->
|
||||
MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.apply {
|
||||
// content's type *must* be Map<String, Any?>
|
||||
// content's type *must* be Map<String, PartConfig<*>>
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(content as Map<String, Any?>).forEach { (key, value) ->
|
||||
if (value is File) {
|
||||
val partHeaders = Headers.headersOf(
|
||||
"Content-Disposition",
|
||||
"form-data; name=\"$key\"; filename=\"${value.name}\""
|
||||
)
|
||||
val fileMediaType = guessContentTypeFromFile(value).toMediaTypeOrNull()
|
||||
addPart(partHeaders, value.asRequestBody(fileMediaType))
|
||||
} else {
|
||||
val partHeaders = Headers.headersOf(
|
||||
"Content-Disposition",
|
||||
"form-data; name=\"$key\""
|
||||
)
|
||||
addPart(
|
||||
partHeaders,
|
||||
parameterToString(value).toRequestBody(null)
|
||||
)
|
||||
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
|
||||
val contentType = part.headers.remove("Content-Type")
|
||||
val bodies = if (part.body is Iterable<*>) part.body else listOf(part.body)
|
||||
bodies.forEach { body ->
|
||||
val headers = part.headers.toMutableMap() +
|
||||
("Content-Disposition" to "form-data; name=\"$name\"" + if (body is File) "; filename=\"${body.name}\"" else "")
|
||||
addPart(headers.toHeaders(),
|
||||
requestSingleBody(body, contentType))
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
else -> requestSingleBody(content, mediaType)
|
||||
}
|
||||
|
||||
protected inline fun <reified T> requestSingleBody(content: T, mediaType: String?): RequestBody =
|
||||
when {
|
||||
content is File -> content.asRequestBody((mediaType ?: guessContentTypeFromFile(content)).toMediaTypeOrNull())
|
||||
mediaType == FormUrlEncMediaType -> {
|
||||
FormBody.Builder().apply {
|
||||
// content's type *must* be Map<String, Any?>
|
||||
// content's type *must* be Map<String, PartConfig<*>>
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(content as Map<String, Any?>).forEach { (key, value) ->
|
||||
add(key, parameterToString(value))
|
||||
(content as Map<String, PartConfig<*>>).forEach { (name, part) ->
|
||||
add(name, parameterToString(part.body))
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
mediaType.startsWith("application/") && mediaType.endsWith("json") ->
|
||||
mediaType == null || mediaType.startsWith("application/") && mediaType.endsWith("json") ->
|
||||
if (content == null) {
|
||||
EMPTY_REQUEST
|
||||
} else {
|
||||
Serializer.moshi.adapter(T::class.java).toJson(content)
|
||||
.toRequestBody(
|
||||
mediaType.toMediaTypeOrNull()
|
||||
)
|
||||
.toRequestBody((mediaType ?: JsonMediaType).toMediaTypeOrNull())
|
||||
}
|
||||
mediaType == XmlMediaType -> throw UnsupportedOperationException("xml not currently supported.")
|
||||
// TODO: this should be extended with other serializers
|
||||
@@ -123,22 +117,20 @@ open class ApiClient(val baseUrl: String) {
|
||||
if(body == null) {
|
||||
return null
|
||||
}
|
||||
val bodyContent = body.string()
|
||||
if (bodyContent.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
if (T::class.java == File::class.java) {
|
||||
// return tempfile
|
||||
// Attention: if you are developing an android app that supports API Level 25 and bellow, please check flag supportAndroidApiLevel25AndBelow in https://openapi-generator.tech/docs/generators/kotlin#config-options
|
||||
val f = java.nio.file.Files.createTempFile("tmp.org.openapitools.client", null).toFile()
|
||||
f.deleteOnExit()
|
||||
val out = BufferedWriter(FileWriter(f))
|
||||
out.write(bodyContent)
|
||||
out.close()
|
||||
body.byteStream().use { java.nio.file.Files.copy(it, f.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING) }
|
||||
return f as T
|
||||
}
|
||||
val bodyContent = body.string()
|
||||
if (bodyContent.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
return when {
|
||||
mediaType==null || (mediaType.startsWith("application/") && mediaType.endsWith("json")) ->
|
||||
mediaType==null || (mediaType.startsWith("application/") && mediaType.endsWith("json")) ->
|
||||
Serializer.moshi.adapter<T>().fromJson(bodyContent)
|
||||
else -> throw UnsupportedOperationException("responseBody currently only supports JSON body.")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.openapitools.client.infrastructure
|
||||
|
||||
/**
|
||||
* Defines a config object for a given part of a multi-part request.
|
||||
* NOTE: Headers is a Map<String,String> because rfc2616 defines
|
||||
* multi-valued headers as csv-only.
|
||||
*/
|
||||
data class PartConfig<T>(
|
||||
val headers: MutableMap<String, String> = mutableMapOf(),
|
||||
val body: T? = null
|
||||
)
|
||||
Reference in New Issue
Block a user