[BUG] [Kotlin] jvm-retrofit2 with Jackson issues
Created by: Sam-Kruglov
Bug Report Checklist
-
Have you provided a full/minimal spec to reproduce the issue? -
Have you validated the input using an OpenAPI validator (example)? -
Have you tested with the latest master to confirm the issue still exists? -
Have you searched for related issues/PRs? -
What's the actual output vs expected output? -
[Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
When generating kotlin retrofit client with Jackson, the ApiClient doesn't compile and other small issues.
Here's what I have:
class ApiClient(
private var baseUrl: String = defaultBasePath,
private val okHttpClientBuilder: OkHttpClient.Builder? = null,
private val serializerBuilder: Builder = Serializer.Builder, // Builder is unresolved, serializerBuilder isn't used in the class
private val okHttpClient: OkHttpClient? = null
) {
...
// in my spec I have 3 requirements: basic, jwt, and oauth2. So, one of these should be fulfilled to access the api
fun setCredentials(username: String, password: String): ApiClient {
apiAuthorizations.values.runOnFirst<Interceptor, HttpBasicAuth> {
setCredentials(username, password)
}
apiAuthorizations.values.runOnFirst<Interceptor, OAuth> {
tokenRequestBuilder.setUsername(username).setPassword(password)
}
return this
}
// this method should not be here, it's already handled above in the setCredentials
// this method should only be present when the is oauth but there is no basic auth
fun setCredentials(username: String, password: String): ApiClient {
apiAuthorizations.values.runOnFirst<Interceptor, OAuth> {
tokenRequestBuilder.setUsername(username).setPassword(password)
}
return this
}
...
}
Also, okhttp version collision, logging-interceptor wants 4.4.0, retrofit wants 3.14.9 (https://github.com/square/retrofit/issues/3384). So, given I fix the interceptor version to 3.14.9 as well, I still have to fix some issues.
ApiKeyAuth.kt
doesn't compile:
...
if ("query" == location) {
var newQuery = request.url.toUri().query // request.url does not exist, request.url() returns okhttp3.HttpUrl
val paramValue = "$paramName=$apiKey"
if (newQuery == null) {
newQuery = paramValue
} else {
newQuery += "&$paramValue"
}
val newUri: URI
try {
val oldUri = request.url.toUri()
newUri = URI(
oldUri.scheme, oldUri.authority,
oldUri.path, newQuery, oldUri.fragment
)
} catch (e: URISyntaxException) {
throw IOException(e)
}
request = request.newBuilder().url(newUri.toURL()).build()
} else ...
...
Here's how it should be (I think):
if ("query" == location) {
val newUrl = request.url().newBuilder().addQueryParameter(paramName, apiKey).build()
request = request.newBuilder().url(newUrl).build()
} else ...
Also, OAuth.kt
doesn't compile because it uses request.url
and request.body
instead of request.url()
and request.body()
.
Also OAuthOkHttpClient.kt
doesn't compile because of request.body
and because it uses a non-existent extension toMediaTypeOrNull()
which I think can be replaced by static call MediaType.parse(...)
.
Also, ApiClient#retrofitBuilder
should really be public to allow customizations.
Also, maybe ApiClient
should be open
for extension? I can extend it if generated by Java.
Also, there are empty files which only contain a package: ByteArrayAdapter.kt
, ResponseExt.kt
.
Also, there is a Serializer.kt
which has a method but it's not used either:
object Serializer {
@JvmStatic
val jacksonObjectMapper: ObjectMapper = jacksonObjectMapper()
.registerModule(Jdk8Module())
.registerModule(JavaTimeModule())
.setSerializationInclusion(JsonInclude.Include.NON_ABSENT)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
}
Also, the resulting code cannot serialize JSON, so I had to add the dependencycompile "com.squareup.retrofit2:converter-jackson:$retrofitVersion"
and this line to the ApiClient
:
private val retrofitBuilder: Retrofit.Builder by lazy {
Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(JacksonConverterFactory.create(Serializer.jacksonObjectMapper)) // I added this line
}
Also, the markdown docs for each API class seem to document all authentication methods at the same time (but oauth is missing):
val apiClient = ApiClient()
apiClient.setCredentials("USERNAME", "PASSWORD")
apiClient.setBearerToken("TOKEN")
val webService = apiClient.createWebservice(...
Also, logging interceptor doesn't do anything by default. Could be refactoring like this (default constructor take care of it):
.addInterceptor(
(if (logger != null) HttpLoggingInterceptor(logger) else HttpLoggingInterceptor()).apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
Also, ApiClient
accepts authName
in specialized constructor even though it is redundant. For example, it asks for username and password, so it already knows that the authentication method is basic and there is no need to explicitly ask for its name.
Also, method ApiClient#createService
accepts java class but it should accept kotlin class for convenience.
Also, this line contains multiple statements a looks weird, should be reformatted:
https://github.com/OpenAPITools/openapi-generator/blob/9e5610488f8b649856a72cd25695fc071b0ca1c4/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/infrastructure/ApiClient.kt.mustache#L106
Links to mustache: ApiClient, ResponseExt.kt, Serializer.kt, ByteArrayAdapter.kt, ApiKeyAuth.kt
And I am not sure if this is relevant but on the Retrofit's homepage they say there is a Jackson converter at com.squareup.retrofit2:converter-jackson
that we could use.
openapi-generator version
latest build from master
Steps to reproduce
openapi-generator generate \
--generator-name kotlin \
...
--library jvm-retrofit2 \
--additional-properties serializationLibrary=jackson
the ...
is ambiguous but I think the only thing that matters here is that it's a combination of retrofit and Jackson but let me know.