[Kotlin] client improvement / remove WildCards in client/server (#2862)

This commit is contained in:
Vincent Devos
2019-05-11 05:42:11 +02:00
committed by William Cheng
parent 9323cad06b
commit ef26ce68d4
58 changed files with 656 additions and 340 deletions

View File

@@ -2,7 +2,7 @@
## Requires
* Kotlin 1.3.20
* Kotlin 1.3.31
* Gradle 4.9
## Build

View File

@@ -7,7 +7,7 @@ wrapper {
}
buildscript {
ext.kotlin_version = '1.3.20'
ext.kotlin_version = '1.3.31'
repositories {
mavenCentral()
@@ -32,7 +32,7 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "com.squareup.moshi:moshi-kotlin:1.5.0"
compile "com.squareup.moshi:moshi-adapters:1.5.0"
compile "com.squareup.okhttp3:okhttp:3.8.0"
compile "org.threeten:threetenbp:1.3.6"
compile "com.squareup.okhttp3:okhttp:3.14.0"
compile "org.threeten:threetenbp:1.3.8"
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.1.0"
}

View File

@@ -14,7 +14,17 @@ package org.openapitools.client.apis
import org.openapitools.client.models.ApiResponse
import org.openapitools.client.models.Pet
import org.openapitools.client.infrastructure.*
import org.openapitools.client.infrastructure.ApiClient
import org.openapitools.client.infrastructure.ClientException
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.RequestConfig
import org.openapitools.client.infrastructure.RequestMethod
import org.openapitools.client.infrastructure.ResponseType
import org.openapitools.client.infrastructure.Success
import org.openapitools.client.infrastructure.toMultiValue
class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiClient(basePath) {
@@ -27,7 +37,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli
fun addPet(body: Pet) : Unit {
val localVariableBody: kotlin.Any? = body
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"/pet",
@@ -58,7 +68,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli
fun deletePet(petId: kotlin.Long, apiKey: kotlin.String?) : Unit {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf("api_key" to apiKey.toString())
val localVariableHeaders: MutableMap<String, String> = mutableMapOf("api_key" to apiKey.toString())
val localVariableConfig = RequestConfig(
RequestMethod.DELETE,
"/pet/{petId}".replace("{"+"petId"+"}", "$petId"),
@@ -89,7 +99,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli
fun findPetsByStatus(status: kotlin.Array<kotlin.String>) : kotlin.Array<Pet> {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf("status" to toMultiValue(status.toList(), "csv"))
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.GET,
"/pet/findByStatus",
@@ -120,7 +130,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli
fun findPetsByTags(tags: kotlin.Array<kotlin.String>) : kotlin.Array<Pet> {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf("tags" to toMultiValue(tags.toList(), "csv"))
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.GET,
"/pet/findByTags",
@@ -151,7 +161,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli
fun getPetById(petId: kotlin.Long) : Pet {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.GET,
"/pet/{petId}".replace("{"+"petId"+"}", "$petId"),
@@ -181,7 +191,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli
fun updatePet(body: Pet) : Unit {
val localVariableBody: kotlin.Any? = body
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.PUT,
"/pet",
@@ -213,7 +223,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli
fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?) : Unit {
val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status")
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf("Content-Type" to "multipart/form-data")
val localVariableHeaders: MutableMap<String, String> = mutableMapOf("Content-Type" to "")
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"/pet/{petId}".replace("{"+"petId"+"}", "$petId"),
@@ -246,7 +256,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli
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 localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf("Content-Type" to "multipart/form-data")
val localVariableHeaders: MutableMap<String, String> = mutableMapOf("Content-Type" to "")
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"/pet/{petId}/uploadImage".replace("{"+"petId"+"}", "$petId"),

View File

@@ -13,7 +13,17 @@ package org.openapitools.client.apis
import org.openapitools.client.models.Order
import org.openapitools.client.infrastructure.*
import org.openapitools.client.infrastructure.ApiClient
import org.openapitools.client.infrastructure.ClientException
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.RequestConfig
import org.openapitools.client.infrastructure.RequestMethod
import org.openapitools.client.infrastructure.ResponseType
import org.openapitools.client.infrastructure.Success
import org.openapitools.client.infrastructure.toMultiValue
class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiClient(basePath) {
@@ -26,7 +36,7 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC
fun deleteOrder(orderId: kotlin.String) : Unit {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.DELETE,
"/store/order/{orderId}".replace("{"+"orderId"+"}", "$orderId"),
@@ -56,7 +66,7 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC
fun getInventory() : kotlin.collections.Map<kotlin.String, kotlin.Int> {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.GET,
"/store/inventory",
@@ -87,7 +97,7 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC
fun getOrderById(orderId: kotlin.Long) : Order {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.GET,
"/store/order/{orderId}".replace("{"+"orderId"+"}", "$orderId"),
@@ -118,7 +128,7 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC
fun placeOrder(body: Order) : Order {
val localVariableBody: kotlin.Any? = body
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"/store/order",

View File

@@ -13,7 +13,17 @@ package org.openapitools.client.apis
import org.openapitools.client.models.User
import org.openapitools.client.infrastructure.*
import org.openapitools.client.infrastructure.ApiClient
import org.openapitools.client.infrastructure.ClientException
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.RequestConfig
import org.openapitools.client.infrastructure.RequestMethod
import org.openapitools.client.infrastructure.ResponseType
import org.openapitools.client.infrastructure.Success
import org.openapitools.client.infrastructure.toMultiValue
class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiClient(basePath) {
@@ -26,7 +36,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl
fun createUser(body: User) : Unit {
val localVariableBody: kotlin.Any? = body
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"/user",
@@ -56,7 +66,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl
fun createUsersWithArrayInput(body: kotlin.Array<User>) : Unit {
val localVariableBody: kotlin.Any? = body
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"/user/createWithArray",
@@ -86,7 +96,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl
fun createUsersWithListInput(body: kotlin.Array<User>) : Unit {
val localVariableBody: kotlin.Any? = body
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"/user/createWithList",
@@ -116,7 +126,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl
fun deleteUser(username: kotlin.String) : Unit {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.DELETE,
"/user/{username}".replace("{"+"username"+"}", "$username"),
@@ -147,7 +157,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl
fun getUserByName(username: kotlin.String) : User {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.GET,
"/user/{username}".replace("{"+"username"+"}", "$username"),
@@ -179,7 +189,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl
fun loginUser(username: kotlin.String, password: kotlin.String) : kotlin.String {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf("username" to listOf("$username"), "password" to listOf("$password"))
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.GET,
"/user/login",
@@ -208,7 +218,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl
fun logoutUser() : Unit {
val localVariableBody: kotlin.Any? = null
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.GET,
"/user/logout",
@@ -239,7 +249,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl
fun updateUser(username: kotlin.String, body: User) : Unit {
val localVariableBody: kotlin.Any? = body
val localVariableQuery: MultiValueMap = mapOf()
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
val localVariableConfig = RequestConfig(
RequestMethod.PUT,
"/user/{username}".replace("{"+"username"+"}", "$username"),

View File

@@ -3,9 +3,15 @@ package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import okhttp3.*
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.MediaType
import okhttp3.FormBody
import okhttp3.HttpUrl
import okhttp3.ResponseBody
import okhttp3.Request
import java.io.File
import java.util.*
import java.util.UUID
open class ApiClient(val baseUrl: String) {
companion object {
@@ -13,6 +19,7 @@ open class ApiClient(val baseUrl: String) {
protected const val Accept = "Accept"
protected const val JsonMediaType = "application/json"
protected const val FormDataMediaType = "multipart/form-data"
protected const val FormUrlEncMediaType = "application/x-www-form-urlencoded"
protected const val XmlMediaType = "application/xml"
@JvmStatic
@@ -22,38 +29,38 @@ open class ApiClient(val baseUrl: String) {
@JvmStatic
val builder: OkHttpClient.Builder = OkHttpClient.Builder()
@JvmStatic
var defaultHeaders: Map<String, String> by ApplicationDelegates.setOnce(mapOf(ContentType to JsonMediaType, Accept to JsonMediaType))
@JvmStatic
val jsonHeaders: Map<String, String> = mapOf(ContentType to JsonMediaType, Accept to JsonMediaType)
}
protected inline fun <reified T> requestBody(content: T, mediaType: String = JsonMediaType): RequestBody =
when {
content is File -> RequestBody.create(
MediaType.parse(mediaType), content
)
mediaType == FormDataMediaType -> {
var builder = FormBody.Builder()
// content's type *must* be Map<String, Any>
@Suppress("UNCHECKED_CAST")
(content as Map<String,String>).forEach { key, value ->
builder = builder.add(key, value)
}
builder.build()
}
mediaType == JsonMediaType -> RequestBody.create(
MediaType.parse(mediaType), Serializer.moshi.adapter(T::class.java).toJson(content)
)
mediaType == XmlMediaType -> TODO("xml not currently supported.")
// TODO: this should be extended with other serializers
else -> TODO("requestBody currently only supports JSON body and File body.")
}
when {
content is File -> RequestBody.create(
MediaType.parse(mediaType), content
)
mediaType == FormDataMediaType || mediaType == FormUrlEncMediaType -> {
var builder = FormBody.Builder()
// content's type *must* be Map<String, Any>
@Suppress("UNCHECKED_CAST")
(content as Map<String,String>).forEach { key, value ->
builder = builder.add(key, value)
}
builder.build()
}
mediaType == JsonMediaType -> RequestBody.create(
MediaType.parse(mediaType), Serializer.moshi.adapter(T::class.java).toJson(content)
)
mediaType == XmlMediaType -> TODO("xml not currently supported.")
// TODO: this should be extended with other serializers
else -> TODO("requestBody currently only supports JSON body and File body.")
}
protected inline fun <reified T: Any?> responseBody(body: ResponseBody?, mediaType: String = JsonMediaType): T? {
if(body == null) return null
protected inline fun <reified T: Any?> responseBody(body: ResponseBody?, mediaType: String? = JsonMediaType): T? {
if(body == null) {
return null
}
val bodyContent = body.string()
if (bodyContent.length == 0) {
return null
}
return when(mediaType) {
JsonMediaType -> Moshi.Builder().add(object {
@ToJson
@@ -62,8 +69,8 @@ open class ApiClient(val baseUrl: String) {
fun fromJson(s: String) = UUID.fromString(s)
})
.add(ByteArrayAdapter())
.build().adapter(T::class.java).fromJson(body.source())
else -> TODO()
.build().adapter(T::class.java).fromJson(bodyContent)
else -> TODO("responseBody currently only supports JSON body.")
}
}
@@ -80,7 +87,15 @@ open class ApiClient(val baseUrl: String) {
}
val url = urlBuilder.build()
val headers = requestConfig.headers + defaultHeaders
// take content-type/accept from spec or set to default (application/json) if not defined
if (requestConfig.headers[ContentType].isNullOrEmpty()) {
requestConfig.headers.put(ContentType, JsonMediaType)
}
if (requestConfig.headers[Accept].isNullOrEmpty()) {
requestConfig.headers.put(Accept, JsonMediaType)
}
val headers = requestConfig.headers
if(headers[ContentType] ?: "" == "") {
throw kotlin.IllegalStateException("Missing Content-Type header. This is required.")
@@ -90,9 +105,8 @@ open class ApiClient(val baseUrl: String) {
throw kotlin.IllegalStateException("Missing Accept header. This is required.")
}
// TODO: support multiple contentType,accept options here.
// TODO: support multiple contentType options here.
val contentType = (headers[ContentType] as String).substringBefore(";").toLowerCase()
val accept = (headers[Accept] as String).substringBefore(";").toLowerCase()
var request : Request.Builder = when (requestConfig.method) {
RequestMethod.DELETE -> Request.Builder().url(url).delete()
@@ -108,6 +122,7 @@ open class ApiClient(val baseUrl: String) {
val realRequest = request.build()
val response = client.newCall(realRequest).execute()
val accept = response.header(ContentType)?.substringBefore(";")?.toLowerCase()
// TODO: handle specific mapping types. e.g. Map<int, Class<?>>
when {

View File

@@ -11,6 +11,6 @@ package org.openapitools.client.infrastructure
data class RequestConfig(
val method: RequestMethod,
val path: String,
val headers: Map<String, String> = mapOf(),
val headers: MutableMap<String, String> = mutableMapOf(),
val query: Map<String, List<String>> = mapOf()
)