Skip to content

update ktor multiplatform serialization issue #14045

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@ private void processJVMKtorLibrary(final String infrastructureFolder) {
supportingFiles.add(new SupportingFile("infrastructure/ApiAbstractions.kt.mustache", infrastructureFolder, "ApiAbstractions.kt"));
supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt"));
supportingFiles.add(new SupportingFile("infrastructure/HttpResponse.kt.mustache", infrastructureFolder, "HttpResponse.kt"));
supportingFiles.add(new SupportingFile("infrastructure/HttpResponseExtensions.kt.mustache", infrastructureFolder, "HttpResponseExtensions.kt"));
supportingFiles.add(new SupportingFile("infrastructure/Errors.kt.mustache", infrastructureFolder, "Errors.kt"));
supportingFiles.add(new SupportingFile("infrastructure/RequestConfig.kt.mustache", infrastructureFolder, "RequestConfig.kt"));
supportingFiles.add(new SupportingFile("infrastructure/RequestMethod.kt.mustache", infrastructureFolder, "RequestMethod.kt"));

Expand All @@ -669,6 +671,8 @@ private void processJVMKtorLibrary(final String infrastructureFolder) {
supportingFiles.add(new SupportingFile("auth/HttpBasicAuth.kt.mustache", authFolder, "HttpBasicAuth.kt"));
supportingFiles.add(new SupportingFile("auth/HttpBearerAuth.kt.mustache", authFolder, "HttpBearerAuth.kt"));
supportingFiles.add(new SupportingFile("auth/OAuth.kt.mustache", authFolder, "OAuth.kt"));

addSupportingSerializerAdapters(infrastructureFolder);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ apply plugin: 'kotlinx-serialization'
{{#idea}}
apply plugin: 'idea'
{{/idea}}
apply plugin: 'maven-publish'

repositories {
maven { url "https://repo1.maven.org/maven2" }
Expand Down Expand Up @@ -113,6 +112,7 @@ dependencies {
implementation "io.ktor:ktor-client-jackson:$ktor_version"
implementation "io.ktor:ktor-serialization-jackson:$ktor_version"
{{/jackson}}
implementation "io.ktor:ktor-client-cio:$ktor_version"
{{/jvm-ktor}}
{{#jvm-okhttp3}}
implementation "com.squareup.okhttp3:okhttp:3.12.13"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,22 @@ import org.threeten.bp.format.DateTimeFormatter

@FromJson
fun fromJson(value: String): OffsetDateTime {
return OffsetDateTime.parse(value, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
return OffsetDateTime.parse(tryFormatDateTime(value), DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

private fun tryFormatDateTime(stringValue: String): String {
val offset = stringValue.substringAfter("+")
return when (offset.length) {
4 -> fixOffsetValue(stringValue, offset)
else -> stringValue
}
}

private fun fixOffsetValue(stringValue: String = "0000", offset: String): String {
val offsetChars = offset.toCharArray().toMutableList()
offsetChars.add(2, ':')
return stringValue.replaceAfter("+", offsetChars.joinToString(""))
}
}
{{/moshi}}
{{#gson}}
Expand All @@ -58,16 +71,28 @@ import org.threeten.bp.format.DateTimeFormatter
override fun read(out: JsonReader?): OffsetDateTime? {
out ?: return null

when (out.peek()) {
return when (out.peek()) {
NULL -> {
out.nextNull()
return null
}
else -> {
return OffsetDateTime.parse(out.nextString(), formatter)
}
else -> OffsetDateTime.parse(tryFormatDateTime(out.nextString()), formatter)
}
}

private fun tryFormatDateTime(stringValue: String): String {
val offset = stringValue.substringAfter("+")
return when (offset.length) {
4 -> fixOffsetValue(stringValue, offset)
else -> stringValue
}
}

private fun fixOffsetValue(stringValue: String = "0000", offset: String): String {
val offsetChars = offset.toCharArray().toMutableList()
offsetChars.add(2, ':')
return stringValue.replaceAfter("+", offsetChars.joinToString(""))
}
}
{{/gson}}
{{#kotlinx_serialization}}
Expand All @@ -80,7 +105,21 @@ import org.threeten.bp.format.DateTimeFormatter
}

override fun deserialize(decoder: Decoder): OffsetDateTime {
return OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME)
return OffsetDateTime.parse(tryFormatDateTime(decoder.decodeString()), DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

private fun tryFormatDateTime(stringValue: String): String {
val offset = stringValue.substringAfter("+")
return when (offset.length) {
4 -> fixOffsetValue(stringValue, offset)
else -> stringValue
}
}

private fun fixOffsetValue(stringValue: String = "0000", offset: String): String {
val offsetChars = offset.toCharArray().toMutableList()
offsetChars.add(2, ':')
return stringValue.replaceAfter("+", offsetChars.joinToString(""))
}
}
{{/kotlinx_serialization}}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ import io.ktor.http.content.PartData
import io.ktor.http.encodeURLQueryComponent
import io.ktor.http.encodedPath
import io.ktor.http.takeFrom
import io.ktor.http.ContentType.Application
{{#gson}}
import io.ktor.serialization.gson.*
import com.google.gson.GsonBuilder
import java.text.DateFormat
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.OffsetDateTime
{{/gson}}
{{#jackson}}
import io.ktor.serialization.jackson.*
Expand Down Expand Up @@ -85,6 +89,10 @@ import {{packageName}}.auth.*
{{#gson}}
val JSON_DEFAULT : GsonBuilder.() -> Unit = {
setDateFormat(DateFormat.LONG)
registerTypeAdapter(OffsetDateTime::class.java, OffsetDateTimeAdapter())
registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeAdapter())
registerTypeAdapter(LocalDate::class.java, LocalDateAdapter())
registerTypeAdapter(ByteArray::class.java, ByteArrayAdapter())
setPrettyPrinting()
}
{{/gson}}
Expand Down Expand Up @@ -196,6 +204,7 @@ import {{packageName}}.auth.*
this.method = requestConfig.method.httpMethod
headers.filter { header -> !UNSAFE_HEADERS.contains(header.key) }.forEach { header -> this.header(header.key, header.value) }
if (requestConfig.method in listOf(RequestMethod.PUT, RequestMethod.POST, RequestMethod.PATCH)) {
this.contentType(Application.Json)
setBody(body)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@file:Suppress("unused")
package {{packageName}}.infrastructure

import java.lang.RuntimeException

{{#nonPublicApi}}internal {{/nonPublicApi}}open class ClientException(message: kotlin.String? = null, val statusCode: Int = -1, val response: io.ktor.client.statement.HttpResponse? = null) : RuntimeException(message) {

{{#nonPublicApi}}internal {{/nonPublicApi}}companion object {
private const val serialVersionUID: Long = 123L
}
}

{{#nonPublicApi}}internal {{/nonPublicApi}}open class ServerException(message: kotlin.String? = null, val statusCode: Int = -1, val response: io.ktor.client.statement.HttpResponse? = null) : RuntimeException(message) {

{{#nonPublicApi}}internal {{/nonPublicApi}}companion object {
private const val serialVersionUID: Long = 456L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package {{packageName}}.infrastructure

import io.ktor.http.Headers
import io.ktor.http.isSuccess
import io.ktor.util.InternalAPI
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import io.ktor.utils.io.readUTF8Line


{{#nonPublicApi}}internal {{/nonPublicApi}}open class HttpResponse<T : Any>(val response: io.ktor.client.statement.HttpResponse, val provider: BodyProvider<T>) {
val status: Int = response.status.value
Expand All @@ -26,10 +29,15 @@ import io.ktor.util.reflect.typeInfo
suspend fun <V : Any> typedBody(response: io.ktor.client.statement.HttpResponse, type: TypeInfo): V
}

@OptIn(InternalAPI::class)
{{#nonPublicApi}}internal {{/nonPublicApi}}class TypedBodyProvider<T : Any>(private val type: TypeInfo) : BodyProvider<T> {
@Suppress("UNCHECKED_CAST")
override suspend fun body(response: io.ktor.client.statement.HttpResponse): T =
response.call.body(type) as T
override suspend fun body(response: io.ktor.client.statement.HttpResponse): T = when {
response.status.isSuccess() -> response.call.body(type) as T
response.isClientError -> throw ClientException("Client error : ${response.status.value} ${response.content.readUTF8Line()}", response.status.value, response)
response.isServerError -> throw ServerException("Server error : ${response.status.value} ${response.content.readUTF8Line()}", response.status.value, response)
else -> throw RuntimeException()
}

@Suppress("UNCHECKED_CAST")
override suspend fun <V : Any> typedBody(response: io.ktor.client.statement.HttpResponse, type: TypeInfo): V =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package {{packageName}}.infrastructure

import io.ktor.client.statement.HttpResponse

/**
* Provides an extension to evaluation whether the response is a 1xx code
*/
{{#nonPublicApi}}internal {{/nonPublicApi}}val HttpResponse.isInformational : Boolean get() = this.status.value in 100..199

/**
* Provides an extension to evaluation whether the response is a 3xx code
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
{{#nonPublicApi}}internal {{/nonPublicApi}}val HttpResponse.isRedirect : Boolean get() = this.status.value in 300..399

/**
* Provides an extension to evaluation whether the response is a 4xx code
*/
{{#nonPublicApi}}internal {{/nonPublicApi}}val HttpResponse.isClientError : Boolean get() = this.status.value in 400..499

/**
* Provides an extension to evaluation whether the response is a 5xx (Standard) through 9999 (non-standard) code
*/
{{#nonPublicApi}}internal {{/nonPublicApi}}val HttpResponse.isServerError : Boolean get() = this.status.value in 500..9999
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,11 @@ import {{packageName}}.infrastructure.toMultiValue
ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.")
ResponseType.ClientError -> {
val localVarError = localVarResponse as ClientError<*>
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.body}", localVarError.statusCode, localVarResponse)
}
ResponseType.ServerError -> {
val localVarError = localVarResponse as ServerError<*>
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.body}", localVarError.statusCode, localVarResponse)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.ktor.client.request.parameter
import io.ktor.client.statement.HttpResponse
import io.ktor.serialization.kotlinx.json.json
import io.ktor.http.*
import io.ktor.http.ContentType.Application
import io.ktor.http.content.PartData
import kotlin.Unit
import kotlinx.serialization.json.Json
Expand Down Expand Up @@ -154,6 +155,7 @@ import {{packageName}}.auth.*
this.method = requestConfig.method.httpMethod
headers.filter { header -> !UNSAFE_HEADERS.contains(header.key) }.forEach { header -> this.header(header.key, header.value) }
if (requestConfig.method in listOf(RequestMethod.PUT, RequestMethod.POST, RequestMethod.PATCH)) {
this.contentType(Application.Json)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for not using the following code to support different content types?

                val contentType = (requestConfig.headers[HttpHeaders.ContentType]?.let { ContentType.parse(it) }
                    ?: ContentType.Application.Json)
                this.contentType(contentType)

This was used before ktor was broken by #12966

this.setBody(body)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ buildscript {
}

apply plugin: 'kotlin'
apply plugin: 'maven-publish'

repositories {
maven { url "https://repo1.maven.org/maven2" }
Expand Down
Empty file modified samples/client/petstore/kotlin-allOff-discriminator/gradlew
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ class BirdApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient =
ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.")
ResponseType.ClientError -> {
val localVarError = localVarResponse as ClientError<*>
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.body}", localVarError.statusCode, localVarResponse)
}
ResponseType.ServerError -> {
val localVarError = localVarResponse as ServerError<*>
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.body}", localVarError.statusCode, localVarResponse)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,20 @@ class OffsetDateTimeAdapter {

@FromJson
fun fromJson(value: String): OffsetDateTime {
return OffsetDateTime.parse(value, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
return OffsetDateTime.parse(tryFormatDateTime(value), DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

private fun tryFormatDateTime(stringValue: String): String {
val offset = stringValue.substringAfter("+")
return when (offset.length) {
4 -> fixOffsetValue(stringValue, offset)
else -> stringValue
}
}

private fun fixOffsetValue(stringValue: String = "0000", offset: String): String {
val offsetChars = offset.toCharArray().toMutableList()
offsetChars.add(2, ':')
return stringValue.replaceAfter("+", offsetChars.joinToString(""))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ buildscript {
}

apply plugin: 'kotlin'
apply plugin: 'maven-publish'

repositories {
maven { url "https://repo1.maven.org/maven2" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient
ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.")
ResponseType.ClientError -> {
val localVarError = localVarResponse as ClientError<*>
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.body}", localVarError.statusCode, localVarResponse)
}
ResponseType.ServerError -> {
val localVarError = localVarResponse as ServerError<*>
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.body}", localVarError.statusCode, localVarResponse)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,20 @@ class OffsetDateTimeAdapter {

@FromJson
fun fromJson(value: String): OffsetDateTime {
return OffsetDateTime.parse(value, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
return OffsetDateTime.parse(tryFormatDateTime(value), DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

private fun tryFormatDateTime(stringValue: String): String {
val offset = stringValue.substringAfter("+")
return when (offset.length) {
4 -> fixOffsetValue(stringValue, offset)
else -> stringValue
}
}

private fun fixOffsetValue(stringValue: String = "0000", offset: String): String {
val offsetChars = offset.toCharArray().toMutableList()
offsetChars.add(2, ':')
return stringValue.replaceAfter("+", offsetChars.joinToString(""))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ buildscript {
}

apply plugin: 'kotlin'
apply plugin: 'maven-publish'

repositories {
maven { url "https://repo1.maven.org/maven2" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient
ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.")
ResponseType.ClientError -> {
val localVarError = localVarResponse as ClientError<*>
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.body}", localVarError.statusCode, localVarResponse)
}
ResponseType.ServerError -> {
val localVarError = localVarResponse as ServerError<*>
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.body}", localVarError.statusCode, localVarResponse)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,20 @@ class OffsetDateTimeAdapter {

@FromJson
fun fromJson(value: String): OffsetDateTime {
return OffsetDateTime.parse(value, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
return OffsetDateTime.parse(tryFormatDateTime(value), DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

private fun tryFormatDateTime(stringValue: String): String {
val offset = stringValue.substringAfter("+")
return when (offset.length) {
4 -> fixOffsetValue(stringValue, offset)
else -> stringValue
}
}

private fun fixOffsetValue(stringValue: String = "0000", offset: String): String {
val offsetChars = offset.toCharArray().toMutableList()
offsetChars.add(2, ':')
return stringValue.replaceAfter("+", offsetChars.joinToString(""))
}
}
Loading