[BUG][Kotlin-client] Handling default values of parameters (#12255)

* Bugfix Kotlin-client: Handling default values of parameters

* Adding object
This commit is contained in:
Johan Sjöblom
2022-04-29 14:26:29 +00:00
committed by GitHub
parent b6a8037f62
commit 8950a9a3c0
221 changed files with 12616 additions and 5807 deletions

View File

@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.openapitools.client">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application />
</manifest>

View File

@@ -0,0 +1,198 @@
package org.openapitools.client.apis
import android.content.Context
import com.android.volley.DefaultRetryPolicy
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.BaseHttpStack
import com.android.volley.toolbox.Volley
import java.util.*;
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import com.google.gson.reflect.TypeToken
import org.openapitools.client.request.IRequestFactory
import org.openapitools.client.request.RequestFactory
import org.openapitools.client.infrastructure.CollectionFormats.*
/*
* If you wish to use a custom http stack with your client you
* can pass that to the request queue like:
* Volley.newRequestQueue(context.applicationContext, myCustomHttpStack)
*/
class DefaultApi (
private val context: Context,
private val requestQueue: Lazy<RequestQueue> = lazy(initializer = {
Volley.newRequestQueue(context.applicationContext)
}),
private val requestFactory: IRequestFactory = RequestFactory(),
private val basePath: String = "http://localhost",
private val postProcessors :List <(Request<*>) -> Unit> = listOf()) {
/**
* Tests default values
* Tests default values of different parameters
* @param pi0 (default to 10)
* @param pi1
* @param pn0 (default to 10.0)
* @param pn1
* @param qi0 (optional, default to 10)
* @param qi1 (default to 71)
* @param qi2 (optional)
* @param qi3
* @param qn0 (optional, default to 10.0)
* @param qn1 (default to 71.0)
* @param qn2 (optional)
* @param qn3
* @param hi0 (optional, default to 10)
* @param hi1 (default to 71)
* @param hi2 (optional)
* @param hi3
* @param hn0 (optional, default to 10.0)
* @param hn1 (default to 71.0)
* @param hn2 (optional)
* @param hn3
* @param fi0 (optional, default to 10)
* @param fi1 (default to 71)
* @param fi2 (optional)
* @param fi3
* @param fn0 (optional, default to 10.0)
* @param fn1 (default to 71.0)
* @param fn2 (optional)
* @param fn3
* @return void
*/
suspend fun test(pi0: kotlin.Int = 10, pi1: kotlin.Int, pn0: java.math.BigDecimal = java.math.BigDecimal("10.0"), pn1: java.math.BigDecimal, qi0: kotlin.Int? = 10, qi1: kotlin.Int = 71, qi2: kotlin.Int? = null, qi3: kotlin.Int, qn0: java.math.BigDecimal? = java.math.BigDecimal("10.0"), qn1: java.math.BigDecimal = java.math.BigDecimal("71.0"), qn2: java.math.BigDecimal? = null, qn3: java.math.BigDecimal, hi0: kotlin.Int? = 10, hi1: kotlin.Int = 71, hi2: kotlin.Int? = null, hi3: kotlin.Int, hn0: java.math.BigDecimal? = java.math.BigDecimal("10.0"), hn1: java.math.BigDecimal = java.math.BigDecimal("71.0"), hn2: java.math.BigDecimal? = null, hn3: java.math.BigDecimal, fi0: kotlin.Int? = 10, fi1: kotlin.Int = 71, fi2: kotlin.Int? = null, fi3: kotlin.Int, fn0: java.math.BigDecimal? = java.math.BigDecimal("10.0"), fn1: java.math.BigDecimal = java.math.BigDecimal("71.0"), fn2: java.math.BigDecimal? = null, fn3: java.math.BigDecimal): Unit {
val body: Any? = null
// verify the required parameter 'pi0' is set
// This is probably taken care of by non-null types anyway
requireNotNull(pi0)
// verify the required parameter 'pi1' is set
// This is probably taken care of by non-null types anyway
requireNotNull(pi1)
// verify the required parameter 'pn0' is set
// This is probably taken care of by non-null types anyway
requireNotNull(pn0)
// verify the required parameter 'pn1' is set
// This is probably taken care of by non-null types anyway
requireNotNull(pn1)
// verify the required parameter 'qi1' is set
// This is probably taken care of by non-null types anyway
requireNotNull(qi1)
// verify the required parameter 'qi3' is set
// This is probably taken care of by non-null types anyway
requireNotNull(qi3)
// verify the required parameter 'qn1' is set
// This is probably taken care of by non-null types anyway
requireNotNull(qn1)
// verify the required parameter 'qn3' is set
// This is probably taken care of by non-null types anyway
requireNotNull(qn3)
// verify the required parameter 'hi1' is set
// This is probably taken care of by non-null types anyway
requireNotNull(hi1)
// verify the required parameter 'hi3' is set
// This is probably taken care of by non-null types anyway
requireNotNull(hi3)
// verify the required parameter 'hn1' is set
// This is probably taken care of by non-null types anyway
requireNotNull(hn1)
// verify the required parameter 'hn3' is set
// This is probably taken care of by non-null types anyway
requireNotNull(hn3)
// verify the required parameter 'fi1' is set
// This is probably taken care of by non-null types anyway
requireNotNull(fi1)
// verify the required parameter 'fi3' is set
// This is probably taken care of by non-null types anyway
requireNotNull(fi3)
// verify the required parameter 'fn1' is set
// This is probably taken care of by non-null types anyway
requireNotNull(fn1)
// verify the required parameter 'fn3' is set
// This is probably taken care of by non-null types anyway
requireNotNull(fn3)
val contentTypes : Array<String> = arrayOf("multipart/form-data")
val contentType: String = if (contentTypes.isNotEmpty()) { contentTypes.first() } else { "application/json" }
// Do some work or avoid some work based on what we know about the model,
// before we delegate to a pluggable request factory template
// The request factory template contains only pure code and no templates
// to make it easy to override with your own.
// create path and map variables
val path = "/test".replace("{" + "pi0" + "}", IRequestFactory.escapeString(pi0.toString())).replace("{" + "pi1" + "}", IRequestFactory.escapeString(pi1.toString())).replace("{" + "pn0" + "}", IRequestFactory.escapeString(pn0.toString())).replace("{" + "pn1" + "}", IRequestFactory.escapeString(pn1.toString()));
// form params
val formParams = mapOf<String, String>(
"fi0" to IRequestFactory.parameterToString(fi0),
"fi1" to IRequestFactory.parameterToString(fi1),
"fi2" to IRequestFactory.parameterToString(fi2),
"fi3" to IRequestFactory.parameterToString(fi3),
"fn0" to IRequestFactory.parameterToString(fn0),
"fn1" to IRequestFactory.parameterToString(fn1),
"fn2" to IRequestFactory.parameterToString(fn2),
"fn3" to IRequestFactory.parameterToString(fn3),
)
// TODO: Cater for allowing empty values
// TODO, if its apikey auth, then add the header names here and the hardcoded auth key
// Only support hard coded apikey in query param auth for when we do this first path
val queryParams = mapOf<String, String>(
"qi0" to IRequestFactory.parameterToString(qi0),
"qi1" to IRequestFactory.parameterToString(qi1),
"qi2" to IRequestFactory.parameterToString(qi2),
"qi3" to IRequestFactory.parameterToString(qi3),
"qn0" to IRequestFactory.parameterToString(qn0),
"qn1" to IRequestFactory.parameterToString(qn1),
"qn2" to IRequestFactory.parameterToString(qn2),
"qn3" to IRequestFactory.parameterToString(qn3),
).filter { it.value.isNotEmpty() }
val headerParams: Map<String, String> = mapOf(
"hi0" to IRequestFactory.parameterToString(hi0),
"hi1" to IRequestFactory.parameterToString(hi1),
"hi2" to IRequestFactory.parameterToString(hi2),
"hi3" to IRequestFactory.parameterToString(hi3),
"hn0" to IRequestFactory.parameterToString(hn0),
"hn1" to IRequestFactory.parameterToString(hn1),
"hn2" to IRequestFactory.parameterToString(hn2),
"hn3" to IRequestFactory.parameterToString(hn3),
)
return suspendCoroutine { continuation ->
val responseListener = Response.Listener<Unit> { response ->
continuation.resume(response)
}
val errorListener = Response.ErrorListener { error ->
continuation.resumeWithException(error)
}
val responseType = object : TypeToken<Unit>() {}.type
// Call the correct request builder based on whether we have a return type or a body.
// All other switching on types must be done in code inside the builder
val request: Request<Unit> = requestFactory.build(
Request.Method.POST,
"$basePath$path",
body,
headerParams,
queryParams,
formParams,
contentType,
responseType,
responseListener,
errorListener)
postProcessors.forEach{ it.invoke(request)}
requestQueue.value.add(request)
}
}
}

View File

@@ -0,0 +1,33 @@
package org.openapitools.client.infrastructure
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import com.google.gson.stream.JsonToken.NULL
import java.io.IOException
class ByteArrayAdapter : TypeAdapter<ByteArray>() {
@Throws(IOException::class)
override fun write(out: JsonWriter?, value: ByteArray?) {
if (value == null) {
out?.nullValue()
} else {
out?.value(String(value))
}
}
@Throws(IOException::class)
override fun read(out: JsonReader?): ByteArray? {
out ?: return null
when (out.peek()) {
NULL -> {
out.nextNull()
return null
}
else -> {
return out.nextString().toByteArray()
}
}
}
}

View File

@@ -0,0 +1,56 @@
package org.openapitools.client.infrastructure
class CollectionFormats {
open class CSVParams {
var params: List<String>
constructor(params: List<String>) {
this.params = params
}
constructor(vararg params: String) {
this.params = listOf(*params)
}
override fun toString(): String {
return params.joinToString(",")
}
}
open class SSVParams : CSVParams {
constructor(params: List<String>) : super(params)
constructor(vararg params: String) : super(*params)
override fun toString(): String {
return params.joinToString(" ")
}
}
class TSVParams : CSVParams {
constructor(params: List<String>) : super(params)
constructor(vararg params: String) : super(*params)
override fun toString(): String {
return params.joinToString("\t")
}
}
class PIPESParams : CSVParams {
constructor(params: List<String>) : super(params)
constructor(vararg params: String) : super(*params)
override fun toString(): String {
return params.joinToString("|")
}
}
class SPACEParams : SSVParams()
}

View File

@@ -0,0 +1,28 @@
/**
* Demo
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* Please note:
* This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* Do not edit this file manually.
*/
@file:Suppress(
"ArrayInDataClass",
"EnumEntryName",
"RemoveRedundantQualifierName",
"UnusedImport"
)
package org.openapitools.client.infrastructure
import org.openapitools.client.models.room.*
// TODO ITransformForStorage
interface ITransformForStorage<T> {
fun toRoomModel(): T
}

View File

@@ -0,0 +1,35 @@
package org.openapitools.client.infrastructure
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import com.google.gson.stream.JsonToken.NULL
import java.io.IOException
import java.time.LocalDate
import java.time.format.DateTimeFormatter
class LocalDateAdapter(private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE) : TypeAdapter<LocalDate>() {
@Throws(IOException::class)
override fun write(out: JsonWriter?, value: LocalDate?) {
if (value == null) {
out?.nullValue()
} else {
out?.value(formatter.format(value))
}
}
@Throws(IOException::class)
override fun read(out: JsonReader?): LocalDate? {
out ?: return null
when (out.peek()) {
NULL -> {
out.nextNull()
return null
}
else -> {
return LocalDate.parse(out.nextString(), formatter)
}
}
}
}

View File

@@ -0,0 +1,35 @@
package org.openapitools.client.infrastructure
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import com.google.gson.stream.JsonToken.NULL
import java.io.IOException
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class LocalDateTimeAdapter(private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME) : TypeAdapter<LocalDateTime>() {
@Throws(IOException::class)
override fun write(out: JsonWriter?, value: LocalDateTime?) {
if (value == null) {
out?.nullValue()
} else {
out?.value(formatter.format(value))
}
}
@Throws(IOException::class)
override fun read(out: JsonReader?): LocalDateTime? {
out ?: return null
when (out.peek()) {
NULL -> {
out.nextNull()
return null
}
else -> {
return LocalDateTime.parse(out.nextString(), formatter)
}
}
}
}

View File

@@ -0,0 +1,35 @@
package org.openapitools.client.infrastructure
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import com.google.gson.stream.JsonToken.NULL
import java.io.IOException
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
class OffsetDateTimeAdapter(private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME) : TypeAdapter<OffsetDateTime>() {
@Throws(IOException::class)
override fun write(out: JsonWriter?, value: OffsetDateTime?) {
if (value == null) {
out?.nullValue()
} else {
out?.value(formatter.format(value))
}
}
@Throws(IOException::class)
override fun read(out: JsonReader?): OffsetDateTime? {
out ?: return null
when (out.peek()) {
NULL -> {
out.nextNull()
return null
}
else -> {
return OffsetDateTime.parse(out.nextString(), formatter)
}
}
}
}

View File

@@ -0,0 +1,62 @@
/**
* Demo
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* Please note:
* This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* Do not edit this file manually.
*/
@file:Suppress(
"ArrayInDataClass",
"EnumEntryName",
"RemoveRedundantQualifierName",
"UnusedImport"
)
package org.openapitools.client.models
import com.google.gson.annotations.SerializedName
import org.openapitools.client.models.room.ApaRoomModel
import org.openapitools.client.infrastructure.ITransformForStorage
/**
*
*
* @param i0
* @param n0
* @param i1
* @param n1
*/
data class Apa (
@SerializedName("i0")
val i0: kotlin.Int,
@SerializedName("n0")
val n0: java.math.BigDecimal,
@SerializedName("i1")
val i1: kotlin.Int? = null,
@SerializedName("n1")
val n1: java.math.BigDecimal? = null
): ITransformForStorage<ApaRoomModel> {
companion object { }
override fun toRoomModel(): ApaRoomModel =
ApaRoomModel(roomTableId = 0,
i0 = this.i0,
n0 = this.n0,
i1 = this.i1,
n1 = this.n1,
)
}

View File

@@ -0,0 +1,55 @@
/**
* Demo
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* Please note:
* This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* Do not edit this file manually.
*/
@file:Suppress(
"ArrayInDataClass",
"EnumEntryName",
"RemoveRedundantQualifierName",
"UnusedImport"
)
package org.openapitools.client.models.room
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import org.openapitools.client.models.*
@Entity(tableName = "Apa")
/**
* Room model for
* @param i0
* @param n0
* @param i1
* @param n1
*/
data class ApaRoomModel (
@PrimaryKey(autoGenerate = true) var roomTableId: Int,
var i0: kotlin.Int,
var n0: java.math.BigDecimal,
var i1: kotlin.Int? = null,
var n1: java.math.BigDecimal? = null,
) {
companion object { }
fun toApiModel(): Apa = Apa(
i0 = this.i0,
n0 = this.n0,
i1 = this.i1,
n1 = this.n1,
)
}

View File

@@ -0,0 +1,119 @@
package org.openapitools.client.request
import com.android.volley.NetworkResponse
import com.android.volley.ParseError
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.HttpHeaderParser
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
import java.net.HttpURLConnection
import java.lang.reflect.Type
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.OffsetDateTime
import org.openapitools.client.infrastructure.OffsetDateTimeAdapter
import org.openapitools.client.infrastructure.LocalDateTimeAdapter
import org.openapitools.client.infrastructure.LocalDateAdapter
import org.openapitools.client.infrastructure.ByteArrayAdapter
class GsonRequest<T>(
method: Int,
url: String,
private val body: Any?,
private val headers: Map<String, String>?,
private val params: MutableMap<String, String>?,
private val contentTypeForBody: String?,
private val encodingForParams: String?,
private val gsonAdapters: Map<Type, Any>?,
private val type: Type,
private val listener: Response.Listener<T>,
errorListener: Response.ErrorListener
) : Request<T>(method, url, errorListener) {
val gsonBuilder: GsonBuilder = GsonBuilder()
.registerTypeAdapter(OffsetDateTime::class.java, OffsetDateTimeAdapter())
.registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeAdapter())
.registerTypeAdapter(LocalDate::class.java, LocalDateAdapter())
.registerTypeAdapter(ByteArray::class.java, ByteArrayAdapter())
.apply {
gsonAdapters?.forEach {
this.registerTypeAdapter(it.key, it.value)
}
}
val gson: Gson by lazy {
gsonBuilder.create()
}
private var response: NetworkResponse? = null
override fun deliverResponse(response: T?) {
listener.onResponse(response)
}
override fun getParams(): MutableMap<String, String>? = params ?: super.getParams()
override fun getBodyContentType(): String = contentTypeForBody ?: super.getBodyContentType()
override fun getParamsEncoding(): String = encodingForParams ?: super.getParamsEncoding()
override fun getHeaders(): MutableMap<String, String> {
val combined = HashMap<String, String>()
combined.putAll(super.getHeaders())
if (headers != null) {
combined.putAll(headers)
}
return combined
}
override fun getBody(): ByteArray? {
if (body != null) {
return gson.toJson(body).toByteArray(Charsets.UTF_8)
}
return super.getBody()
}
override fun parseNetworkResponse(response: NetworkResponse?): Response<T> {
return try {
this.response = copyTo(response)
val json = String(
response?.data ?: ByteArray(0),
Charset.forName(HttpHeaderParser.parseCharset(response?.headers))
)
Response.success(
gson.fromJson<T>(json, type),
HttpHeaderParser.parseCacheHeaders(response)
)
} catch (e: UnsupportedEncodingException) {
Response.error(ParseError(e))
} catch (e: JsonSyntaxException) {
Response.error(ParseError(e))
}
}
private fun copyTo(response: NetworkResponse?): NetworkResponse {
return if (response != null) {
NetworkResponse(
response.statusCode,
response.data,
response.notModified,
response.networkTimeMs,
response.allHeaders
)
} else {
// Return an empty response.
NetworkResponse(
HttpURLConnection.HTTP_BAD_METHOD,
ByteArray(0),
false,
0,
emptyList()
)
}
}
}

