All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kotlin-client.libraries.jvm-okhttp.infrastructure.ApiClient.kt.mustache Maven / Gradle / Ivy

There is a newer version: 7.9.0
Show newest version
package {{packageName}}.infrastructure

{{#supportAndroidApiLevel25AndBelow}}
import android.os.Build
{{/supportAndroidApiLevel25AndBelow}}
import okhttp3.OkHttpClient
import okhttp3.RequestBody
{{#jvm-okhttp3}}
import okhttp3.MediaType
{{/jvm-okhttp3}}
{{#jvm-okhttp4}}
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
{{/jvm-okhttp4}}
import okhttp3.FormBody
{{#jvm-okhttp3}}
import okhttp3.HttpUrl
{{/jvm-okhttp3}}
{{#jvm-okhttp4}}
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
{{/jvm-okhttp4}}
import okhttp3.ResponseBody
{{#jvm-okhttp4}}
import okhttp3.MediaType.Companion.toMediaTypeOrNull
{{/jvm-okhttp4}}
import okhttp3.Request
import okhttp3.Headers
{{#jvm-okhttp4}}
import okhttp3.Headers.Companion.toHeaders
{{/jvm-okhttp4}}
import okhttp3.MultipartBody
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
{{#jvm-okhttp3}}
import okhttp3.internal.Util.EMPTY_REQUEST
{{/jvm-okhttp3}}
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.net.URLConnection
{{^threetenbp}}
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.OffsetDateTime
import java.time.OffsetTime
{{/threetenbp}}
import java.util.Locale
{{#useCoroutines}}
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
{{/useCoroutines}}
{{#kotlinx_serialization}}
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
{{/kotlinx_serialization}}
{{#threetenbp}}
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import org.threeten.bp.OffsetDateTime
import org.threeten.bp.OffsetTime
{{/threetenbp}}
{{#gson}}
import com.google.gson.reflect.TypeToken
{{/gson}}
{{#jackson}}
import com.fasterxml.jackson.core.type.TypeReference
{{/jackson}}
{{#moshi}}
import com.squareup.moshi.adapter
{{/moshi}}

{{#jvm-okhttp4}}
{{#nonPublicApi}}internal {{/nonPublicApi}} val EMPTY_REQUEST: RequestBody = ByteArray(0).toRequestBody()
{{/jvm-okhttp4}}

{{#nonPublicApi}}internal {{/nonPublicApi}}open class ApiClient(val baseUrl: String, val client: OkHttpClient = defaultClient) {
    {{#nonPublicApi}}internal {{/nonPublicApi}}companion object {
        protected const val ContentType = "Content-Type"
        protected const val Accept = "Accept"
        protected const val Authorization = "Authorization"
        protected const val JsonMediaType = "application/json"
        protected const val FormDataMediaType = "multipart/form-data"
        protected const val FormUrlEncMediaType = "application/x-www-form-urlencoded"
        protected const val XmlMediaType = "application/xml"
        protected const val OctetMediaType = "application/octet-stream"

        val apiKey: MutableMap = mutableMapOf()
        val apiKeyPrefix: MutableMap = mutableMapOf()
        var username: String? = null
        var password: String? = null
        var accessToken: String? = null
        const val baseUrlKey = "{{packageName}}.baseUrl"

        @JvmStatic
        val defaultClient: OkHttpClient by lazy {
            builder.build()
        }

        @JvmStatic
        val builder: OkHttpClient.Builder = OkHttpClient.Builder()
    }

    /**
     * Guess Content-Type header from the given file (defaults to "application/octet-stream").
     *
     * @param file The given file
     * @return The guessed Content-Type
     */
    protected fun guessContentTypeFromFile(file: File): String {
        val contentType = URLConnection.guessContentTypeFromName(file.name)
        return contentType ?: "application/octet-stream"
    }

    protected inline fun  requestBody(content: T, mediaType: String?): RequestBody =
        when {
            {{#jvm-okhttp3}}
            content is File -> RequestBody.create(MediaType.parse(mediaType ?: guessContentTypeFromFile(content)), content)
            {{/jvm-okhttp3}}
            {{#jvm-okhttp4}}
            content is File -> content.asRequestBody((mediaType ?: guessContentTypeFromFile(content)).toMediaTypeOrNull())
            {{/jvm-okhttp4}}
            mediaType == FormDataMediaType ->
                MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .apply {
                        // content's type *must* be Map>
                        @Suppress("UNCHECKED_CAST")
                        (content as Map>).forEach { (name, part) ->
                            if (part.body is File) {
                                val partHeaders = part.headers.toMutableMap() +
                                    ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"")
                                {{#jvm-okhttp3}}
                                val fileMediaType = MediaType.parse(guessContentTypeFromFile(part.body))
                                addPart(
                                    Headers.of(partHeaders),
                                    RequestBody.create(fileMediaType, part.body)
                                )
                                {{/jvm-okhttp3}}
                                {{#jvm-okhttp4}}
                                val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull()
                                addPart(
                                    partHeaders.toHeaders(),
                                    part.body.asRequestBody(fileMediaType)
                                )
                                {{/jvm-okhttp4}}
                            } else {
                                val partHeaders = part.headers.toMutableMap() +
                                    ("Content-Disposition" to "form-data; name=\"$name\"")
                                {{#jvm-okhttp3}}
                                addPart(
                                    Headers.of(partHeaders),
                                    RequestBody.create(null, parameterToString(part.body))
                                )
                                {{/jvm-okhttp3}}
                                {{#jvm-okhttp4}}
                                addPart(
                                    partHeaders.toHeaders(),
                                    parameterToString(part.body).toRequestBody(null)
                                )
                                {{/jvm-okhttp4}}
                            }
                        }
                    }.build()
            mediaType == FormUrlEncMediaType -> {
                FormBody.Builder().apply {
                    // content's type *must* be Map>
                    @Suppress("UNCHECKED_CAST")
                    (content as Map>).forEach { (name, part) ->
                        add(name, parameterToString(part.body))
                    }
                }.build()
            }
            mediaType == null || mediaType.startsWith("application/") && mediaType.endsWith("json") ->
            {{#jvm-okhttp3}}
                if (content == null) {
                    EMPTY_REQUEST
                } else {
                    RequestBody.create(
                        {{#moshi}}
                        MediaType.parse(mediaType ?: JsonMediaType), Serializer.moshi.adapter(T::class.java).toJson(content)
                        {{/moshi}}
                        {{#gson}}
                        MediaType.parse(mediaType ?: JsonMediaType), Serializer.gson.toJson(content, T::class.java)
                        {{/gson}}
                        {{#jackson}}
                        MediaType.parse(mediaType ?: JsonMediaType), Serializer.jacksonObjectMapper.writeValueAsString(content)
                        {{/jackson}}
                        {{#kotlinx_serialization}}
                        MediaType.parse(mediaType ?: JsonMediaType), Serializer.kotlinxSerializationJson.encodeToString(content)
                        {{/kotlinx_serialization}}
                    )
                }
            {{/jvm-okhttp3}}
            {{#jvm-okhttp4}}
                if (content == null) {
                    EMPTY_REQUEST
                } else {
                    {{#moshi}}
                    Serializer.moshi.adapter(T::class.java).toJson(content)
                    {{/moshi}}
                    {{#gson}}
                    Serializer.gson.toJson(content, T::class.java)
                    {{/gson}}
                    {{#jackson}}
                    Serializer.jacksonObjectMapper.writeValueAsString(content)
                    {{/jackson}}
                    {{#kotlinx_serialization}}
                    Serializer.kotlinxSerializationJson.encodeToString(content)
                    {{/kotlinx_serialization}}
                        .toRequestBody((mediaType ?: JsonMediaType).toMediaTypeOrNull())
                }
            {{/jvm-okhttp4}}
            mediaType == XmlMediaType -> throw UnsupportedOperationException("xml not currently supported.")
            mediaType == OctetMediaType && content is ByteArray ->
            {{#jvm-okhttp3}}
                RequestBody.create(MediaType.parse(OctetMediaType), content)
            {{/jvm-okhttp3}}
            {{#jvm-okhttp4}}
                content.toRequestBody(OctetMediaType.toMediaTypeOrNull())
            {{/jvm-okhttp4}}
            // TODO: this should be extended with other serializers
            else -> throw UnsupportedOperationException("requestBody currently only supports JSON body, byte body and File body.")
        }

    {{#moshi}}
    @OptIn(ExperimentalStdlibApi::class)
    {{/moshi}}
    protected inline fun  responseBody(body: ResponseBody?, mediaType: String? = JsonMediaType): T? {
        if(body == null) {
            return null
        }
        if (T::class.java == File::class.java) {
            // return tempFile
            {{^supportAndroidApiLevel25AndBelow}}
            // Attention: if you are developing an android app that supports API Level 25 and bellow, please check flag supportAndroidApiLevel25AndBelow in https://openapi-generator.tech/docs/generators/kotlin#config-options
            val tempFile = java.nio.file.Files.createTempFile("tmp.{{packageName}}", null).toFile()
            {{/supportAndroidApiLevel25AndBelow}}
            {{#supportAndroidApiLevel25AndBelow}}
            val tempFile = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                java.nio.file.Files.createTempFile("tmp.net.medicineone.teleconsultationandroid.openapi.openapicommon", null).toFile()
            } else {
                @Suppress("DEPRECATION")
                createTempFile("tmp.net.medicineone.teleconsultationandroid.openapi.openapicommon", null)
            }
            {{/supportAndroidApiLevel25AndBelow}}
            tempFile.deleteOnExit()
            body.byteStream().use { inputStream ->
                tempFile.outputStream().use { tempFileOutputStream ->
                    inputStream.copyTo(tempFileOutputStream)
                }
            }
            return tempFile as T
        }

        return when {
            mediaType == null || (mediaType.startsWith("application/") && mediaType.endsWith("json")) -> {
                val bodyContent = body.string()
                if (bodyContent.isEmpty()) {
                    return null
                }
                {{#moshi}}Serializer.moshi.adapter().fromJson(bodyContent){{/moshi}}{{!
                }}{{#gson}}Serializer.gson.fromJson(bodyContent, (object: TypeToken(){}).getType()){{/gson}}{{!
                }}{{#jackson}}Serializer.jacksonObjectMapper.readValue(bodyContent, object: TypeReference() {}){{/jackson}}{{!
                }}{{#kotlinx_serialization}}Serializer.kotlinxSerializationJson.decodeFromString(bodyContent){{/kotlinx_serialization}}
            }
            mediaType == OctetMediaType -> body.bytes() as? T
            else ->  throw UnsupportedOperationException("responseBody currently only supports JSON body.")
        }
    }

    {{#hasAuthMethods}}
    protected fun  updateAuthParams(requestConfig: RequestConfig) {
        {{#authMethods}}
        {{#isApiKey}}
        {{#isKeyInHeader}}
        if (requestConfig.headers["{{keyParamName}}"].isNullOrEmpty()) {
        {{/isKeyInHeader}}
        {{#isKeyInQuery}}
        if (requestConfig.query["{{keyParamName}}"].isNullOrEmpty()) {
        {{/isKeyInQuery}}
            if (apiKey["{{keyParamName}}"] != null) {
                if (apiKeyPrefix["{{keyParamName}}"] != null) {
                    {{#isKeyInHeader}}
                    requestConfig.headers["{{keyParamName}}"] = apiKeyPrefix["{{keyParamName}}"]!! + " " + apiKey["{{keyParamName}}"]!!
                    {{/isKeyInHeader}}
                    {{#isKeyInQuery}}
                    requestConfig.query["{{keyParamName}}"] = listOf(apiKeyPrefix["{{keyParamName}}"]!! + " " + apiKey["{{keyParamName}}"]!!)
                    {{/isKeyInQuery}}
                } else {
                    {{#isKeyInHeader}}
                    requestConfig.headers["{{keyParamName}}"] = apiKey["{{keyParamName}}"]!!
                    {{/isKeyInHeader}}
                    {{#isKeyInQuery}}
                    requestConfig.query["{{keyParamName}}"] = listOf(apiKey["{{keyParamName}}"]!!)
                    {{/isKeyInQuery}}
                }
            }
        {{#isKeyInQuery}}
        }
        {{/isKeyInQuery}}
        {{#isKeyInHeader}}
        }
        {{/isKeyInHeader}}
        {{/isApiKey}}
        {{#isBasic}}
        {{#isBasicBasic}}
        if (requestConfig.headers[Authorization].isNullOrEmpty()) {
            username?.let { username ->
                password?.let { password ->
                    requestConfig.headers[Authorization] = okhttp3.Credentials.basic(username, password)
                }
            }
        }
        {{/isBasicBasic}}
        {{#isBasicBearer}}
        if (requestConfig.headers[Authorization].isNullOrEmpty()) {
            accessToken?.let { accessToken ->
                requestConfig.headers[Authorization] = "Bearer $accessToken"
            }
        }
        {{/isBasicBearer}}
        {{/isBasic}}
        {{#isOAuth}}
        if (requestConfig.headers[Authorization].isNullOrEmpty()) {
            accessToken?.let { accessToken ->
                requestConfig.headers[Authorization] = "Bearer $accessToken "
            }
        }
        {{/isOAuth}}
        {{/authMethods}}
    }
    {{/hasAuthMethods}}

    protected {{#useCoroutines}}suspend {{/useCoroutines}}inline fun  request(requestConfig: RequestConfig): ApiResponse {
        {{#jvm-okhttp3}}
        val httpUrl = HttpUrl.parse(baseUrl) ?: throw IllegalStateException("baseUrl is invalid.")
        {{/jvm-okhttp3}}
        {{#jvm-okhttp4}}
        val httpUrl = baseUrl.toHttpUrlOrNull() ?: throw IllegalStateException("baseUrl is invalid.")
        {{/jvm-okhttp4}}
        {{#hasAuthMethods}}

        // take authMethod from operation
        updateAuthParams(requestConfig)
        {{/hasAuthMethods}}

        val url = httpUrl.newBuilder()
            .addEncodedPathSegments(requestConfig.path.trimStart('/'))
            .apply {
                requestConfig.query.forEach { query ->
                    query.value.forEach { queryValue ->
                        addQueryParameter(query.key, queryValue)
                    }
                }
            }.build()

        // take content-type/accept from spec or set to default (application/json) if not defined
        if (requestConfig.body != null && requestConfig.headers[ContentType].isNullOrEmpty()) {
            requestConfig.headers[ContentType] = JsonMediaType
        }
        if (requestConfig.headers[Accept].isNullOrEmpty()) {
            requestConfig.headers[Accept] = JsonMediaType
        }
        val headers = requestConfig.headers

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

        val contentType = if (headers[ContentType] != null) {
            // TODO: support multiple contentType options here.
            (headers[ContentType] as String).substringBefore(";").lowercase(Locale.US)
        } else {
            null
        }

        val request = when (requestConfig.method) {
            RequestMethod.DELETE -> Request.Builder().url(url).delete(requestBody(requestConfig.body, contentType))
            RequestMethod.GET -> Request.Builder().url(url)
            RequestMethod.HEAD -> Request.Builder().url(url).head()
            RequestMethod.PATCH -> Request.Builder().url(url).patch(requestBody(requestConfig.body, contentType))
            RequestMethod.PUT -> Request.Builder().url(url).put(requestBody(requestConfig.body, contentType))
            RequestMethod.POST -> Request.Builder().url(url).post(requestBody(requestConfig.body, contentType))
            RequestMethod.OPTIONS -> Request.Builder().url(url).method("OPTIONS", null)
        }.apply {
            headers.forEach { header -> addHeader(header.key, header.value) }
        }.build()

        {{#useCoroutines}}
        val response: Response = suspendCancellableCoroutine { continuation ->
            val call = client.newCall(request)
            continuation.invokeOnCancellation { call.cancel() }
            call.enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    continuation.resumeWithException(e)
                }
                override fun onResponse(call: Call, response: Response) {
                    continuation.resume(response)
                }
            })
        }
        {{/useCoroutines}}
        {{^useCoroutines}}
        val response = client.newCall(request).execute()
        {{/useCoroutines}}

        val accept = response.header(ContentType)?.substringBefore(";")?.lowercase(Locale.US)

        // TODO: handle specific mapping types. e.g. Map>
        @Suppress("UNNECESSARY_SAFE_CALL")
        return response.use {
            when {
                it.isRedirect -> Redirection(
                    it.code{{#jvm-okhttp3}}(){{/jvm-okhttp3}},
                    it.headers{{#jvm-okhttp3}}(){{/jvm-okhttp3}}.toMultimap()
                )
                it.isInformational -> Informational(
                    it.message{{#jvm-okhttp3}}(){{/jvm-okhttp3}},
                    it.code{{#jvm-okhttp3}}(){{/jvm-okhttp3}},
                    it.headers{{#jvm-okhttp3}}(){{/jvm-okhttp3}}.toMultimap()
                )
                it.isSuccessful -> Success(
                    responseBody(it.body{{#jvm-okhttp3}}(){{/jvm-okhttp3}}, accept),
                    it.code{{#jvm-okhttp3}}(){{/jvm-okhttp3}},
                    it.headers{{#jvm-okhttp3}}(){{/jvm-okhttp3}}.toMultimap()
                )
                it.isClientError -> ClientError(
                    it.message{{#jvm-okhttp3}}(){{/jvm-okhttp3}},
                    it.body{{#jvm-okhttp3}}(){{/jvm-okhttp3}}?.string(),
                    it.code{{#jvm-okhttp3}}(){{/jvm-okhttp3}},
                    it.headers{{#jvm-okhttp3}}(){{/jvm-okhttp3}}.toMultimap()
                )
                else -> ServerError(
                    it.message{{#jvm-okhttp3}}(){{/jvm-okhttp3}},
                    it.body{{#jvm-okhttp3}}(){{/jvm-okhttp3}}?.string(),
                    it.code{{#jvm-okhttp3}}(){{/jvm-okhttp3}},
                    it.headers{{#jvm-okhttp3}}(){{/jvm-okhttp3}}.toMultimap()
                )
            }
        }
    }

    protected fun parameterToString(value: Any?): String = when (value) {
        null -> ""
        is Array<*> -> toMultiValue(value, "csv").toString()
        is Iterable<*> -> toMultiValue(value, "csv").toString()
        is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime ->
            parseDateToQueryString(value)
        else -> value.toString()
    }

    protected inline fun  parseDateToQueryString(value : T): String {
        {{#toJson}}
        /*
        .replace("\"", "") converts the json object string to an actual string for the query parameter.
        The moshi or gson adapter allows a more generic solution instead of trying to use a native
        formatter. It also easily allows to provide a simple way to define a custom date format pattern
        inside a gson/moshi adapter.
        */
        {{#moshi}}
        return Serializer.moshi.adapter(T::class.java).toJson(value).replace("\"", "")
        {{/moshi}}
        {{#gson}}
        return Serializer.gson.toJson(value, T::class.java).replace("\"", "")
        {{/gson}}
        {{#jackson}}
        return Serializer.jacksonObjectMapper.writeValueAsString(value).replace("\"", "")
        {{/jackson}}
        {{#kotlinx_serialization}}
        return Serializer.kotlinxSerializationJson.encodeToString(value).replace("\"", "")
        {{/kotlinx_serialization}}
        {{/toJson}}
        {{^toJson}}
        return value.toString()
        {{/toJson}}
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy