From f402126460e7d01251f9f13ff3213cb69bce7fae Mon Sep 17 00:00:00 2001 From: Bruno Coelho <4brunu@users.noreply.github.com> Date: Tue, 10 Mar 2020 17:04:53 +0000 Subject: [PATCH] [Kotlin][client] fix file upload (#5548) * [kotlin] fix file upload * [kotlin] fix file upload * [kotlin] fix file upload * [kotlin][client] fix jackson integration * [kotlin] fix file upload * [kotlin] fix file upload --- .../main/resources/kotlin-client/api.mustache | 4 +- .../infrastructure/ApiClient.kt.mustache | 102 ++++++++++++++++-- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 84 ++++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- .../org/openapitools/client/apis/PetApi.kt | 8 +- .../client/infrastructure/ApiClient.kt | 75 ++++++++++++- 22 files changed, 856 insertions(+), 89 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/api.mustache index 20070e1b86b..485b0bed9b5 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/api.mustache @@ -32,7 +32,7 @@ import {{packageName}}.infrastructure.toMultiValue @Suppress("UNCHECKED_CAST"){{/returnType}} @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun {{operationId}}({{#allParams}}{{{paramName}}}: {{{dataType}}}{{^required}}?{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{#nullableReturnType}}?{{/nullableReturnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}} { - val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{{paramName}}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to "${{{paramName}}}"{{#hasMore}}, {{/hasMore}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}} + val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{{paramName}}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to {{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}} val localVariableQuery: MultiValueMap = {{^hasQueryParams}}mutableMapOf() {{/hasQueryParams}}{{#hasQueryParams}}mutableMapOf>() .apply { @@ -55,7 +55,7 @@ import {{packageName}}.infrastructure.toMultiValue {{/queryParams}} } {{/hasQueryParams}} - val localVariableHeaders: MutableMap = mutableMapOf({{#hasFormParams}}"Content-Type" to {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{MediaType}}"{{/consumes.0}}{{/hasFormParams}}{{^hasHeaderParams}}){{/hasHeaderParams}}{{#hasHeaderParams}}{{#hasFormParams}}, {{/hasFormParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{{paramName}}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}{{{paramName}}}.toString(){{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}){{/hasHeaderParams}} + val localVariableHeaders: MutableMap = mutableMapOf({{#hasFormParams}}"Content-Type" to {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{/hasFormParams}}{{^hasHeaderParams}}){{/hasHeaderParams}}{{#hasHeaderParams}}{{#hasFormParams}}, {{/hasFormParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{{paramName}}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}{{{paramName}}}.toString(){{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}){{/hasHeaderParams}} val localVariableConfig = RequestConfig( RequestMethod.{{httpMethod}}, "{{path}}"{{#pathParams}}.replace("{"+"{{baseName}}"+"}", "${{{paramName}}}"){{/pathParams}}, diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache index 8913d9e5ae3..3bff3d10d97 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache @@ -22,7 +22,25 @@ import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull {{/jvm-okhttp4}} import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +{{^threetenbp}} +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime +{{/threetenbp}} +{{#threetenbp}} +import org.threeten.bp.LocalDate +import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalTime +import org.threeten.bp.OffsetDateTime +import org.threeten.bp.OffsetTime +{{/threetenbp}} {{#nonPublicApi}}internal {{/nonPublicApi}}open class ApiClient(val baseUrl: String) { {{#nonPublicApi}}internal {{/nonPublicApi}}companion object { @@ -49,6 +67,17 @@ import java.io.File val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { {{#jvm-okhttp3}} @@ -61,12 +90,50 @@ import java.io.File mediaType.toMediaTypeOrNull() ) {{/jvm-okhttp4}} - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).forEach { (key, value) -> + if (value is File) { + val partHeaders = Headers.{{#jvm-okhttp3}}of{{/jvm-okhttp3}}{{#jvm-okhttp4}}headersOf{{/jvm-okhttp4}}( + "Content-Disposition", + "form-data; name=\"$key\"; filename=\"${value.name}\"" + ) + {{#jvm-okhttp3}} + val fileMediaType = MediaType.parse(guessContentTypeFromFile(value)) + addPart(partHeaders, RequestBody.create(fileMediaType, value)) + {{/jvm-okhttp3}} + {{#jvm-okhttp4}} + val fileMediaType = guessContentTypeFromFile(value).toMediaTypeOrNull() + addPart(partHeaders, value.asRequestBody(fileMediaType)) + {{/jvm-okhttp4}} + } else { + val partHeaders = Headers.{{#jvm-okhttp3}}of{{/jvm-okhttp3}}{{#jvm-okhttp4}}headersOf{{/jvm-okhttp4}}( + "Content-Disposition", + "form-data; name=\"$key\"" + ) + addPart( + partHeaders, + {{#jvm-okhttp3}} + RequestBody.create(null, parameterToString(value)) + {{/jvm-okhttp3}} + {{#jvm-okhttp4}} + parameterToString(value).toRequestBody(null) + {{/jvm-okhttp4}} + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -79,7 +146,7 @@ import java.io.File MediaType.parse(mediaType), Serializer.gson.toJson(content, T::class.java) {{/gson}} {{#jackson}} - MediaType.parse(mediaType), Serializer.jackson.toJson(content, T::class.java) + MediaType.parse(mediaType), Serializer.jacksonObjectMapper.writeValueAsString(content) {{/jackson}} ) {{/jvm-okhttp3}} @@ -254,7 +321,26 @@ import java.io.File } } - {{^jackson}} + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { {{#toJson}} /* @@ -269,10 +355,12 @@ import java.io.File {{#gson}} return Serializer.gson.toJson(value, T::class.java).replace("\"", "") {{/gson}} + {{#jackson}} + return Serializer.jacksonObjectMapper.writeValueAsString(value).replace("\"", "") + {{/jackson}} {{/toJson}} {{^toJson}} return value.toString() {{/toJson}} } - {{/jackson}} } diff --git a/samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 2e3b24ae2ea..a2698dc47ea 100644 --- a/samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 7876fe75504..5d64a9add42 100644 --- a/samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -37,17 +46,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,6 +219,26 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { /* .replace("\"", "") converts the json object string to an actual string for the query parameter. diff --git a/samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 2e3b24ae2ea..a2698dc47ea 100644 --- a/samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 3047834f4ef..2c15d73b189 100644 --- a/samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -37,17 +46,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,4 +219,33 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + + protected inline fun parseDateToQueryString(value : T): String { + /* + .replace("\"", "") converts the json object string to an actual string for the query parameter. + The moshi or gson adapter allows a more generic solution instead of trying to use a native + formatter. It also easily allows to provide a simple way to define a custom date format pattern + inside a gson/moshi adapter. + */ + return Serializer.jacksonObjectMapper.writeValueAsString(value).replace("\"", "") + } } diff --git a/samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 97248ed4cfd..d5906cbd656 100644 --- a/samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -293,9 +293,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -336,9 +336,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index feb9aeebbf6..a69de1961da 100644 --- a/samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -37,17 +46,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,6 +219,26 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { return value.toString() } diff --git a/samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 2e3b24ae2ea..a2698dc47ea 100644 --- a/samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 5d3ecd7b31f..e7f366d02cd 100644 --- a/samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -37,17 +46,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,6 +219,26 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { /* .replace("\"", "") converts the json object string to an actual string for the query parameter. diff --git a/samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 51247617d9c..50770e4c745 100644 --- a/samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ internal class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ internal class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index c591f98d389..473b3fd42ce 100644 --- a/samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime internal open class ApiClient(val baseUrl: String) { internal companion object { @@ -37,17 +46,55 @@ internal open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,6 +219,26 @@ internal open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { /* .replace("\"", "") converts the json object string to an actual string for the query parameter. diff --git a/samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index de4ab6dbb83..cf797f67c9f 100644 --- a/samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse? { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 5d3ecd7b31f..e7f366d02cd 100644 --- a/samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -37,17 +46,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,6 +219,26 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { /* .replace("\"", "") converts the json object string to an actual string for the query parameter. diff --git a/samples/client/petstore/kotlin-okhttp3/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-okhttp3/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 2e3b24ae2ea..a2698dc47ea 100644 --- a/samples/client/petstore/kotlin-okhttp3/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-okhttp3/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-okhttp3/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-okhttp3/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 1588da740d4..f60e40309bc 100644 --- a/samples/client/petstore/kotlin-okhttp3/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-okhttp3/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -8,7 +8,16 @@ import okhttp3.FormBody import okhttp3.HttpUrl import okhttp3.ResponseBody import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -35,17 +44,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> RequestBody.create( MediaType.parse(mediaType), content ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).forEach { (key, value) -> + if (value is File) { + val partHeaders = Headers.of( + "Content-Disposition", + "form-data; name=\"$key\"; filename=\"${value.name}\"" + ) + val fileMediaType = MediaType.parse(guessContentTypeFromFile(value)) + addPart(partHeaders, RequestBody.create(fileMediaType, value)) + } else { + val partHeaders = Headers.of( + "Content-Disposition", + "form-data; name=\"$key\"" + ) + addPart( + partHeaders, + RequestBody.create(null, parameterToString(value)) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -170,6 +217,26 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { /* .replace("\"", "") converts the json object string to an actual string for the query parameter. diff --git a/samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 69d1e725e49..93269dced25 100644 --- a/samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 5d3ecd7b31f..e7f366d02cd 100644 --- a/samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -37,17 +46,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,6 +219,26 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { /* .replace("\"", "") converts the json object string to an actual string for the query parameter. diff --git a/samples/client/petstore/kotlin-threetenbp/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-threetenbp/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 2e3b24ae2ea..a2698dc47ea 100644 --- a/samples/client/petstore/kotlin-threetenbp/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-threetenbp/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin-threetenbp/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-threetenbp/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 5d3ecd7b31f..cb540f6ebd7 100644 --- a/samples/client/petstore/kotlin-threetenbp/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-threetenbp/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import org.threeten.bp.LocalDate +import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalTime +import org.threeten.bp.OffsetDateTime +import org.threeten.bp.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -37,17 +46,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,6 +219,26 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { /* .replace("\"", "") converts the json object string to an actual string for the query parameter. diff --git a/samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 2e3b24ae2ea..a2698dc47ea 100644 --- a/samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -291,9 +291,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit { - val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") + val localVariableBody: kotlin.Any? = mapOf("name" to name, "status" to status) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}".replace("{"+"petId"+"}", "$petId"), @@ -334,9 +334,9 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") @Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class) fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: java.io.File?) : ApiResponse { - val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") + val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to additionalMetadata, "file" to file) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "") + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, "/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"), diff --git a/samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 5d3ecd7b31f..e7f366d02cd 100644 --- a/samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,7 +10,16 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.ResponseBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.Headers +import okhttp3.MultipartBody import java.io.File +import java.net.URLConnection +import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime open class ApiClient(val baseUrl: String) { companion object { @@ -37,17 +46,55 @@ open class ApiClient(val baseUrl: String) { val builder: OkHttpClient.Builder = OkHttpClient.Builder() } + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + protected inline fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody = when { content is File -> content.asRequestBody( mediaType.toMediaTypeOrNull() ) - mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> { + mediaType == FormDataMediaType -> { + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).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) + ) + } + } + }.build() + } + mediaType == FormUrlEncMediaType -> { FormBody.Builder().apply { - // content's type *must* be Map + // content's type *must* be Map @Suppress("UNCHECKED_CAST") - (content as Map).forEach { (key, value) -> - add(key, value) + (content as Map).forEach { (key, value) -> + add(key, parameterToString(value)) } }.build() } @@ -172,6 +219,26 @@ open class ApiClient(val baseUrl: String) { } } + protected fun parameterToString(value: Any?): String { + when (value) { + null -> { + return "" + } + is Array<*> -> { + return toMultiValue(value, "csv").toString() + } + is Iterable<*> -> { + return toMultiValue(value, "csv").toString() + } + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime, is Date -> { + return parseDateToQueryString(value) + } + else -> { + return value.toString() + } + } + } + protected inline fun parseDateToQueryString(value : T): String { /* .replace("\"", "") converts the json object string to an actual string for the query parameter.