View File

@@ -0,0 +1,64 @@
package org.openapitools.client.request
import com.android.volley.Request
import com.android.volley.Response
import java.io.UnsupportedEncodingException
import java.lang.reflect.Type
import java.net.URLEncoder
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import java.time.format.DateTimeFormatter
import java.time.OffsetDateTime
import java.time.LocalDate
interface IRequestFactory {
companion object {
/**
* ISO 8601 date time format.
* @see https://en.wikipedia.org/wiki/ISO_8601
*/
fun formatDateTime(datetime: OffsetDateTime) = DateTimeFormatter.ISO_INSTANT.format(datetime)
fun formatDate(date: LocalDate) = DateTimeFormatter.ISO_LOCAL_DATE.format(date)
fun escapeString(str: String): String {
return try {
URLEncoder.encode(str, "UTF-8")
} catch (e: UnsupportedEncodingException) {
str
}
}
fun parameterToString(param: Any?) =
when (param) {
null -> ""
is OffsetDateTime -> formatDateTime(param)
is Collection<*> -> {
val b = StringBuilder()
for (o in param) {
if (b.isNotEmpty()) {
b.append(",")
}
b.append(o.toString())
}
b.toString()
}
else -> param.toString()
}
}
fun <T> build(
method: Int,
url : String,
body: Any?,
headers: Map<String, String>?,
queryParams: Map<String, String>?,
formParams: Map<String, String>?,
contentTypeForBody: String?,
type: Type,
responseListener: Response.Listener<T>,
errorListener: Response.ErrorListener): Request<T>
}

