Skip to content

Commit 2bed754

Browse files
authored
dataconnect: EnumValue and EnumValueSerializer added (#7153)
1 parent ae770aa commit 2bed754

File tree

9 files changed

+760
-13
lines changed

9 files changed

+760
-13
lines changed

firebase-dataconnect/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
- [changed] Ignore unknown fields in response data instead of throwing a
55
`DataConnectOperationException` with message "decoding data from the server's response failed: An
66
unknown field for index -3" ([#7314](https://github.com/firebase/firebase-android-sdk/pull/7314))
7+
- [changed] Added classes `EnumValue` and `EnumValueSerializer`. These classes are identical to
8+
those produced by the Data Connect code generator; however, a future version of the code generator
9+
will start using these classes from the SDK rather than generating them.
10+
([#7153](https://github.com/firebase/firebase-android-sdk/pull/7153))
711

812
# 17.0.0
913

firebase-dataconnect/api.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,31 @@ package com.google.firebase.dataconnect {
9595
method public static com.google.firebase.dataconnect.DataConnectSettings copy(com.google.firebase.dataconnect.DataConnectSettings, String host = host, boolean sslEnabled = sslEnabled);
9696
}
9797

98+
public sealed interface EnumValue<T extends java.lang.Enum<? extends T>> {
99+
method public String getStringValue();
100+
method public T? getValue();
101+
property public abstract String stringValue;
102+
property public abstract T? value;
103+
}
104+
105+
public static final class EnumValue.Known<T extends java.lang.Enum<T>> implements com.google.firebase.dataconnect.EnumValue<T> {
106+
ctor public EnumValue.Known(T value);
107+
method public com.google.firebase.dataconnect.EnumValue.Known<T> copy(T value = value);
108+
method public String getStringValue();
109+
method public T getValue();
110+
property public String stringValue;
111+
property public T value;
112+
}
113+
114+
public static final class EnumValue.Unknown implements com.google.firebase.dataconnect.EnumValue {
115+
ctor public EnumValue.Unknown(String stringValue);
116+
method public com.google.firebase.dataconnect.EnumValue.Unknown copy(String stringValue = stringValue);
117+
method public String getStringValue();
118+
method public Void? getValue();
119+
property public String stringValue;
120+
property public Void? value;
121+
}
122+
98123
@kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING, message="This declaration is \"experimental\": its signature and/or semantics " + "may change in backwards-incompatible ways at any time without notice, " + "up to and including complete removal. " + "If you have a use case that relies on this declaration please open a " + "\"feature request\" issue at https://github.com/firebase/firebase-android-sdk " + "requesting this declaration\'s promotion from \"experimental\" to \"fully-supported\".") @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalFirebaseDataConnect {
99124
}
100125

@@ -346,6 +371,14 @@ package com.google.firebase.dataconnect.serializers {
346371
field public static final com.google.firebase.dataconnect.serializers.AnyValueSerializer INSTANCE;
347372
}
348373

374+
public class EnumValueSerializer<T extends java.lang.Enum<T>> implements kotlinx.serialization.KSerializer<com.google.firebase.dataconnect.EnumValue<? extends T>> {
375+
ctor public EnumValueSerializer(Iterable<? extends T> values);
376+
method public com.google.firebase.dataconnect.EnumValue<T> deserialize(kotlinx.serialization.encoding.Decoder decoder);
377+
method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
378+
method public void serialize(kotlinx.serialization.encoding.Encoder encoder, com.google.firebase.dataconnect.EnumValue<? extends T> value);
379+
property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
380+
}
381+
349382
public final class JavaTimeLocalDateSerializer implements kotlinx.serialization.KSerializer<java.time.LocalDate> {
350383
method public java.time.LocalDate deserialize(kotlinx.serialization.encoding.Decoder decoder);
351384
method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
version=17.0.1
1+
version=17.1.0
22
latestReleasedVersion=17.0.0

firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/EnumIntegrationTest.kt

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17+
@file:OptIn(ExperimentalFirebaseDataConnect::class)
18+
1719
package com.google.firebase.dataconnect
1820

21+
import com.google.firebase.dataconnect.EnumValue.Known
22+
import com.google.firebase.dataconnect.EnumValue.Unknown
23+
import com.google.firebase.dataconnect.serializers.EnumValueSerializer
1924
import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase
2025
import com.google.firebase.dataconnect.testutil.property.arbitrary.dataConnect
2126
import com.google.firebase.dataconnect.testutil.property.arbitrary.enumWithNull
@@ -94,9 +99,13 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
9499
val insertVariables = InsertNonNullableVariables(enumValue)
95100
val key = dataConnect.mutation(insertVariables).execute().data.key
96101
val queryVariables = GetNonNullableByKeyVariables(key)
97-
val queryRef = dataConnect.query(queryVariables)
102+
val queryRef =
103+
dataConnect
104+
.query(queryVariables)
105+
.withDataDeserializer(serializer<GetNonNullableByKeySubsetData>())
98106
val queryResult = queryRef.execute()
99-
queryResult.asClue { it.data.item.value shouldBe enumValue }
107+
val expectedValue = enumValue.toN5ekmae3jnSubsetEnumValue()
108+
queryResult.asClue { it.data.item.value shouldBe expectedValue }
100109
}
101110
}
102111

@@ -183,6 +192,15 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
183192
@Serializable data class Item(val value: N5ekmae3jn)
184193
}
185194

195+
@Serializable
196+
private data class GetNonNullableByKeySubsetData(val item: Item) {
197+
@Serializable
198+
data class Item(
199+
@Serializable(with = N5ekmae3jnSubset.Serializer::class)
200+
val value: EnumValue<N5ekmae3jnSubset>
201+
)
202+
}
203+
186204
//////////////////////////////////////////////////////////////////////////////////////////////////
187205
// Tests for EnumNullable table.
188206
//////////////////////////////////////////////////////////////////////////////////////////////////
@@ -227,9 +245,13 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
227245
val insertVariables = InsertNullableVariables(enumValue)
228246
val key = dataConnect.mutation(insertVariables).execute().data.key
229247
val queryVariables = GetNullableByKeyVariables(key)
230-
val queryRef = dataConnect.query(queryVariables)
248+
val queryRef =
249+
dataConnect
250+
.query(queryVariables)
251+
.withDataDeserializer(serializer<GetNullableByKeySubsetData>())
231252
val queryResult = queryRef.execute()
232-
queryResult.asClue { it.data.item.value shouldBe enumValue }
253+
val expectedValue = enumValue?.toN5ekmae3jnSubsetEnumValue()
254+
queryResult.asClue { it.data.item.value shouldBe expectedValue }
233255
}
234256
}
235257

@@ -306,6 +328,15 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
306328
@Serializable data class Item(val value: N5ekmae3jn?)
307329
}
308330

331+
@Serializable
332+
private data class GetNullableByKeySubsetData(val item: Item) {
333+
@Serializable
334+
data class Item(
335+
@Serializable(with = N5ekmae3jnSubset.Serializer::class)
336+
val value: EnumValue<N5ekmae3jnSubset>?
337+
)
338+
}
339+
309340
//////////////////////////////////////////////////////////////////////////////////////////////////
310341
// Tests for EnumNonNullableTableDefault table.
311342
//////////////////////////////////////////////////////////////////////////////////////////////////
@@ -377,9 +408,14 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
377408
val insertVariables = InsertNonNullableListOfNonNullableVariables(values)
378409
val key = dataConnect.mutation(insertVariables).execute().data.key
379410
val queryVariables = GetNonNullableListOfNonNullableByKeyVariables(key)
380-
val queryRef = dataConnect.query(queryVariables)
411+
val queryRef =
412+
dataConnect
413+
.query(queryVariables)
414+
.withDataDeserializer(serializer<GetNonNullableListOfNonNullableByKeySubsetData>())
381415
val queryResult = queryRef.execute()
382-
queryResult.asClue { it.data.item.value shouldBe values }
416+
val expectedEnumValues: List<EnumValue<N5ekmae3jnSubset>> =
417+
values.map { it.toN5ekmae3jnSubsetEnumValue() }
418+
queryResult.asClue { it.data.item.value shouldBe expectedEnumValues }
383419
}
384420
}
385421

@@ -397,6 +433,15 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
397433
@Serializable data class Item(val value: List<N5ekmae3jn?>)
398434
}
399435

