Skip to content

Commit 15c45dd

Browse files
author
Dariusz Kuc
authored
[client] kotlinx support for custom scalar optional input (ExpediaGroup#1357)
Current optional input serializer was not able to handle custom scalars. Updated generation logic to dynamically generate optional input serializer for each optional input type. New serializers provide logic for handling optional type and delegate to a specific child serializer for serializing defined element values. Resolves: ExpediaGroup#1355
1 parent 27f99ed commit 15c45dd

File tree

85 files changed

+1806
-288
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+1806
-288
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2022 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.client.serialization.serializers
18+
19+
import com.expediagroup.graphql.client.serialization.types.OptionalInput
20+
import kotlinx.serialization.KSerializer
21+
import kotlinx.serialization.builtins.ListSerializer
22+
import kotlinx.serialization.builtins.nullable
23+
import kotlinx.serialization.descriptors.SerialDescriptor
24+
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
25+
import kotlinx.serialization.encoding.Decoder
26+
import kotlinx.serialization.encoding.Encoder
27+
28+
/**
29+
* KSerializer that can serialize/deserialize optional lists of scalar values.
30+
*/
31+
object OptionalScalarListSerializer : KSerializer<OptionalInput<List<Any?>>> {
32+
private val delegate: KSerializer<List<Any?>> = ListSerializer(AnyKSerializer)
33+
34+
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("OptionalScalarList")
35+
36+
override fun serialize(encoder: Encoder, value: OptionalInput<List<Any?>>) {
37+
when (value) {
38+
is OptionalInput.Undefined -> { return }
39+
is OptionalInput.Defined<List<Any?>> -> {
40+
encoder.encodeNullableSerializableValue(delegate, value.value)
41+
}
42+
}
43+
}
44+
45+
// undefined is only supported during client serialization
46+
override fun deserialize(decoder: Decoder): OptionalInput<List<Any?>> = OptionalInput.Defined(
47+
decoder.decodeNullableSerializableValue(delegate.nullable)
48+
)
49+
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,11 +24,11 @@ import kotlinx.serialization.encoding.Decoder
2424
import kotlinx.serialization.encoding.Encoder
2525

2626
/**
27-
* KSerializer that supports serialization of optional input. Delegates to `AnyKSerializer` serializer logic for serializing the contents.
27+
* KSerializer that can serialize/deserialize optional scalar values.
2828
*/
29-
object OptionalInputSerializer : KSerializer<OptionalInput<Any>> {
29+
object OptionalScalarSerializer : KSerializer<OptionalInput<Any>> {
3030

31-
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("OptionalInput")
31+
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("OptionalScalar")
3232

3333
override fun serialize(encoder: Encoder, value: OptionalInput<Any>) {
3434
when (value) {

clients/graphql-kotlin-client-serialization/src/main/kotlin/com/expediagroup/graphql/client/serialization/types/OptionalInput.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,23 +16,17 @@
1616

1717
package com.expediagroup.graphql.client.serialization.types
1818

19-
import com.expediagroup.graphql.client.serialization.serializers.OptionalInputSerializer
20-
import kotlinx.serialization.Serializable
21-
22-
@Serializable(with = OptionalInputSerializer::class)
2319
sealed class OptionalInput<out T> {
2420
/**
2521
* Represents missing/undefined value.
2622
*/
27-
@Serializable(with = OptionalInputSerializer::class)
2823
object Undefined : OptionalInput<Nothing>() {
2924
override fun toString() = "UNDEFINED"
3025
}
3126

3227
/**
3328
* Wrapper holding explicitly specified value including NULL.
3429
*/
35-
@Serializable(with = OptionalInputSerializer::class)
3630
data class Defined<out U>(val value: U?) : OptionalInput<U>() {
3731
override fun toString(): String = "Defined(value=$value)"
3832
}

clients/graphql-kotlin-client-serialization/src/test/kotlin/com/expediagroup/graphql/client/serialization/GraphQLClientKotlinXSerializerTest.kt

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -172,7 +172,7 @@ class GraphQLClientKotlinXSerializerTest {
172172
@Test
173173
fun `verify we can serialize custom scalars`() {
174174
val randomUUID = UUID.randomUUID()
175-
val scalarQuery = ScalarQuery(variables = ScalarQuery.Variables(alias = "1234", custom = com.expediagroup.graphql.client.serialization.data.scalars.UUID(randomUUID)))
175+
val scalarQuery = ScalarQuery(variables = ScalarQuery.Variables(alias = "1234", custom = randomUUID))
176176

177177
val serialized = serializer.serialize(scalarQuery)
178178
val expected = """{
@@ -193,14 +193,17 @@ class GraphQLClientKotlinXSerializerTest {
193193
"""{
194194
| "data": {
195195
| "scalarAlias": "1234",
196-
| "customScalar": "$expectedUUID"
196+
| "customScalar": "$expectedUUID",
197+
| "customScalarList": ["$expectedUUID"]
197198
| }
198199
|}
199200
""".trimMargin()
200201

201202
val result = serializer.deserialize(scalarResponse, ScalarQuery(ScalarQuery.Variables()).responseType())
202203
assertEquals("1234", result.data?.scalarAlias)
203-
assertEquals(expectedUUID, result.data?.customScalar?.value)
204+
assertEquals(expectedUUID, result.data?.customScalar)
205+
assertEquals(1, result.data?.customScalarList?.size)
206+
assertEquals(expectedUUID, result.data?.customScalarList?.get(0))
204207
}
205208

206209
@Test
@@ -243,20 +246,27 @@ class GraphQLClientKotlinXSerializerTest {
243246

244247
@Test
245248
fun `verify we can serialize optional inputs`() {
249+
val uuid = UUID.randomUUID()
246250
val query = OptionalInputQuery(
247251
variables = OptionalInputQuery.Variables(
248252
requiredInput = 123,
249253
optionalIntInput = OptionalInput.Defined(123),
250-
optionalStringInput = OptionalInput.Defined(null)
254+
optionalStringInput = OptionalInput.Defined(null),
251255
// optionalBooleanInput = OptionalInput.Undefined // use default
256+
optionalUUIDInput = OptionalInput.Defined(uuid),
257+
optionalUUIDListInput = OptionalInput.Defined(listOf(uuid))
252258
)
253259
)
254260
val rawQuery =
255261
"""{
256262
| "variables": {
257263
| "requiredInput": 123,
258264
| "optionalIntInput": 123,
259-
| "optionalStringInput": null
265+
| "optionalStringInput": null,
266+
| "optionalUUIDInput": "$uuid",
267+
| "optionalUUIDListInput": [
268+
| "$uuid"
269+
| ]
260270
| },
261271
| "query": "OPTIONAL_INPUT_QUERY",
262272
| "operationName": "OptionalInputQuery"

clients/graphql-kotlin-client-serialization/src/test/kotlin/com/expediagroup/graphql/client/serialization/data/OptionalInputQuery.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,13 @@
1616

1717
package com.expediagroup.graphql.client.serialization.data
1818

19+
import com.expediagroup.graphql.client.serialization.data.scalars.OptionalInputSerializer
20+
import com.expediagroup.graphql.client.serialization.data.scalars.UUIDSerializer
1921
import com.expediagroup.graphql.client.serialization.types.OptionalInput
2022
import com.expediagroup.graphql.client.types.GraphQLClientRequest
2123
import kotlinx.serialization.Required
2224
import kotlinx.serialization.Serializable
25+
import java.util.UUID
2326
import kotlin.reflect.KClass
2427

2528
@Serializable
@@ -37,9 +40,16 @@ class OptionalInputQuery(
3740
@Serializable
3841
data class Variables(
3942
val requiredInput: Int,
43+
@Serializable(with = OptionalInputSerializer::class)
4044
val optionalIntInput: OptionalInput<Int> = OptionalInput.Undefined,
45+
@Serializable(with = OptionalInputSerializer::class)
4146
val optionalStringInput: OptionalInput<String> = OptionalInput.Undefined,
42-
val optionalBooleanInput: OptionalInput<Boolean> = OptionalInput.Undefined
47+
@Serializable(with = OptionalInputSerializer::class)
48+
val optionalBooleanInput: OptionalInput<Boolean> = OptionalInput.Undefined,
49+
@Serializable(with = OptionalInputSerializer::class)
50+
val optionalUUIDInput: OptionalInput<@Serializable(with = UUIDSerializer::class) UUID> = OptionalInput.Undefined,
51+
@Serializable(with = OptionalInputSerializer::class)
52+
val optionalUUIDListInput: OptionalInput<List<@Serializable(with = UUIDSerializer::class) UUID>> = OptionalInput.Undefined,
4353
)
4454

4555
@Serializable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Files in this directory would normally be generated by Gradle or Maven plugin. Since we don't want to put that as a test dependency
2+
to this module, generated files are copied over for various test cases.

clients/graphql-kotlin-client-serialization/src/test/kotlin/com/expediagroup/graphql/client/serialization/data/ScalarQuery.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,11 @@
1616

1717
package com.expediagroup.graphql.client.serialization.data
1818

19-
import com.expediagroup.graphql.client.serialization.data.scalars.UUID
19+
import com.expediagroup.graphql.client.serialization.data.scalars.UUIDSerializer
2020
import com.expediagroup.graphql.client.types.GraphQLClientRequest
2121
import kotlinx.serialization.Required
2222
import kotlinx.serialization.Serializable
23+
import java.util.UUID
2324
import kotlin.reflect.KClass
2425

2526
// typealiases would be in separate file
@@ -40,12 +41,15 @@ class ScalarQuery(
4041
@Serializable
4142
data class Variables(
4243
val alias: ID? = null,
44+
@Serializable(with = UUIDSerializer::class)
4345
val custom: UUID? = null
4446
)
4547

4648
@Serializable
4749
data class Result(
4850
val scalarAlias: ID,
49-
val customScalar: UUID
51+
@Serializable(with = UUIDSerializer::class)
52+
val customScalar: UUID,
53+
val customScalarList: List<@Serializable(with = UUIDSerializer::class) UUID>
5054
)
5155
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2022 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.client.serialization.data.scalars
18+
19+
import com.expediagroup.graphql.client.Generated
20+
import com.expediagroup.graphql.client.serialization.serializers.AnyKSerializer
21+
import com.expediagroup.graphql.client.serialization.types.OptionalInput
22+
import java.util.UUID
23+
import kotlin.Any
24+
import kotlin.Suppress
25+
import kotlin.collections.Map
26+
import kotlin.collections.mapOf
27+
import kotlin.reflect.KClass
28+
import kotlinx.serialization.KSerializer
29+
import kotlinx.serialization.builtins.ListSerializer
30+
import kotlinx.serialization.descriptors.SerialDescriptor
31+
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
32+
import kotlinx.serialization.encoding.Decoder
33+
import kotlinx.serialization.encoding.Encoder
34+
35+
@Generated
36+
object OptionalInputSerializer : KSerializer<OptionalInput<Any>> {
37+
private val delegates: Map<KClass<*>, KSerializer<*>> = mapOf(UUID::class to UUIDSerializer)
38+
39+
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("OptionalInput")
40+
41+
@Suppress("UNCHECKED_CAST")
42+
override fun serialize(encoder: Encoder, `value`: OptionalInput<Any>) {
43+
when (value) {
44+
is OptionalInput.Undefined -> {
45+
return
46+
}
47+
is OptionalInput.Defined<*> -> {
48+
val definedValue = value.value
49+
if (definedValue != null) {
50+
if (definedValue is List<*>) {
51+
val element = definedValue.firstOrNull()
52+
val elementSerializer = if (element == null) {
53+
AnyKSerializer
54+
} else {
55+
delegates[element::class] as? KSerializer<Any?> ?: AnyKSerializer
56+
}
57+
encoder.encodeSerializableValue(ListSerializer(elementSerializer), definedValue)
58+
} else {
59+
val delegate: KSerializer<Any?> = delegates[definedValue::class] as? KSerializer<Any?> ?: AnyKSerializer
60+
encoder.encodeSerializableValue(delegate, definedValue)
61+
}
62+
} else {
63+
encoder.encodeNull()
64+
}
65+
}
66+
}
67+
}
68+
69+
/**
70+
* undefined is only supported during client serialization, this code should never be invoked
71+
*/
72+
override fun deserialize(decoder: Decoder): OptionalInput<Any> =
73+
OptionalInput.Defined(AnyKSerializer.deserialize(decoder))
74+
}
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,37 +16,34 @@
1616

1717
package com.expediagroup.graphql.client.serialization.data.scalars
1818

19+
import com.expediagroup.graphql.client.Generated
1920
import com.expediagroup.graphql.client.converter.ScalarConverter
21+
import java.util.UUID
2022
import kotlinx.serialization.KSerializer
21-
import kotlinx.serialization.Serializable
22-
import kotlinx.serialization.descriptors.PrimitiveKind
23+
import kotlinx.serialization.descriptors.PrimitiveKind.STRING
2324
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
2425
import kotlinx.serialization.descriptors.SerialDescriptor
2526
import kotlinx.serialization.encoding.Decoder
2627
import kotlinx.serialization.encoding.Encoder
2728
import kotlinx.serialization.json.JsonDecoder
2829
import kotlinx.serialization.json.jsonPrimitive
2930

30-
@Serializable(with = UUIDSerializer::class)
31-
data class UUID(
32-
val value: java.util.UUID
33-
)
34-
35-
class UUIDSerializer : KSerializer<UUID> {
31+
@Generated
32+
object UUIDSerializer : KSerializer<UUID> {
3633
private val converter: UUIDScalarConverter = UUIDScalarConverter()
3734

38-
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
35+
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", STRING)
3936

40-
override fun serialize(encoder: Encoder, value: UUID) {
41-
val encoded = converter.toJson(value.value)
37+
override fun serialize(encoder: Encoder, `value`: UUID) {
38+
val encoded = converter.toJson(value)
4239
encoder.encodeString(encoded.toString())
4340
}
4441

4542
override fun deserialize(decoder: Decoder): UUID {
4643
val jsonDecoder = decoder as JsonDecoder
4744
val element = jsonDecoder.decodeJsonElement()
4845
val rawContent = element.jsonPrimitive.content
49-
return UUID(value = converter.toScalar(rawContent))
46+
return converter.toScalar(rawContent)
5047
}
5148
}
5249

0 commit comments

Comments
 (0)