[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

@@ -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()
)

View File

@@ -2,8 +2,11 @@ package org.openapitools.client
import io.kotlintest.shouldBe
import io.kotlintest.matchers.numerics.shouldBeGreaterThan
import io.kotlintest.matchers.string.shouldContain
import io.kotlintest.shouldThrow
import io.kotlintest.specs.ShouldSpec
import org.openapitools.client.apis.PetApi
import org.openapitools.client.infrastructure.ClientException
import org.openapitools.client.models.Category
import org.openapitools.client.models.Pet
import org.openapitools.client.models.Tag
@@ -11,8 +14,11 @@ import org.openapitools.client.models.Tag
class PetApiTest : ShouldSpec() {
init {
val petId:Long = 10006
val api = PetApi()
should("add a pet") {
val petId:Long = 10006
val pet = Pet(
id = petId,
name = "kotlin client test",
@@ -20,15 +26,11 @@ class PetApiTest : ShouldSpec() {
category = Category(petId, "test kotlin category"),
tags = arrayOf(Tag(petId, "test kotlin tag"))
)
val api = PetApi()
api.addPet(pet)
}
should("get pet by id") {
val petId: Long = 10006
val api = PetApi()
val result = api.getPetById(petId)
result.id shouldBe (petId)
@@ -42,7 +44,6 @@ class PetApiTest : ShouldSpec() {
}
should("find pet by status") {
val api = PetApi()
val result = api.findPetsByStatus(arrayOf("available"))
result.size.shouldBeGreaterThan(0)
@@ -58,15 +59,12 @@ class PetApiTest : ShouldSpec() {
}
should("update a pet") {
val petId:Long = 10007
val pet = Pet(
id = petId,
name = "kotlin client updatePet",
status = Pet.Status.pending,
photoUrls = arrayOf("http://test_kotlin_unit_test.com")
)
val api = PetApi()
api.updatePet(pet)
// verify updated Pet
@@ -77,14 +75,10 @@ class PetApiTest : ShouldSpec() {
}
//TODO the test fail cause client doesn't support other JSON contentType/Accept
/*
should("update a pet with form") {
val petId:Long = 10007
val name = "kotlin client updatePet with Form"
val status = "pending"
val api = PetApi()
api.updatePetWithForm(petId, name, status)
// verify updated Pet
@@ -94,7 +88,16 @@ class PetApiTest : ShouldSpec() {
result.status shouldBe (Pet.Status.pending)
}
*/
should("delete a pet") {
api.deletePet(petId, "apiKey")
// verify updated Pet
val exception = shouldThrow<ClientException> {
api.getPetById(petId)
}
exception.message?.shouldContain("Pet not found")
}
}