436+
@Serializable
437+
private data class GetNonNullableListOfNonNullableByKeySubsetData(val item: Item) {
438+
@Serializable
439+
data class Item(
440+
val value:
441+
List<@Serializable(with = N5ekmae3jnSubset.Serializer::class) EnumValue<N5ekmae3jnSubset>?>?
442+
)
443+
}
444+
400445
//////////////////////////////////////////////////////////////////////////////////////////////////
401446
// Tests for EnumNonNullableListOfNullable table.
402447
//////////////////////////////////////////////////////////////////////////////////////////////////
@@ -437,9 +482,14 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
437482
val insertVariables = InsertNonNullableListOfNullableVariables(values)
438483
val key = dataConnect.mutation(insertVariables).execute().data.key
439484
val queryVariables = GetNonNullableListOfNullableByKeyVariables(key)
440-
val queryRef = dataConnect.query(queryVariables)
485+
val queryRef =
486+
dataConnect
487+
.query(queryVariables)
488+
.withDataDeserializer(serializer<GetNonNullableListOfNonNullableByKeySubsetData>())
441489
val queryResult = queryRef.execute()
442-
queryResult.asClue { it.data.item.value shouldBe values }
490+
val expectedEnumValues: List<EnumValue<N5ekmae3jnSubset>?> =
491+
values.map { it?.toN5ekmae3jnSubsetEnumValue() }
492+
queryResult.asClue { it.data.item.value shouldBe expectedEnumValues }
443493
}
444494
}
445495

@@ -499,9 +549,14 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
499549
val insertVariables = InsertNullableListOfNonNullableVariables(values)
500550
val key = dataConnect.mutation(insertVariables).execute().data.key
501551
val queryVariables = GetNullableListOfNonNullableByKeyVariables(key)
502-
val queryRef = dataConnect.query(queryVariables)
552+
val queryRef =
553+
dataConnect
554+
.query(queryVariables)
555+
.withDataDeserializer(serializer<GetNonNullableListOfNonNullableByKeySubsetData>())
503556
val queryResult = queryRef.execute()
504-
queryResult.asClue { it.data.item.value shouldBe values }
557+
val expectedEnumValues: List<EnumValue<N5ekmae3jnSubset>?> =
558+
values.map { it?.toN5ekmae3jnSubsetEnumValue() }
559+
queryResult.asClue { it.data.item.value shouldBe expectedEnumValues }
505560
}
506561
}
507562

@@ -561,9 +616,14 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
561616
val insertVariables = InsertNullableListOfNullableVariables(values)
562617
val key = dataConnect.mutation(insertVariables).execute().data.key
563618
val queryVariables = GetNullableListOfNullableByKeyVariables(key)
564-
val queryRef = dataConnect.query(queryVariables)
619+
val queryRef =
620+
dataConnect
621+
.query(queryVariables)
622+
.withDataDeserializer(serializer<GetNonNullableListOfNonNullableByKeySubsetData>())
565623
val queryResult = queryRef.execute()
566-
queryResult.asClue { it.data.item.value shouldBe values }
624+
val expectedEnumValues: List<EnumValue<N5ekmae3jnSubset>?> =
625+
values.map { it?.toN5ekmae3jnSubsetEnumValue() }
626+
queryResult.asClue { it.data.item.value shouldBe expectedEnumValues }
567627
}
568628
}
569629

@@ -701,6 +761,15 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
701761
N3HWNCRWBP,
702762
}
703763

