Skip to content
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

Add a "compat" v2 CustomTypeAdapter to v3 Adapter API #3482

Merged
merged 5 commits into from
Nov 2, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
47 changes: 47 additions & 0 deletions apollo-api/api/apollo-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ public final class com/apollographql/apollo3/api/CustomScalarAdapters : com/apol
public final class com/apollographql/apollo3/api/CustomScalarAdapters$Builder {
public fun <init> ()V
public final fun add (Lcom/apollographql/apollo3/api/CustomScalarType;Lcom/apollographql/apollo3/api/Adapter;)Lcom/apollographql/apollo3/api/CustomScalarAdapters$Builder;
public final fun add (Lcom/apollographql/apollo3/api/CustomScalarType;Lcom/apollographql/apollo3/api/CustomTypeAdapter;)Lcom/apollographql/apollo3/api/CustomScalarAdapters$Builder;
public final fun addAll (Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lcom/apollographql/apollo3/api/CustomScalarAdapters$Builder;
public final fun build ()Lcom/apollographql/apollo3/api/CustomScalarAdapters;
public final fun clear ()V
Expand All @@ -352,6 +353,46 @@ public final class com/apollographql/apollo3/api/CustomScalarType : com/apollogr
public final fun getClassName ()Ljava/lang/String;
}

public abstract interface class com/apollographql/apollo3/api/CustomTypeAdapter {
public abstract fun decode (Lcom/apollographql/apollo3/api/CustomTypeValue;)Ljava/lang/Object;
public abstract fun encode (Ljava/lang/Object;)Lcom/apollographql/apollo3/api/CustomTypeValue;
}

public class com/apollographql/apollo3/api/CustomTypeValue {
public static final field Companion Lcom/apollographql/apollo3/api/CustomTypeValue$Companion;
public fun <init> (Ljava/lang/Object;)V
public static final fun fromRawValue (Ljava/lang/Object;)Lcom/apollographql/apollo3/api/CustomTypeValue;
public final fun getValue ()Ljava/lang/Object;
}

public final class com/apollographql/apollo3/api/CustomTypeValue$Companion {
public final fun fromRawValue (Ljava/lang/Object;)Lcom/apollographql/apollo3/api/CustomTypeValue;
}

public final class com/apollographql/apollo3/api/CustomTypeValue$GraphQLBoolean : com/apollographql/apollo3/api/CustomTypeValue {
public fun <init> (Z)V
}

public final class com/apollographql/apollo3/api/CustomTypeValue$GraphQLJsonList : com/apollographql/apollo3/api/CustomTypeValue {
public fun <init> (Ljava/util/List;)V
}

public final class com/apollographql/apollo3/api/CustomTypeValue$GraphQLJsonObject : com/apollographql/apollo3/api/CustomTypeValue {
public fun <init> (Ljava/util/Map;)V
}

public final class com/apollographql/apollo3/api/CustomTypeValue$GraphQLNull : com/apollographql/apollo3/api/CustomTypeValue {
public static final field INSTANCE Lcom/apollographql/apollo3/api/CustomTypeValue$GraphQLNull;
}

public final class com/apollographql/apollo3/api/CustomTypeValue$GraphQLNumber : com/apollographql/apollo3/api/CustomTypeValue {
public fun <init> (Ljava/lang/Number;)V
}

public final class com/apollographql/apollo3/api/CustomTypeValue$GraphQLString : com/apollographql/apollo3/api/CustomTypeValue {
public fun <init> (Ljava/lang/String;)V
}

public final class com/apollographql/apollo3/api/DoubleAdapter : com/apollographql/apollo3/api/Adapter {
public static final field INSTANCE Lcom/apollographql/apollo3/api/DoubleAdapter;
public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Double;
Expand Down Expand Up @@ -688,6 +729,12 @@ public final class com/apollographql/apollo3/api/UploadAdapter : com/apollograph
public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V
}

public final class com/apollographql/apollo3/api/Version2CustomTypeAdapterToAdapter : com/apollographql/apollo3/api/Adapter {
public fun <init> (Lcom/apollographql/apollo3/api/CustomTypeAdapter;)V
public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object;
public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V
}

public final class com/apollographql/apollo3/api/http/DefaultHttpRequestComposer : com/apollographql/apollo3/api/http/HttpRequestComposer {
public static final field Companion Lcom/apollographql/apollo3/api/http/DefaultHttpRequestComposer$Companion;
public static final field HEADER_APOLLO_OPERATION_ID Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package com.apollographql.apollo3.api

import com.apollographql.apollo3.api.internal.json.BufferedSinkJsonWriter
import com.apollographql.apollo3.api.internal.json.BufferedSourceJsonReader
import com.apollographql.apollo3.api.json.MapJsonReader
import com.apollographql.apollo3.api.json.JsonReader
import com.apollographql.apollo3.api.json.JsonWriter
import com.apollographql.apollo3.api.json.MapJsonReader
import okio.Buffer
import okio.BufferedSink
import okio.BufferedSource
Expand Down Expand Up @@ -53,10 +53,10 @@ interface Adapter<T> {
* }
* ```
*
* Alternatively, you can use the the built-in [AnyAdapter] to simplify the parsing loop:
* Alternatively, you can use the built-in [AnyAdapter] to simplify the parsing loop:
martinbonnin marked this conversation as resolved.
Show resolved Hide resolved
* ```
* override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): Hero {
* val map = AnyAdapter.fromResponse(reader) as Map<String, String>
* val map = AnyAdapter.fromJson(reader) as Map<String, String>
*
* return Hero(map["name"]!!, map["homeworld"]!!)
* }
Expand All @@ -77,7 +77,7 @@ interface Adapter<T> {
* }
*```
*
* Alternatively, you can use the the built-in [AnyAdapter]:
* Alternatively, you can use the built-in [AnyAdapter]:
* ```
* override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: Hero) {
* val map = mapOf("name" to value.name, "homeworld" to value.homeworld)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.apollographql.apollo3.api

@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This API is for internal use only in Apollo"
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class ApolloInternal
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.apollographql.apollo3.api

import com.apollographql.apollo3.api.internal.Version2CustomTypeAdapterToAdapter
import kotlin.jvm.JvmField

/**
Expand Down Expand Up @@ -51,6 +52,15 @@ class CustomScalarAdapters
adaptersMap[customScalarType.name] = customScalarAdapter
}

@OptIn(ApolloInternal::class)
@Deprecated("Used for backward compatibility with 2.x")
fun <T> add(
customScalarType: CustomScalarType,
customTypeAdapter: CustomTypeAdapter<T>,
) = apply {
adaptersMap[customScalarType.name] = Version2CustomTypeAdapterToAdapter(customTypeAdapter)
}

fun addAll(customScalarAdapters: CustomScalarAdapters) = apply {
this.adaptersMap.putAll(customScalarAdapters.adaptersMap)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.apollographql.apollo3.api

import kotlin.jvm.JvmStatic

/**
* A replica of Apollo Android v2's CustomTypeAdapter, to ease migration from v2 to v3.
*
* Make your CustomTypeAdapters implement this interface by updating the imports
* from `com.apollographql.apollo.api` to `com.apollographql.apollo3.api`.
*/
@Deprecated("Used for backward compatibility with 2.x, use Adapter instead")
interface CustomTypeAdapter<T> {
fun decode(value: CustomTypeValue<*>): T
fun encode(value: T): CustomTypeValue<*>
}

/**
* A replica of Apollo Android v2's CustomTypeValue, to ease migration from v2 to v3.
*
* In your [CustomTypeAdapter], update the imports from `com.apollographql.apollo.api` to
* `com.apollographql.apollo3.api` to use this version.
*/
@Deprecated("Used for backward compatibility with 2.x, use Adapter instead")
open class CustomTypeValue<T>(val value: T) {
object GraphQLNull : CustomTypeValue<Unit>(Unit)
class GraphQLString(value: String) : CustomTypeValue<String>(value)
class GraphQLBoolean(value: Boolean) : CustomTypeValue<Boolean>(value)
class GraphQLNumber(value: Number) : CustomTypeValue<Number>(value)
class GraphQLJsonObject(value: Map<String, Any>) : CustomTypeValue<Map<String, Any>>(value)
class GraphQLJsonList(value: List<Any>) : CustomTypeValue<List<Any>>(value)

companion object {
@JvmStatic
@Suppress("UNCHECKED_CAST")
fun fromRawValue(value: Any): CustomTypeValue<*> {
return when (value) {
is Map<*, *> -> GraphQLJsonObject(value as Map<String, Any>)
is List<*> -> GraphQLJsonList(value as List<Any>)
is Boolean -> GraphQLBoolean(value)
// Not supported as we are in common code here
/* is BigDecimal -> GraphQLNumber(value.toNumber()) */
is Number -> GraphQLNumber(value)
else -> GraphQLString(value.toString())
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.apollographql.apollo3.api.internal

import com.apollographql.apollo3.api.Adapter
import com.apollographql.apollo3.api.ApolloInternal
import com.apollographql.apollo3.api.CustomScalarAdapters
import com.apollographql.apollo3.api.CustomTypeAdapter
import com.apollographql.apollo3.api.CustomTypeValue
import com.apollographql.apollo3.api.NullableAnyAdapter
import com.apollographql.apollo3.api.json.JsonReader
import com.apollographql.apollo3.api.json.JsonWriter

/**
* An [Adapter] that wraps an Apollo Android v2 style [CustomTypeAdapter], to ease migration from v2 to v3.
*/
@ApolloInternal
class Version2CustomTypeAdapterToAdapter<T>(
private val v2CustomTypeAdapter: CustomTypeAdapter<T>,
) : Adapter<T> {
override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): T {
val value: Any? = NullableAnyAdapter.fromJson(reader, customScalarAdapters)
return v2CustomTypeAdapter.decode(CustomTypeValue(value))
}

override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: T) {
val encoded: Any? = v2CustomTypeAdapter.encode(value).value
NullableAnyAdapter.toJson(writer, customScalarAdapters, encoded)
}
}
1 change: 1 addition & 0 deletions apollo-runtime/api/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public final class com/apollographql/apollo3/ApolloClient$Builder : com/apollogr
public fun <init> ()V
public final fun addCustomScalarAdapter (Lcom/apollographql/apollo3/api/CustomScalarType;Lcom/apollographql/apollo3/api/Adapter;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public final fun addCustomTypeAdapter (Lcom/apollographql/apollo3/api/CustomScalarType;Lcom/apollographql/apollo3/api/Adapter;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public final fun addCustomTypeAdapter (Lcom/apollographql/apollo3/api/CustomScalarType;Lcom/apollographql/apollo3/api/CustomTypeAdapter;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public fun addExecutionContext (Lcom/apollographql/apollo3/api/ExecutionContext;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public synthetic fun addExecutionContext (Lcom/apollographql/apollo3/api/ExecutionContext;)Lcom/apollographql/apollo3/api/HasMutableExecutionContext;
public final fun addFlowDecorator (Lkotlin/jvm/functions/Function1;)Lcom/apollographql/apollo3/ApolloClient$Builder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.apollographql.apollo3

import com.apollographql.apollo3.api.Adapter
import com.apollographql.apollo3.api.ApolloInternal
import com.apollographql.apollo3.api.ApolloRequest
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.CustomScalarAdapters
import com.apollographql.apollo3.api.CustomScalarType
import com.apollographql.apollo3.api.CustomTypeAdapter
import com.apollographql.apollo3.api.ExecutionContext
import com.apollographql.apollo3.api.HasExecutionContext
import com.apollographql.apollo3.api.HasMutableExecutionContext
Expand All @@ -19,6 +21,7 @@ import com.apollographql.apollo3.api.http.httpHeaders
import com.apollographql.apollo3.api.http.httpMethod
import com.apollographql.apollo3.api.http.sendApqExtensions
import com.apollographql.apollo3.api.http.sendDocument
import com.apollographql.apollo3.api.internal.Version2CustomTypeAdapterToAdapter
import com.apollographql.apollo3.interceptor.ApolloInterceptor
import com.apollographql.apollo3.interceptor.AutoPersistedQueryInterceptor
import com.apollographql.apollo3.interceptor.DefaultInterceptorChain
Expand Down Expand Up @@ -217,7 +220,9 @@ class ApolloClient @JvmOverloads @Deprecated("Please use ApolloClient.Builder in
/**
* Registers the given [customScalarAdapter]
*
* @param customScalarType a generated [CustomScalarType] from the [Types] generated object
* @param customScalarType a generated [CustomScalarType]. Every GraphQL custom scalar has a
* generated class with a static `type` property. For an example, for a `Date` custom scalar,
* you can use `com.example.Date.type`
* @param customScalarAdapter the [Adapter] to use for this custom scalar
*/
fun <T> addCustomScalarAdapter(customScalarType: CustomScalarType, customScalarAdapter: Adapter<T>) = apply {
Expand All @@ -230,6 +235,13 @@ class ApolloClient @JvmOverloads @Deprecated("Please use ApolloClient.Builder in
customScalarAdapter: Adapter<T>,
) = addCustomScalarAdapter(customScalarType, customScalarAdapter)

@OptIn(ApolloInternal::class)
@Deprecated("Used for backward compatibility with 2.x", ReplaceWith("addCustomScalarAdapter"))
fun <T> addCustomTypeAdapter(
customScalarType: CustomScalarType,
customTypeAdapter: CustomTypeAdapter<T>,
) = addCustomScalarAdapter(customScalarType, Version2CustomTypeAdapterToAdapter(customTypeAdapter))

fun addInterceptor(interceptor: ApolloInterceptor) = apply {
interceptors += interceptor
}
Expand Down
5 changes: 5 additions & 0 deletions tests/integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ fun configureApollo(generateKotlinModels: Boolean) {
"Date" to "kotlinx.datetime.LocalDate"
))
}
"fullstack" -> {
customScalarsMapping.set(mapOf(
"Date" to "com.example.MyDate"
))
}
}

srcDir(file("src/main/graphql/com/apollographql/apollo3/integration/${it.name}/"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example

data class MyDate(val year: Int, val month: Int, val day: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package test

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Adapter
import com.apollographql.apollo3.api.CustomScalarAdapters
import com.apollographql.apollo3.api.CustomTypeAdapter
import com.apollographql.apollo3.api.CustomTypeValue
import com.apollographql.apollo3.api.json.JsonReader
import com.apollographql.apollo3.api.json.JsonWriter
import com.apollographql.apollo3.integration.fullstack.LaunchDetailsByDateQuery
import com.apollographql.apollo3.integration.fullstack.type.Date
import com.apollographql.apollo3.mockserver.MockServer
import com.apollographql.apollo3.mockserver.enqueue
import com.apollographql.apollo3.testing.runTest
import com.example.MyDate
import readResource
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class ScalarAdapterTest {
martinbonnin marked this conversation as resolved.
Show resolved Hide resolved
private lateinit var mockServer: MockServer

private fun setUp() {
mockServer = MockServer()
}

private suspend fun tearDown() {
mockServer.stop()
}

private object MyDateAdapter : Adapter<MyDate> {
override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): MyDate {
val elements = reader.nextString()!!.split('-').map { it.toInt() }
return MyDate(elements[0], elements[1], elements[2])
}

override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: MyDate) {
writer.value("${value.year}-${value.month}-${value.day}")
}
}

private object MyDateV2CustomTypeAdapter : CustomTypeAdapter<MyDate> {
override fun decode(value: CustomTypeValue<*>): MyDate {
val elements = value.value.toString().split('-').map { it.toInt() }
return MyDate(elements[0], elements[1], elements[2])
}

override fun encode(value: MyDate): CustomTypeValue<*> {
return CustomTypeValue.fromRawValue("${value.year}-${value.month}-${value.day}")
}
}

@Test
fun regularCustomScalarAdapter() = runTest(before = { setUp() }, after = { tearDown() }) {
mockServer.enqueue(readResource("LaunchDetailsByDateResponse.json"))

val apolloClient = ApolloClient.Builder()
.serverUrl(mockServer.url())
.addCustomScalarAdapter(Date.type, MyDateAdapter)
.build()

val response = apolloClient.query(LaunchDetailsByDateQuery(MyDate(2001, 6, 23))).execute()

val request = mockServer.takeRequest()
assertTrue(request.body.utf8().contains(""""variables":{"date":"2001-6-23"}"""))

assertEquals(MyDate(1978, 4, 27), response.dataOrThrow.launchByDate?.date)
}

@Test
fun version2CustomTypeAdapter() = runTest(before = { setUp() }, after = { tearDown() }) {
mockServer.enqueue(readResource("LaunchDetailsByDateResponse.json"))

val apolloClient = ApolloClient.Builder()
.serverUrl(mockServer.url())
.addCustomTypeAdapter(Date.type, MyDateV2CustomTypeAdapter)
.build()

val response = apolloClient.query(LaunchDetailsByDateQuery(MyDate(2001, 6, 23))).execute()

val request = mockServer.takeRequest()
assertTrue(request.body.utf8().contains(""""variables":{"date":"2001-6-23"}"""))

assertEquals(MyDate(1978, 4, 27), response.dataOrThrow.launchByDate?.date)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
query LaunchDetailsByDate($date:Date!) {
launchByDate(date: $date) {
id
site
mission {
name
missionPatch(size:LARGE)
}
rocket {
name
type
}
isBooked
date
}
}
Loading