swagger-api / swagger-api/swagger-codegen

[Kotlin] Empty POST body causes KotlinNullPointerException

Open
#8,505 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Mustache
Stars
17.8k
Forks
6k
PR merge metrics
No merged PRs in 30d

Description

Description
Swagger-codegen version

2.2.3

Swagger declaration file content or url

https://gist.github.com/NikitoZZZZZ/7d3340b6668a269ad8e8dfe2189abc8e

Command line used for generation

https://gist.github.com/NikitoZZZZZ/6303480bffdfa670db124878bc9e1633

Steps to reproduce

Generated method from my swagger api doc looks like this:

@Suppress("UNCHECKED_CAST")
    fun cancelNotificationUsingPOST(id: kotlin.String) : NotificationResponse {
        val localVariableBody: kotlin.Any? = null
        val localVariableQuery: MultiValueMap = mapOf()
        val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf()
        val localVariableConfig = RequestConfig(
            RequestMethod.POST,
            "/api/v1/notification/{id}/cancel".replace("{"+"id"+"}", "$id"),
            query = localVariableQuery,
            headers = localVariableHeaders
        )
        val response = request<NotificationResponse>(
            localVariableConfig,
            localVariableBody
        )

        return when (response.responseType) {
            ResponseType.Success -> (response as Success<*>).data as NotificationResponse
            ResponseType.Informational -> TODO()
            ResponseType.Redirection -> TODO()
            ResponseType.ClientError -> throw ClientException((response as ClientError<*>).body as? String ?: "Client error")
            ResponseType.ServerError -> throw ServerException((response as ServerError<*>).message ?: "Server error")
            else -> throw kotlin.IllegalStateException("Undefined ResponseType.")
        }
    }

Generated ApiClient class has method request that looks like this:

inline protected fun <reified T: Any?> request(requestConfig: RequestConfig, body : Any? = null): ApiInfrastructureResponse<T?> {
        val httpUrl = HttpUrl.parse(baseUrl) ?: throw IllegalStateException("baseUrl is invalid.")

        var urlBuilder = httpUrl.newBuilder()
                .addPathSegments(requestConfig.path.trimStart('/'))

        requestConfig.query.forEach { k, v ->
            v.forEach { queryValue ->
                urlBuilder = urlBuilder.addQueryParameter(k,queryValue)
            }
        }

        val url = urlBuilder.build()
        val headers = requestConfig.headers + defaultHeaders

        if(headers[ContentType] ?: "" == "") {
            throw kotlin.IllegalStateException("Missing Content-Type header. This is required.")
        }

        if(headers[Accept] ?: "" == "") {
            throw kotlin.IllegalStateException("Missing Accept header. This is required.")
        }

        // TODO: support multiple contentType,accept options here.
        val contentType = (headers[ContentType] as String).substringBefore(";").toLowerCase()
        val accept = (headers[Accept] as String).substringBefore(";").toLowerCase()

        var request : Request.Builder =  when (requestConfig.method) {
            RequestMethod.DELETE -> Request.Builder().url(url).delete()
            RequestMethod.GET -> Request.Builder().url(url)
            RequestMethod.HEAD -> Request.Builder().url(url).head()
            RequestMethod.PATCH -> Request.Builder().url(url).patch(requestBody(body!!, contentType))
            RequestMethod.PUT -> Request.Builder().url(url).put(requestBody(body!!, contentType))
            RequestMethod.POST -> Request.Builder().url(url).post(requestBody(body!!, contentType))
            RequestMethod.OPTIONS -> Request.Builder().url(url).method("OPTIONS", null)
        }

        headers.forEach { header -> request = request.addHeader(header.key, header.value) }

        val realRequest = request.build()
        val response = client.newCall(realRequest).execute()

        // TODO: handle specific mapping types. e.g. Map<int, Class<?>>
        when {
            response.isRedirect -> return Redirection(
                    response.code(),
                    response.headers().toMultimap()
            )
            response.isInformational -> return Informational(
                    response.message(),
                    response.code(),
                    response.headers().toMultimap()
            )
            response.isSuccessful -> return Success(
                    responseBody(response.body(), accept),
                    response.code(),
                    response.headers().toMultimap()
            )
            response.isClientError -> return ClientError(
                    response.body()?.string(),
                    response.code(),
                    response.headers().toMultimap()
            )
            else -> return ServerError(
                    null,
                    response.body()?.string(),
                    response.code(),
                    response.headers().toMultimap()
            )
        }
    }

As you can see variable localVariableBody is set to null in method cancelNotificationUsingPOST because POST method doesn't have a body. So I get KotlinNullPointerException in method request in line RequestMethod.POST -> Request.Builder().url(url).post(requestBody(body!!, contentType)) since parameter body!! is null.

Suggest a fix/enhancement

Change POST body generation from null to empty string(""). WA for now is to set it to "" manually.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the generated cancelNotificationUsingPOST call from the linked command, then inspect the generated ApiClient.request entry point and its POST handling. Done means a POST operation without a declared body no longer throws KotlinNullPointerException and still produces a valid request.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin
Domain
api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.