764+
@Suppress("SpellCheckingInspection", "unused")
765+
private enum class N5ekmae3jnSubset {
766+
DPSKD6HR3A,
767+
XGWGVMYTHJ,
768+
QJX7C7RD5T;
769+
770+
object Serializer : EnumValueSerializer<N5ekmae3jnSubset>(N5ekmae3jnSubset.entries)
771+
}
772+
704773
@Suppress("SpellCheckingInspection", "unused")
705774
private enum class S7yayynb25 {
706775
XJ27ZAXKD3,
@@ -714,6 +783,20 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
714783
/** The default number of iterations to use in property-based tests. */
715784
const val NUM_ITERATIONS = 10
716785

786+
@Suppress("SpellCheckingInspection")
787+
fun N5ekmae3jn.toN5ekmae3jnSubsetOrNull(): N5ekmae3jnSubset? =
788+
when (this) {
789+
N5ekmae3jn.DPSKD6HR3A -> N5ekmae3jnSubset.DPSKD6HR3A
790+
N5ekmae3jn.XGWGVMYTHJ -> N5ekmae3jnSubset.XGWGVMYTHJ
791+
N5ekmae3jn.QJX7C7RD5T -> N5ekmae3jnSubset.QJX7C7RD5T
792+
else -> null
793+
}
794+
795+
@Suppress("SpellCheckingInspection")
796+
fun N5ekmae3jn.toN5ekmae3jnSubsetEnumValue(): EnumValue<N5ekmae3jnSubset> {
797+
return Known(toN5ekmae3jnSubsetOrNull() ?: return Unknown(name))
798+
}
799+
717800
fun FirebaseDataConnect.mutation(
718801
variables: InsertNonNullableVariables
719802
): MutationRef<InsertData, InsertNonNullableVariables> =
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2025 Google LLC
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+
* http://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.google.firebase.dataconnect
18+
19+
/**
20+
* Stores the value of an `enum` or a string if the string does not correspond to one of the enum's
21+
* values.
22+
*/
23+
public sealed interface EnumValue<out T : Enum<out T>> {
24+
25+
/** [Known.value] in the case of [Known], or `null` in the case of [Unknown]. */
26+
public val value: T?
27+
28+
/**
29+
* The string value of the enum, either the [Enum.name] of [Known.value] in the case of [Known] or
30+
* the `stringValue` given to the constructor in the case of [Unknown].
31+
*/
32+
public val stringValue: String
33+
34+
/**
35+
* Represents an unknown enum value.
36+
*
37+
* This could happen, for example, if an enum gained a new value but this code was compiled for
38+
* the older version that lacked the new enum value. Instead of failing, the unknown enum value
39+
* will be gracefully mapped to [Unknown].
40+
*/
41+
public class Unknown(
42+
/** The unknown string value. */
43+
public override val stringValue: String
44+
) : EnumValue<Nothing> {
45+
46+
/** Always `null`. */
47+
override val value: Nothing? = null
48+
49+
/**
50+
* Compares this object with another object for equality.
51+
*
52+
* @param other The object to compare to this for equality.
53+
* @return true if, and only if, the other object is an instance of [Unknown] whose
54+
* [stringValue] compares equal to this object's [stringValue] using the `==` operator.
55+
*/
56+
override fun equals(other: Any?): Boolean = other is Unknown && stringValue == other.stringValue
57+
58+
/**
59+
* Calculates and returns the hash code for this object.
60+
*
61+
* The hash code is _not_ guaranteed to be stable across application restarts.
62+
*
63+
* @return the hash code for this object, that incorporates the values of this object's public
64+
* properties.
65+
*/
66+
override fun hashCode(): Int = stringValue.hashCode()
67+
68+
/**
69+
* Returns a string representation of this object, useful for debugging.
70+
*
71+
* The string representation is _not_ guaranteed to be stable and may change without notice at
72+
* any time. Therefore, the only recommended usage of the returned string is debugging and/or
73+
* logging. Namely, parsing the returned string or storing the returned string in non-volatile
74+
* storage should generally be avoided in order to be robust in case that the string
75+
* representation changes.
76+
*/
77+
override fun toString(): String = "Unknown($stringValue)"
78+
79+
/** Creates and returns a new [Unknown] instance with the given property values. */
80+
public fun copy(stringValue: String = this.stringValue): Unknown = Unknown(stringValue)
81+
}
82+
83+
/**
84+
* Represents a known enum value.
85+
*
86+
* @param value The enum value.
87+
*/
88+
public class Known<T : Enum<T>>(
89+
/** The enum value wrapped by this object. */
90+
override val value: T
91+
) : EnumValue<T> {
92+
93+
/** [Enum.name] of [value]. */
94+
override val stringValue: String
95+
get() = value.name
96+
97+
/**
98+
* Compares this object with another object for equality.
99+
*
100+
* @param other The object to compare to this for equality.
101+
* @return true if, and only if, the other object is an instance of [Known] whose [value]
102+
* compares equal to this object's [value] using the `==` operator.
103+
*/
104+
override fun equals(other: Any?): Boolean = other is Known<*> && value == other.value
105+
106+
/**
107+
* Calculates and returns the hash code for this object.
108+
*
109+
* The hash code is _not_ guaranteed to be stable across application restarts.
110+
*
111+
* @return the hash code for this object, that incorporates the values of this object's public
112+
* properties.
113+
*/
114+
override fun hashCode(): Int = value.hashCode()
115+
116+
/**
117+
* Returns a string representation of this object, useful for debugging.
118+
*
119+
* The string representation is _not_ guaranteed to be stable and may change without notice at
120+
* any time. Therefore, the only recommended usage of the returned string is debugging and/or
121+
* logging. Namely, parsing the returned string or storing the returned string in non-volatile
122+
* storage should generally be avoided in order to be robust in case that the string
123+
* representation changes.
124+
*/
125+
override fun toString(): String = "Known(${value.name})"
126+
127+
/** Creates and returns a new [Known] instance with the given property values. */
128+
public fun copy(value: T = this.value): Known<T> = Known(value)
129+
}
130+
}

0 commit comments

Comments
 (0)