mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-12-19 22:27:07 +00:00
[Bug][Kotlin-client] Can now handle path param of type list (#12244)
* Bugfix Kotlin-client: Can now handle path param of type list for jvm-volley and multiplatform. Also cleaning up generated code * Adding samples to github workflow. Deleting old workflow * Tweaking setup of jvm-volley * Updating samples Co-authored-by: William Cheng <wing328hk@gmail.com>
This commit is contained in:
@@ -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>
|
||||
@@ -0,0 +1,96 @@
|
||||
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()) {
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param ids
|
||||
* @return void
|
||||
*/
|
||||
suspend fun idsGet(ids: kotlin.collections.List<kotlin.String>): Unit {
|
||||
val body: Any? = null
|
||||
|
||||
val contentTypes : Array<String> = arrayOf()
|
||||
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 = "/{ids}".replace("{" + "ids" + "}", ids.joinToString(","))
|
||||
|
||||
val formParams = mapOf<String, String>()
|
||||
|
||||
|
||||
// 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>()
|
||||
.filter { it.value.isNotEmpty() }
|
||||
|
||||
val headerParams: Map<String, String> = mapOf()
|
||||
|
||||
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.GET,
|
||||
"$basePath$path",
|
||||
body,
|
||||
headerParams,
|
||||
queryParams,
|
||||
formParams,
|
||||
contentType,
|
||||
responseType,
|
||||
responseListener,
|
||||
errorListener)
|
||||
|
||||
postProcessors.forEach { it.invoke(request) }
|
||||
|
||||
requestQueue.value.add(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user