[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:
Johan Sjöblom 2022-05-04 17:04:20 +00:00 committed by GitHub
parent ee038e7e6c
commit 706791f43f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
183 changed files with 3583 additions and 668 deletions

View File

@ -38,13 +38,17 @@ jobs:
- samples/client/petstore/kotlin-string
- samples/client/petstore/kotlin-threetenbp
- samples/client/petstore/kotlin-uppercase-enum
- samples/client/petstore/kotlin-array-simple-string
- samples/client/petstore/kotlin-bigdecimal-default
- samples/client/petstore/kotlin-default-values-jvm-okhttp3
- samples/client/petstore/kotlin-default-values-jvm-okhttp4
- samples/client/petstore/kotlin-default-values-jvm-retrofit2
- samples/client/petstore/kotlin-default-values-jvm-volley
- samples/client/petstore/kotlin-default-values-multiplatform
- samples/client/petstore/kotlin-array-simple-string-jvm-okhttp3
- samples/client/petstore/kotlin-array-simple-string-jvm-okhttp4
- samples/client/petstore/kotlin-array-simple-string-jvm-volley
- samples/client/petstore/kotlin-array-simple-string-multiplatform
- samples/client/petstore/kotlin-bigdecimal-default-multiplatform
- samples/client/petstore/kotlin-bigdecimal-default-okhttp4
- samples/client/petstore/kotlin-jvm-ktor-jackson
- samples/client/petstore/kotlin-jvm-ktor-gson
steps:

View File

@ -1,6 +1,7 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-array-simple-string
outputDir: samples/client/petstore/kotlin-array-simple-string-jvm-okhttp3
inputSpec: modules/openapi-generator/src/test/resources/3_0/issue_7199_array_simple_string.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
artifactId: kotlin-array-simple-string
artifactId: kotlin-array-simple-string-jvm-okhttp3
library: jvm-okhttp3

View File

@ -0,0 +1,7 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-array-simple-string-jvm-okhttp4
inputSpec: modules/openapi-generator/src/test/resources/3_0/issue_7199_array_simple_string.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
artifactId: kotlin-array-simple-string-jvm-okhttp4
library: jvm-okhttp4

View File

@ -0,0 +1,9 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-array-simple-string-jvm-volley
inputSpec: modules/openapi-generator/src/test/resources/3_0/issue_7199_array_simple_string.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
artifactId: kotlin-array-simple-string-jvm-volley
library: jvm-volley
serializationLibrary: gson
generateRoomModels: false

View File

@ -0,0 +1,7 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-array-simple-string-multiplatform
inputSpec: modules/openapi-generator/src/test/resources/3_0/issue_7199_array_simple_string.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
artifactId: kotlin-array-simple-string-multiplatform
library: multiplatform

View File

@ -66,20 +66,20 @@ import {{packageName}}.infrastructure.toMultiValue
{{#enumVars}}
{{^multiplatform}}
{{#moshi}}
@Json(name = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}),
@Json(name = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}
{{/moshi}}
{{#gson}}
@SerializedName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}),
@SerializedName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}
{{/gson}}
{{#jackson}}
@JsonProperty(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}),
@JsonProperty(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}
{{/jackson}}
{{#kotlinx_serialization}}
@SerialName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}),
@SerialName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}
{{/kotlinx_serialization}}
{{/multiplatform}}
{{#multiplatform}}
@SerialName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}),
@SerialName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}
{{/multiplatform}}
{{/enumVars}}
{{/allowableValues}}
@ -109,7 +109,6 @@ import {{packageName}}.infrastructure.toMultiValue
{{/enumVars}}
{{/allowableValues}}
{{/enumUnknownDefaultCase}}
;
}
{{/isEnum}}

View File

@ -25,14 +25,14 @@ import rx.Observable
import io.reactivex.Single
{{/useRxJava2}}
{{#useRxJava3}}
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.core.Single
{{/useRxJava3}}
{{^returnType}}
{{#useRxJava2}}
import io.reactivex.Completable
{{/useRxJava2}}
{{#useRxJava3}}
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Completable
{{/useRxJava3}}
{{/returnType}}
{{/doNotUseRxAndCoroutines}}

View File

@ -50,8 +50,8 @@ class OAuth(
) : this(
OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes)
) {
setFlow(flow);
authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl);
setFlow(flow)
authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl)
}
fun setFlow(flow: OAuthFlow) {
@ -146,6 +146,6 @@ class OAuth(
throw IOException(e)
}
}
return true;
return true
}
}

View File

@ -100,13 +100,9 @@ import okhttp3.MediaType.Companion.toMediaType
private val defaultClientBuilder: OkHttpClient.Builder by lazy {
OkHttpClient()
.newBuilder()
.addInterceptor(HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
logger?.invoke(message)
}
}).apply {
level = HttpLoggingInterceptor.Level.BODY
})
.addInterceptor(HttpLoggingInterceptor { message -> logger?.invoke(message) }
.apply { level = HttpLoggingInterceptor.Level.BODY }
)
}
init {
@ -125,7 +121,7 @@ import okhttp3.MediaType.Companion.toMediaType
{{#authMethods}}"{{name}}" -> {{#isBasic}}{{#isBasicBasic}}HttpBasicAuth(){{/isBasicBasic}}{{#isBasicBearer}}HttpBearerAuth("{{scheme}}"){{/isBasicBearer}}{{/isBasic}}{{#isApiKey}}ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"){{/isApiKey}}{{#isOAuth}}OAuth(OAuthFlow.{{flow}}, "{{authorizationUrl}}", "{{tokenUrl}}", "{{#scopes}}{{scope}}{{^-last}}, {{/-last}}{{/scopes}}"){{/isOAuth}}{{/authMethods}}
else -> throw RuntimeException("auth name $authName not found in available auth names")
}
addAuthorization(authName, auth);
addAuthorization(authName, auth)
}
}
@ -182,7 +178,7 @@ import okhttp3.MediaType.Companion.toMediaType
{{#isBasicBasic}}
fun setCredentials(username: String, password: String): ApiClient {
apiAuthorizations.values.runOnFirst<Interceptor, HttpBasicAuth> {
setCredentials(username, password);
setCredentials(username, password)
}
{{#hasOAuthMethods}}
apiAuthorizations.values.runOnFirst<Interceptor, OAuth> {
@ -269,7 +265,7 @@ import okhttp3.MediaType.Companion.toMediaType
?.setClientId(clientId)
?.setRedirectURI(redirectURI)
}
return this;
return this
}
/**
@ -281,7 +277,7 @@ import okhttp3.MediaType.Companion.toMediaType
apiAuthorizations.values.runOnFirst<Interceptor, OAuth> {
registerAccessTokenListener(accessTokenListener)
}
return this;
return this
}
{{/hasOAuthMethods}}

View File

@ -7,7 +7,7 @@ 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 java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@ -52,47 +52,34 @@ class {{classname}} (
{{^bodyParam}}
val body: Any? = null
{{/bodyParam}}
{{#allParams}}
{{#required}}
// verify the required parameter '{{paramName}}' is set
// This is probably taken care of by non-null types anyway
requireNotNull({{paramName}})
{{/required}}
{{/allParams}}
val contentTypes : Array<String> = arrayOf({{#consumes}}"{{{mediaType}}}"{{^-last}},{{/-last}}{{/consumes}})
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,
// 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 = "{{{path}}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", IRequestFactory.escapeString({{{paramName}}}.toString())){{/pathParams}};
val path = "{{{path}}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", {{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}IRequestFactory.escapeString({{{paramName}}}.toString()){{/isContainer}}){{/pathParams}}
// form params
val formParams = mapOf<String, String>(
{{#formParams}}
"{{baseName}}" to IRequestFactory.parameterToString({{paramName}}),
{{/formParams}}
)
val formParams = mapOf<String, String>({{^formParams}}){{/formParams}}{{#formParams}}
"{{baseName}}" to IRequestFactory.parameterToString({{paramName}}){{^-last}},{{/-last}}{{#-last}}
){{/-last}}{{/formParams}}
// 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>(
{{#queryParams}}
"{{baseName}}" to IRequestFactory.parameterToString({{paramName}}),
{{/queryParams}}
).filter { it.value.isNotEmpty() }
val queryParams = mapOf<String, String>({{^queryParams}}){{/queryParams}}{{#queryParams}}
"{{baseName}}" to IRequestFactory.parameterToString({{paramName}}){{^-last}},{{/-last}}{{#-last}}
){{/-last}}{{/queryParams}}
.filter { it.value.isNotEmpty() }
val headerParams: Map<String, String> = mapOf(
{{#headerParams}}
"{{baseName}}" to IRequestFactory.parameterToString({{paramName}}),
{{/headerParams}}
)
val headerParams: Map<String, String> = mapOf({{^headerParams}}){{/headerParams}}{{#headerParams}}
"{{baseName}}" to IRequestFactory.parameterToString({{paramName}}){{^-last}},{{/-last}}{{#-last}}
){{/-last}}{{/headerParams}}
return suspendCoroutine { continuation ->
val responseListener = Response.Listener<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}}> { response ->
@ -119,7 +106,7 @@ class {{classname}} (
responseListener,
errorListener)
postProcessors.forEach{ it.invoke(request)}
postProcessors.forEach { it.invoke(request) }
requestQueue.value.add(request)
}

View File

@ -6,17 +6,11 @@ project.version = '{{artifactVersion}}'
buildscript {
ext.kotlin_version = '1.5.10'
ext.swagger_annotations_version = "1.6.2"
ext.gson_version = "2.8.6"
ext.volley_version = "1.2.0"
ext.junit_version = "4.13.2"
ext.robolectric_version = "4.5.1"
ext.concurrent_unit_version = "0.4.6"
repositories {

View File

@ -1,4 +1,4 @@
{{#generateRoomModels}}
android.useAndroidX=true
{{#generateRoomModels}}
android.enableJetifier=true
{{/generateRoomModels}}

View File

@ -60,19 +60,14 @@ import kotlinx.serialization.encoding.*
{{/hasFormParams}}
{{/hasBodyParam}}
val localVariableQuery = mutableMapOf<String, List<String>>()
{{#queryParams}}
{{{paramName}}}?.apply { localVariableQuery["{{baseName}}"] = {{#isContainer}}toMultiValue(this, "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf("${{{paramName}}}"){{/isContainer}} }
{{/queryParams}}
val localVariableHeaders = mutableMapOf<String, String>()
{{#headerParams}}
{{{paramName}}}?.apply { localVariableHeaders["{{baseName}}"] = {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}} }
{{/headerParams}}
val localVariableQuery = mutableMapOf<String, List<String>>(){{#queryParams}}
{{{paramName}}}?.apply { localVariableQuery["{{baseName}}"] = {{#isContainer}}toMultiValue(this, "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf("${{{paramName}}}"){{/isContainer}} }{{/queryParams}}
val localVariableHeaders = mutableMapOf<String, String>(){{#headerParams}}
{{{paramName}}}?.apply { localVariableHeaders["{{baseName}}"] = {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}} }{{/headerParams}}
val localVariableConfig = RequestConfig<kotlin.Any?>(
RequestMethod.{{httpMethod}},
"{{path}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", "${{{paramName}}}"){{/pathParams}},
"{{path}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", {{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}"${{{paramName}}}"{{/isContainer}}){{/pathParams}},
query = localVariableQuery,
headers = localVariableHeaders
)

View File

@ -32,6 +32,6 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "com.squareup.moshi:moshi-kotlin:1.12.0"
implementation "com.squareup.moshi:moshi-adapters:1.12.0"
implementation "com.squareup.okhttp3:okhttp:4.9.1"
implementation "com.squareup.okhttp3:okhttp:3.12.13"
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2"
}

View File

@ -0,0 +1,2 @@
rootProject.name = 'kotlin-array-simple-string-jvm-okhttp3'

View File

@ -2,20 +2,17 @@ package org.openapitools.client.infrastructure
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.MediaType
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.HttpUrl
import okhttp3.ResponseBody
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request
import okhttp3.Headers
import okhttp3.Headers.Companion.toHeaders
import okhttp3.MultipartBody
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import okhttp3.internal.EMPTY_REQUEST
import okhttp3.internal.Util.EMPTY_REQUEST
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
@ -80,7 +77,7 @@ open class ApiClient(val baseUrl: String) {
bodies.forEach { body ->
val headers = part.headers.toMutableMap() +
("Content-Disposition" to "form-data; name=\"$name\"" + if (body is File) "; filename=\"${body.name}\"" else "")
addPart(headers.toHeaders(),
addPart(Headers.of(headers),
requestSingleBody(body, contentType))
}
}
@ -90,7 +87,7 @@ open class ApiClient(val baseUrl: String) {
protected inline fun <reified T> requestSingleBody(content: T, mediaType: String?): RequestBody =
when {
content is File -> content.asRequestBody((mediaType ?: guessContentTypeFromFile(content)).toMediaTypeOrNull())
content is File -> RequestBody.create(MediaType.parse(mediaType ?: guessContentTypeFromFile(content)), content)
mediaType == FormUrlEncMediaType -> {
FormBody.Builder().apply {
// content's type *must* be Map<String, PartConfig<*>>
@ -104,8 +101,9 @@ open class ApiClient(val baseUrl: String) {
if (content == null) {
EMPTY_REQUEST
} else {
Serializer.moshi.adapter(T::class.java).toJson(content)
.toRequestBody((mediaType ?: JsonMediaType).toMediaTypeOrNull())
RequestBody.create(
MediaType.parse(mediaType ?: JsonMediaType), Serializer.moshi.adapter(T::class.java).toJson(content)
)
}
mediaType == XmlMediaType -> throw UnsupportedOperationException("xml not currently supported.")
// TODO: this should be extended with other serializers
@ -138,7 +136,7 @@ open class ApiClient(val baseUrl: String) {
protected inline fun <reified I, reified T: Any?> request(requestConfig: RequestConfig<I>): ApiResponse<T?> {
val httpUrl = baseUrl.toHttpUrlOrNull() ?: throw IllegalStateException("baseUrl is invalid.")
val httpUrl = HttpUrl.parse(baseUrl) ?: throw IllegalStateException("baseUrl is invalid.")
val url = httpUrl.newBuilder()
.addPathSegments(requestConfig.path.trimStart('/'))
@ -189,30 +187,30 @@ open class ApiClient(val baseUrl: String) {
// TODO: handle specific mapping types. e.g. Map<int, Class<?>>
return when {
response.isRedirect -> Redirection(
response.code,
response.headers.toMultimap()
response.code(),
response.headers().toMultimap()
)
response.isInformational -> Informational(
response.message,
response.code,
response.headers.toMultimap()
response.message(),
response.code(),
response.headers().toMultimap()
)
response.isSuccessful -> Success(
responseBody(response.body, accept),
response.code,
response.headers.toMultimap()
responseBody(response.body(), accept),
response.code(),
response.headers().toMultimap()
)
response.isClientError -> ClientError(
response.message,
response.body?.string(),
response.code,
response.headers.toMultimap()
response.message(),
response.body()?.string(),
response.code(),
response.headers().toMultimap()
)
else -> ServerError(
response.message,
response.body?.string(),
response.code,
response.headers.toMultimap()
response.message(),
response.body()?.string(),
response.code(),
response.headers().toMultimap()
)
}
}

View File

@ -5,20 +5,20 @@ import okhttp3.Response
/**
* Provides an extension to evaluation whether the response is a 1xx code
*/
val Response.isInformational : Boolean get() = this.code in 100..199
val Response.isInformational : Boolean get() = this.code() in 100..199
/**
* Provides an extension to evaluation whether the response is a 3xx code
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
val Response.isRedirect : Boolean get() = this.code in 300..399
val Response.isRedirect : Boolean get() = this.code() in 300..399
/**
* Provides an extension to evaluation whether the response is a 4xx code
*/
val Response.isClientError : Boolean get() = this.code in 400..499
val Response.isClientError : Boolean get() = this.code() in 400..499
/**
* Provides an extension to evaluation whether the response is a 5xx (Standard) through 999 (non-standard) code
*/
val Response.isServerError : Boolean get() = this.code in 500..999
val Response.isServerError : Boolean get() = this.code() in 500..999

View File

@ -1,6 +1,5 @@
README.md
build.gradle
docs/Apa.md
docs/DefaultApi.md
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.properties
@ -25,4 +24,3 @@ src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt
src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt
src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt
src/main/kotlin/org/openapitools/client/models/Apa.kt

View File

@ -35,13 +35,12 @@ All URIs are relative to *http://localhost*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*DefaultApi* | [**testPost**](docs/DefaultApi.md#testpost) | **POST** /test |
*DefaultApi* | [**idsGet**](docs/DefaultApi.md#idsget) | **GET** /{ids} |
<a name="documentation-for-models"></a>
## Documentation for Models
- [org.openapitools.client.models.Apa](docs/Apa.md)
<a name="documentation-for-authorization"></a>

View File

@ -4,12 +4,12 @@ All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
[**testPost**](DefaultApi.md#testPost) | **POST** /test |
[**idsGet**](DefaultApi.md#idsGet) | **GET** /{ids} |
<a name="testPost"></a>
# **testPost**
> testPost(apa)
<a name="idsGet"></a>
# **idsGet**
> idsGet(ids)
@ -20,14 +20,14 @@ Method | HTTP request | Description
//import org.openapitools.client.models.*
val apiInstance = DefaultApi()
val apa : Apa = // Apa |
val ids : kotlin.collections.List<kotlin.String> = // kotlin.collections.List<kotlin.String> |
try {
apiInstance.testPost(apa)
apiInstance.idsGet(ids)
} catch (e: ClientException) {
println("4xx response calling DefaultApi#testPost")
println("4xx response calling DefaultApi#idsGet")
e.printStackTrace()
} catch (e: ServerException) {
println("5xx response calling DefaultApi#testPost")
println("5xx response calling DefaultApi#idsGet")
e.printStackTrace()
}
```
@ -36,7 +36,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**apa** | [**Apa**](Apa.md)| |
**ids** | [**kotlin.collections.List&lt;kotlin.String&gt;**](kotlin.String.md)| |
### Return type
@ -48,6 +48,6 @@ No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Content-Type**: Not defined
- **Accept**: Not defined

View File

@ -0,0 +1,2 @@
rootProject.name = 'kotlin-array-simple-string-jvm-okhttp4'

View File

@ -22,7 +22,6 @@ package org.openapitools.client.apis
import java.io.IOException
import org.openapitools.client.models.Apa
import com.squareup.moshi.Json
@ -49,19 +48,19 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath) : ApiClient(basePath
}
/**
*
*
* @param apa
* @return void
* @throws IllegalStateException If the request is not correctly configured
* @throws IOException Rethrows the OkHttp execute method exception
* @throws UnsupportedOperationException If the API returns an informational or redirection response
* @throws ClientException If the API returns a client error response
* @throws ServerException If the API returns a server error response
*/
*
*
* @param ids
* @return void
* @throws IllegalStateException If the request is not correctly configured
* @throws IOException Rethrows the OkHttp execute method exception
* @throws UnsupportedOperationException If the API returns an informational or redirection response
* @throws ClientException If the API returns a client error response
* @throws ServerException If the API returns a server error response
*/
@Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class)
fun testPost(apa: Apa) : Unit {
val localVarResponse = testPostWithHttpInfo(apa = apa)
fun idsGet(ids: kotlin.collections.List<kotlin.String>) : Unit {
val localVarResponse = idsGetWithHttpInfo(ids = ids)
return when (localVarResponse.responseType) {
ResponseType.Success -> Unit
@ -79,37 +78,36 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath) : ApiClient(basePath
}
/**
*
*
* @param apa
* @return ApiResponse<Unit?>
* @throws IllegalStateException If the request is not correctly configured
* @throws IOException Rethrows the OkHttp execute method exception
*/
*
*
* @param ids
* @return ApiResponse<Unit?>
* @throws IllegalStateException If the request is not correctly configured
* @throws IOException Rethrows the OkHttp execute method exception
*/
@Throws(IllegalStateException::class, IOException::class)
fun testPostWithHttpInfo(apa: Apa) : ApiResponse<Unit?> {
val localVariableConfig = testPostRequestConfig(apa = apa)
fun idsGetWithHttpInfo(ids: kotlin.collections.List<kotlin.String>) : ApiResponse<Unit?> {
val localVariableConfig = idsGetRequestConfig(ids = ids)
return request<Apa, Unit>(
return request<Unit, Unit>(
localVariableConfig
)
}
/**
* To obtain the request config of the operation testPost
*
* @param apa
* @return RequestConfig
*/
fun testPostRequestConfig(apa: Apa) : RequestConfig<Apa> {
val localVariableBody = apa
* To obtain the request config of the operation idsGet
*
* @param ids
* @return RequestConfig
*/
fun idsGetRequestConfig(ids: kotlin.collections.List<kotlin.String>) : RequestConfig<Unit> {
val localVariableBody = null
val localVariableQuery: MultiValueMap = mutableMapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
localVariableHeaders["Content-Type"] = "application/json"
return RequestConfig(
method = RequestMethod.POST,
path = "/test",
method = RequestMethod.GET,
path = "/{ids}".replace("{"+"ids"+"}", ids.joinToString(",")),
query = localVariableQuery,
headers = localVariableHeaders,
body = localVariableBody

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,21 @@
README.md
build.gradle
docs/DefaultApi.md
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
settings.gradle
src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt
src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
src/main/kotlin/org/openapitools/client/infrastructure/BigDecimalAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/BigIntegerAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/CollectionFormats.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/ResponseExt.kt
src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt
src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt

View File

@ -0,0 +1,49 @@
# org.openapitools.client - Kotlin client library for Demo
## Requires
* Kotlin 1.4.30
* Gradle 6.8.3
## Build
First, create the gradle wrapper script:
```
gradle wrapper
```
Then, run:
```
./gradlew check assemble
```
This runs all tests and packages the library.
## Features/Implementation Notes
* Supports JSON inputs/outputs, File inputs, and Form inputs.
* Supports collection formats for query parameters: csv, tsv, ssv, pipes.
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions.
* Implementation of ApiClient is intended to reduce method counts, specifically to benefit Android targets.
<a name="documentation-for-api-endpoints"></a>
## Documentation for API Endpoints
All URIs are relative to *http://localhost*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*DefaultApi* | [**idsGet**](docs/DefaultApi.md#idsget) | **GET** {ids} |
<a name="documentation-for-models"></a>
## Documentation for Models
<a name="documentation-for-authorization"></a>
## Documentation for Authorization
All endpoints do not require authorization.

View File

@ -0,0 +1,41 @@
group 'org.openapitools'
version '1.0.0'
wrapper {
gradleVersion = '6.8.3'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}
buildscript {
ext.kotlin_version = '1.5.10'
ext.retrofitVersion = '2.9.0'
repositories {
maven { url "https://repo1.maven.org/maven2" }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
repositories {
maven { url "https://repo1.maven.org/maven2" }
}
test {
useJUnitPlatform()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "com.squareup.moshi:moshi-kotlin:1.12.0"
implementation "com.squareup.moshi:moshi-adapters:1.12.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.1"
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-scalars:$retrofitVersion"
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2"
}

View File

@ -0,0 +1,45 @@
# DefaultApi
All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
[**idsGet**](DefaultApi.md#idsGet) | **GET** {ids} |
### Example
```kotlin
// Import classes:
//import org.openapitools.client.*
//import org.openapitools.client.infrastructure.*
//import org.openapitools.client.models.*
val apiClient = ApiClient()
val webService = apiClient.createWebservice(DefaultApi::class.java)
val ids : kotlin.collections.List<kotlin.String> = // kotlin.collections.List<kotlin.String> |
webService.idsGet(ids)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**ids** | [**kotlin.collections.List&lt;kotlin.String&gt;**](kotlin.String.md)| |
### Return type
null (empty response body)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: Not defined

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,2 @@
rootProject.name = 'kotlin-array-simple-string-jvm-retrofit2'

View File

@ -0,0 +1,22 @@
package org.openapitools.client.apis
import org.openapitools.client.infrastructure.CollectionFormats.*
import retrofit2.http.*
import retrofit2.Call
import okhttp3.RequestBody
interface DefaultApi {
/**
*
*
* Responses:
* - 200: Successful operation
*
* @param ids
* @return [Call]<[Unit]>
*/
@GET("{ids}")
fun idsGet(@Path("ids") ids: kotlin.collections.List<kotlin.String>): Call<Unit>
}

View File

@ -0,0 +1,102 @@
package org.openapitools.client.infrastructure
import okhttp3.Call
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Converter
import retrofit2.converter.scalars.ScalarsConverterFactory
import com.squareup.moshi.Moshi
import retrofit2.converter.moshi.MoshiConverterFactory
class ApiClient(
private var baseUrl: String = defaultBasePath,
private val okHttpClientBuilder: OkHttpClient.Builder? = null,
private val serializerBuilder: Moshi.Builder = Serializer.moshiBuilder,
private val callFactory : Call.Factory? = null,
private val converterFactory: Converter.Factory? = null,
) {
private val apiAuthorizations = mutableMapOf<String, Interceptor>()
var logger: ((String) -> Unit)? = null
private val retrofitBuilder: Retrofit.Builder by lazy {
Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(serializerBuilder.build()))
.apply {
if (converterFactory != null) {
addConverterFactory(converterFactory)
}
}
}
private val clientBuilder: OkHttpClient.Builder by lazy {
okHttpClientBuilder ?: defaultClientBuilder
}
private val defaultClientBuilder: OkHttpClient.Builder by lazy {
OkHttpClient()
.newBuilder()
.addInterceptor(HttpLoggingInterceptor { message -> logger?.invoke(message) }
.apply { level = HttpLoggingInterceptor.Level.BODY }
)
}
init {
normalizeBaseUrl()
}
/**
* Adds an authorization to be used by the client
* @param authName Authentication name
* @param authorization Authorization interceptor
* @return ApiClient
*/
fun addAuthorization(authName: String, authorization: Interceptor): ApiClient {
if (apiAuthorizations.containsKey(authName)) {
throw RuntimeException("auth name $authName already in api authorizations")
}
apiAuthorizations[authName] = authorization
clientBuilder.addInterceptor(authorization)
return this
}
fun setLogger(logger: (String) -> Unit): ApiClient {
this.logger = logger
return this
}
fun <S> createService(serviceClass: Class<S>): S {
val usedCallFactory = this.callFactory ?: clientBuilder.build()
return retrofitBuilder.callFactory(usedCallFactory).build().create(serviceClass)
}
private fun normalizeBaseUrl() {
if (!baseUrl.endsWith("/")) {
baseUrl += "/"
}
}
private inline fun <T, reified U> Iterable<T>.runOnFirst(callback: U.() -> Unit) {
for (element in this) {
if (element is U) {
callback.invoke(element)
break
}
}
}
companion object {
@JvmStatic
protected val baseUrlKey = "org.openapitools.client.baseUrl"
@JvmStatic
val defaultBasePath: String by lazy {
System.getProperties().getProperty(baseUrlKey, "http://localhost")
}
}
}

View File

@ -0,0 +1,17 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.math.BigDecimal
class BigDecimalAdapter {
@ToJson
fun toJson(value: BigDecimal): String {
return value.toPlainString()
}
@FromJson
fun fromJson(value: String): BigDecimal {
return BigDecimal(value)
}
}

View File

@ -0,0 +1,17 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.math.BigInteger
class BigIntegerAdapter {
@ToJson
fun toJson(value: BigInteger): String {
return value.toString()
}
@FromJson
fun fromJson(value: String): BigInteger {
return BigInteger(value)
}
}

View File

@ -0,0 +1,12 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
class ByteArrayAdapter {
@ToJson
fun toJson(data: ByteArray): String = String(data)
@FromJson
fun fromJson(data: String): ByteArray = data.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,19 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.time.LocalDate
import java.time.format.DateTimeFormatter
class LocalDateAdapter {
@ToJson
fun toJson(value: LocalDate): String {
return DateTimeFormatter.ISO_LOCAL_DATE.format(value)
}
@FromJson
fun fromJson(value: String): LocalDate {
return LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE)
}
}

View File

@ -0,0 +1,19 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class LocalDateTimeAdapter {
@ToJson
fun toJson(value: LocalDateTime): String {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(value)
}
@FromJson
fun fromJson(value: String): LocalDateTime {
return LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}
}

View File

@ -0,0 +1,19 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
class OffsetDateTimeAdapter {
@ToJson
fun toJson(value: OffsetDateTime): String {
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(value)
}
@FromJson
fun fromJson(value: String): OffsetDateTime {
return OffsetDateTime.parse(value, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}
}

View File

@ -0,0 +1,16 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import retrofit2.Response
@Throws(JsonDataException::class)
inline fun <reified T> Response<*>.getErrorResponse(serializerBuilder: Moshi.Builder = Serializer.moshiBuilder): T? {
val serializer = serializerBuilder.build()
val parser = serializer.adapter(T::class.java)
val response = errorBody()?.string()
if (response != null) {
return parser.fromJson(response)
}
return null
}

View File

@ -0,0 +1,23 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
object Serializer {
@JvmStatic
val moshiBuilder: Moshi.Builder = Moshi.Builder()
.add(OffsetDateTimeAdapter())
.add(LocalDateTimeAdapter())
.add(LocalDateAdapter())
.add(UUIDAdapter())
.add(ByteArrayAdapter())
.add(URIAdapter())
.add(KotlinJsonAdapterFactory())
.add(BigDecimalAdapter())
.add(BigIntegerAdapter())
@JvmStatic
val moshi: Moshi by lazy {
moshiBuilder.build()
}
}

View File

@ -0,0 +1,13 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.net.URI
class URIAdapter {
@ToJson
fun toJson(uri: URI) = uri.toString()
@FromJson
fun fromJson(s: String): URI = URI.create(s)
}

View File

@ -0,0 +1,13 @@
package org.openapitools.client.infrastructure
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.util.UUID
class UUIDAdapter {
@ToJson
fun toJson(uuid: UUID) = uuid.toString()
@FromJson
fun fromJson(s: String): UUID = UUID.fromString(s)
}

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,20 @@
README.md
build.gradle
docs/DefaultApi.md
gradle.properties
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
settings.gradle
src/main/AndroidManifest.xml
src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt
src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/CollectionFormats.kt
src/main/kotlin/org/openapitools/client/infrastructure/CollectionFormats.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/request/GsonRequest.kt
src/main/kotlin/org/openapitools/client/request/IRequestFactory.kt
src/main/kotlin/org/openapitools/client/request/RequestFactory.kt

View File

@ -0,0 +1,189 @@
# org.openapitools.client - Kotlin client library for Demo
A kotlin client for Android using the currently recommended http client, Volley. See https://developer.android.com/training/volley
- Currently sends GsonRequests
- Currently only supports Gson as a serializer - will throw an exception if a different serializer is chosen
- Defaults the source location to src/main/java as per standard Android builds
## Design
Volley is a queue/request based layer on top of http url stack specific to Android. Android favours dependency injection and
a layered architecture, and IO performed off the main thread to maintain UI responsiveness, with a preferred technique of
kotlin co-routines. The code gen library reflects these factors.
- Api calls use co-routines, and execute them using volley callbacks to avoid tying up a thread.
- Facilitate dependency injection, with default implementations available.
- Generate a requestFactory that can be overridden
- Allow the passing of the RequestFactory per tag (api client) or per operation (an extra parameter is created on operations with non-global security), with per operation auth overriding global security.
- DI scoping of the Request Factory and pre-generated auth header factories allow for thread safe and secure setting of credentials.
- Lazy header factories allow for refreshing tokens etc
- Factoring of header factories to the Request Factory allow ambient provision of credentials. Code gen library is credential storage agnostic.
- Header factories allow the merging of generated headers from open api spec with dynamically added headers
- Injection of http url stack to allow custom http stacks. Default implementation is best practice singleton
- Data classes used for serialisation to reflect volley's preference - an immutable request that once queued can't be tampered with.
- Reuse model class and other jvm common infrastructure
- Optional generation of room database models, and transform methods to these from open api models
- Room and api models can be extended with additional extension properties.
## Future improvements
- Option to generate image requests on certain conditionals e.g content-type gif etc
- Support for kotlin serialization.
- Multi part form parameters and support for file inputs
## Usage
Hilt Dependency injection example - with default values for parameters overridden.
```
@Provides
internal fun provideSomeApi(
context: Context,
restService: IRestService,
configurationService: IConfigurationService,
sessionService: ISessionService
): SomeApi {
return SomeApi(
context = context,
requestQueue = restService.getRequestQueue(),
requestFactory = RequestFactory(listOf(createSessionHeaderFactory(sessionService), createTraceHeaderFactory()),
postProcessors = listOf(retryPolicySetter)),
basePath = configurationService.getBaseUrl()
)
}
```
Here is the constructor so you can see the defaults
```class SomeApi (
val context: Context,
val requestQueue: Lazy<RequestQueue> = lazy(initializer = {
Volley.newRequestQueue(context.applicationContext)
}),
val requestFactory: IRequestFactory = RequestFactory(),
val basePath: String = "https://yourbasepath.from_input_parameter.com/api",
private val postProcessors :List <(Request<*>) -> Unit> = listOf()) {
```
### Overriding defaults
The above constructor for each api allows the following to be customized
- A custom context, so either a singleton request queue or different scope can be created - see
https://developer.android.com/training/volley/requestqueue#singleton
- An overrideable request queue - which in turn can have a custom http url stack passed to it
- An overrideable request factory constructor call, or a request factory that can be overridden by a custom template, with
custom header factory, request post processors and custom gson adapters injected.
#### Overriding request generation
Request generation can be overridden by
- Overriding the entire request factory template
- Supplying custom header factories - methods that take any possible parameters but return a map of headers
- Supplying custom request post processors - methods that take and return the request object
Header factory examples can be found in the auth section, as these are implemented as header factories. eg
```
val basicAuthHeaderFactoryBuilder = { username: String?, password: String? ->
{ mapOf("Authorization" to "Basic " + Base64.encodeToString("${username ?: ""}:${password ?: ""}".toByteArray(), Base64.DEFAULT))}
}
```
In this case it's a lambda function (a factory method) that takes an username and password, and returns a map of headers. Other
generated code will supply the username and password. In this case it results in a map of just one key/value pair, but
it could be multiple. The important part is it's returning a map - and that the surrounding code
will can bind the inputs to it at some point.
Here is a different example that supplies tracing header values
```
/**
* Create a lambda of tracing headers to be injected into an API's [RequestFactory].
*/
private fun createTraceHeaderFactory(): () -> Map<String, String> = {
mapOf(
HttpHeaderType.b3_traceId.rawValue to UUIDExtensions.asTraceId(UUID.randomUUID()),
HttpHeaderType.b3_spanId.rawValue to UUIDExtensions.asSpanId(UUID.randomUUID()),
HttpHeaderType.b3_sampled.rawValue to "1"
)
}
```
Finally a post processor example
```
/**
* Configure a [DefaultRetryPolicy] to be injected into the [RequestFactory] with a maximum number of retries of zero.
*/
private val retryPolicySetter = { request: Request<*> ->
Unit.apply {
request.setRetryPolicy(
DefaultRetryPolicy(
RestService.DEFAULT_TIMEOUT_MS,
0,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
)
}
}
```
### Serialization
#### Gson and Polymorphic types
The GsonRequest object can be passed custom type adapters
```
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, Object>?,
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())
```
## Requires
* Kotlin 1.4.30
* Gradle 6.8.3
## Build
First, create the gradle wrapper script:
```
gradle wrapper
```
Then, run:
```
./gradlew check assemble
```
This runs all tests and packages the library.
<a name="documentation-for-api-endpoints"></a>
## Documentation for API Endpoints
All URIs are relative to *http://localhost*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*DefaultApi* | [**idsGet**](docs/DefaultApi.md#idsget) | **GET** /{ids} |
<a name="documentation-for-models"></a>
## Documentation for Models
<a name="documentation-for-authorization"></a>
## Documentation for Authorization
All endpoints do not require authorization.

View File

@ -0,0 +1,87 @@
buildscript {
ext.kotlin_version = '1.5.10'
ext.swagger_annotations_version = "1.6.2"
ext.gson_version = "2.8.6"
ext.volley_version = "1.2.0"
ext.junit_version = "4.13.2"
ext.robolectric_version = "4.5.1"
ext.concurrent_unit_version = "0.4.6"
repositories {
mavenLocal()
google()
maven {
url 'https://dl.google.com/dl/android/maven2'
}
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:4.0.2'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
// Rename the aar correctly
libraryVariants.all { variant ->
variant.outputs.all { output ->
if (outputFile != null && outputFileName.endsWith('.aar')) {
outputFileName = "${archivesBaseName}-${version}.aar"
}
}
}
testOptions {
unitTests.returnDefaultValues = true
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "io.swagger:swagger-annotations:$swagger_annotations_version"
implementation "com.google.code.gson:gson:$gson_version"
implementation "com.android.volley:volley:${volley_version}"
testImplementation "junit:junit:$junit_version"
testImplementation "org.robolectric:robolectric:${robolectric_version}"
testImplementation "net.jodah:concurrentunit:${concurrent_unit_version}"
}
afterEvaluate {
android.libraryVariants.all { variant ->
def task = project.tasks.create "jar${variant.name.capitalize()}", Jar
task.description = "Create jar artifact for ${variant.name}"
task.dependsOn variant.javaCompile
task.from variant.javaCompile.destinationDir
task.destinationDirectory = project.file("${project.buildDir}/outputs/jar")
task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar"
artifacts.add('archives', task);
}
}

Some files were not shown because too many files have changed in this diff Show More