Skip to content
2 changes: 1 addition & 1 deletion kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ dependencies {
implementation "io.ktor:ktor-client:$ktorVersion"
implementation "io.ktor:ktor-client-okhttp:$ktorVersion"
implementation "io.ktor:ktor-client-json:$ktorVersion"
implementation "io.ktor:ktor-client-jackson:$ktorVersion"
implementation "io.ktor:ktor-client-gson:$ktorVersion"

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-RC'
implementation 'com.google.code.gson:gson:2.8.5'
Expand Down
72 changes: 67 additions & 5 deletions kotlin/src/main/com/looker/rtl/AuthToken.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,28 @@

package com.looker.rtl

import com.fasterxml.jackson.annotation.JsonProperty
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.TypeAdapter
import com.google.gson.annotations.SerializedName
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import com.looker.sdk.AccessToken
import io.ktor.client.features.json.GsonSerializer
import io.ktor.client.features.json.defaultSerializer
import java.lang.reflect.Type
import java.time.LocalDateTime

data class AuthToken(
@JsonProperty("access_token")
@SerializedName("access_token")
var accessToken: String = "", // TODO: Consider making this/these vals and using new objects instead of mutating
@JsonProperty("token_type")
@SerializedName("token_type")
var tokenType: String = "",
@JsonProperty("expires_in")
@SerializedName("expires_in")
var expiresIn: Long = 0L,
@JsonProperty("refresh_token")
@SerializedName("refresh_token")
var refreshToken: String? = null
) {

Expand Down Expand Up @@ -80,3 +90,55 @@ data class AuthToken(
expiresIn = 0
}
}

/**
* Adapter for serialization/deserialization of [AuthToken].
* This is required since Gson used no-args constructor to create objects. Gson's default
* deserializer first creates an [AuthToken] object with default values which results with incorrect
* value being assigned to [AuthToken.expiresAt] in its init block.
*
* This adapter mitigates this by calling the constructor with deserialized values.
*/
class AuthTokenAdapter: TypeAdapter<AuthToken>() {
override fun read(jsonReader: JsonReader?): AuthToken {
val authToken = AuthToken()
jsonReader?.beginObject()

while (jsonReader?.hasNext() == true) {
if (jsonReader.peek().equals(JsonToken.NAME)) {
when (jsonReader.nextName()) {
"access_token" -> authToken.accessToken = jsonReader.nextString()
"token_type" -> authToken.tokenType = jsonReader.nextString()
"expires_in" -> authToken.expiresIn = jsonReader.nextLong()
"refresh_token" -> {
if (jsonReader.peek().equals(JsonToken.NULL)) {
authToken.refreshToken = null
jsonReader.nextNull()
} else {
authToken.refreshToken = jsonReader.nextString()
}
}
else -> break
}
}
}

jsonReader?.endObject()

// return new AuthToken calling its constructor with deserialized values
return AuthToken(authToken.accessToken, authToken.tokenType, authToken.expiresIn, authToken.refreshToken)
}

override fun write(jsonWriter: JsonWriter?, authToken: AuthToken?) {
jsonWriter?.beginObject()
jsonWriter?.name("access_token")
jsonWriter?.value(authToken?.accessToken)
jsonWriter?.name("token_type")
jsonWriter?.value(authToken?.tokenType)
jsonWriter?.name("expires_in")
jsonWriter?.value(authToken?.expiresIn)
jsonWriter?.name("refresh_token")
jsonWriter?.value(authToken?.refreshToken)
jsonWriter?.endObject()
}
}
17 changes: 10 additions & 7 deletions kotlin/src/main/com/looker/rtl/Transport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@

package com.looker.rtl

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializer
import io.ktor.client.HttpClient
import io.ktor.client.call.receive
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.features.json.JacksonSerializer
import io.ktor.client.features.json.GsonSerializer
import io.ktor.client.features.json.defaultSerializer
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.FormDataContent
Expand Down Expand Up @@ -179,7 +181,9 @@ fun customClient(options: TransportOptions): HttpClient {
// This construction loosely adapted from https://ktor.io/clients/http-client/engines.html#artifact-7
return HttpClient(OkHttp) {
install(JsonFeature) {
serializer = JacksonSerializer()
serializer = GsonSerializer {
registerTypeAdapter(AuthToken::class.java, AuthTokenAdapter())
}
}
engine {
config {
Expand Down Expand Up @@ -336,12 +340,11 @@ class Transport(val options: TransportOptions) {
}
else -> {
// Request body
// val gson = GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssX").create()
val gson = Gson()
val jsonBody = gson.toJson(body)
val json = defaultSerializer()
val jsonBody = json.write(body)

builder.body = jsonBody
headers["Content-Length"] = jsonBody.length.toString()
headers["Content-Length"] = jsonBody.contentLength.toString()
}
}
}
Expand Down
Loading