Skip to content
GitLab
Projects Groups Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
  • O openapi-generator
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 3,476
    • Issues 3,476
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 402
    • Merge requests 402
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Infrastructure Registry
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • OpenAPI Tools
  • openapi-generator
  • Issues
  • #8147
Closed
Open
Issue created Dec 09, 2020 by Administrator@rootContributor3 of 6 checklist items completed3/6 checklist items

[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.ktwhich 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.

Assignee
Assign to
Time tracking