View File

@@ -0,0 +1,62 @@
// Knowing the details of an operation it will produce a call to a Volley Request constructor
package org.openapitools.client.request
import com.android.volley.Request
import com.android.volley.Response
import org.openapitools.client.request.IRequestFactory.Companion.escapeString
import java.lang.reflect.Type
import java.util.Locale
import java.util.UUID
class RequestFactory(private val headerFactories : List<() -> Map<String, String>> = listOf(), private val postProcessors :List <(Request<*>) -> Unit> = listOf(), private val gsonAdapters: Map<Type, Any> = mapOf()): IRequestFactory {
/**
* {@inheritDoc}
*/
@Suppress("UNCHECKED_CAST")
override fun <T> build(
method: Int,
url: String,
body: Any?,
headers: Map<String, String>?,
queryParams: Map<String, String>?,
formParams: Map<String, String>?,
contentTypeForBody: String?,
type: Type,
responseListener: Response.Listener<T>,
errorListener: Response.ErrorListener
): Request<T> {
val afterMarketHeaders = (headers?.toMutableMap() ?: mutableMapOf())
// Factory built and aftermarket
// Merge the after market headers on top of the base ones in case we are overriding per call auth
val allHeaders = headerFactories.fold(afterMarketHeaders) { acc, factory -> (acc + factory.invoke()).toMutableMap() }
// If we decide to support auth parameters in the url, then you will reference them by supplying a url string
// with known variable name refernces in the string. We will then apply
val updatedUrl = if (!queryParams.isNullOrEmpty()) {
queryParams.asSequence().fold("$url?") {acc, param ->
"$acc${escapeString(param.key)}=${escapeString(param.value)}&"
}.trimEnd('&')
} else {
url
}
val request = GsonRequest(
method,
updatedUrl,
body,
allHeaders,
formParams?.toMutableMap(),
contentTypeForBody,
null,
gsonAdapters,
type,
responseListener,
errorListener)
postProcessors.forEach{ it.invoke(request)}
return request
}
}