From 913475859ec62958e721b472826df24ac898d547 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 24 Sep 2024 19:23:43 +0000 Subject: [PATCH 1/2] Refactor to avoid spurious XXXKt classes in api.txt --- firebase-dataconnect/api.txt | 60 +- .../google/firebase/dataconnect/AnyValue.kt | 179 ++--- .../firebase/dataconnect/ConnectorConfig.kt | 16 +- .../dataconnect/DataConnectSettings.kt | 12 +- .../dataconnect/FirebaseDataConnect.kt | 7 +- .../dataconnect/core/DataConnectAppCheck.kt | 2 + .../dataconnect/core/DataConnectAuth.kt | 2 + .../DataConnectCredentialsTokenManager.kt | 32 +- .../dataconnect/core/DataConnectGrpcClient.kt | 97 +-- .../core/DataConnectGrpcMetadata.kt | 5 +- .../dataconnect/core/DataConnectGrpcRPCs.kt | 9 +- .../core/FirebaseDataConnectFactory.kt | 39 +- .../core/FirebaseDataConnectImpl.kt | 3 + .../firebase/dataconnect/core/Globals.kt | 132 ++++ .../firebase/dataconnect/core/Logger.kt | 70 +- .../dataconnect/core/MutationRefImpl.kt | 59 +- .../firebase/dataconnect/core/QueryRefImpl.kt | 21 - .../dataconnect/core/QuerySubscriptionImpl.kt | 1 + .../dataconnect/querymgr/LiveQueries.kt | 11 +- .../dataconnect/querymgr/LiveQuery.kt | 7 +- .../querymgr/RegisteredDataDeserialzer.kt | 9 +- .../serializers/TimestampSerializer.kt | 54 +- .../dataconnect/serializers/UUIDSerializer.kt | 17 +- .../util/AlphanumericStringUtil.kt | 73 +++ .../dataconnect/util/NullOutputStream.kt | 24 + .../dataconnect/util/NullableReference.kt | 22 + .../dataconnect/util/ProtoStructDecoder.kt | 220 ++++--- .../dataconnect/util/ProtoStructEncoder.kt | 42 +- .../firebase/dataconnect/util/ProtoUtil.kt | 615 ++++++++++-------- .../dataconnect/util/ReferenceCounted.kt | 108 +++ .../dataconnect/util/SequencedReference.kt | 79 +++ .../dataconnect/util/SuspendingLazy.kt | 67 ++ .../google/firebase/dataconnect/util/Util.kt | 281 -------- .../firebase/dataconnect/AnyValueUnitTest.kt | 2 +- .../dataconnect/ProtoStructDecoderUnitTest.kt | 6 +- .../dataconnect/ProtoStructEncoderUnitTest.kt | 4 +- .../firebase/dataconnect/UtilUnitTest.kt | 2 +- .../core/DataConnectAuthUnitTest.kt | 1 + .../core/DataConnectGrpcClientUnitTest.kt | 7 +- .../core/MutationRefImplUnitTest.kt | 9 +- .../dataconnect/core/QueryRefImplUnitTest.kt | 1 + .../TimestampSerializerUnitTest.kt | 6 +- .../firebase/dataconnect/testutil/Arbs.kt | 2 +- .../testutil/DataConnectAnySerializer.kt | 8 +- .../dataconnect/testutil/MockLogger.kt | 1 + 45 files changed, 1313 insertions(+), 1111 deletions(-) create mode 100644 firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Globals.kt create mode 100644 firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/AlphanumericStringUtil.kt create mode 100644 firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/NullOutputStream.kt create mode 100644 firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/NullableReference.kt create mode 100644 firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ReferenceCounted.kt create mode 100644 firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/SequencedReference.kt create mode 100644 firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/SuspendingLazy.kt delete mode 100644 firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/Util.kt diff --git a/firebase-dataconnect/api.txt b/firebase-dataconnect/api.txt index 31d114ce21c..3e7d18e11d0 100644 --- a/firebase-dataconnect/api.txt +++ b/firebase-dataconnect/api.txt @@ -7,23 +7,25 @@ package com.google.firebase.dataconnect { ctor public AnyValue(@NonNull String value); ctor public AnyValue(boolean value); ctor public AnyValue(double value); - method public T decode(@NonNull kotlinx.serialization.DeserializationStrategy deserializer, @Nullable kotlinx.serialization.modules.SerializersModule serializersModule = null); - method public inline T decode(); method @NonNull public Object getValue(); property @NonNull public final Object value; field @NonNull public static final com.google.firebase.dataconnect.AnyValue.Companion Companion; } public static final class AnyValue.Companion { - method @NonNull public com.google.firebase.dataconnect.AnyValue encode(@Nullable T value, @NonNull kotlinx.serialization.SerializationStrategy serializer, @Nullable kotlinx.serialization.modules.SerializersModule serializersModule = null); - method public inline com.google.firebase.dataconnect.AnyValue encode(@Nullable T value); - method @NonNull public com.google.firebase.dataconnect.AnyValue fromAny(@NonNull Object value); - method @Nullable public com.google.firebase.dataconnect.AnyValue fromNullableAny(@Nullable Object value); + } + + public final class AnyValueKt { + method public static T decode(@NonNull com.google.firebase.dataconnect.AnyValue, @NonNull kotlinx.serialization.DeserializationStrategy deserializer, @Nullable kotlinx.serialization.modules.SerializersModule serializersModule = null); + method public static inline T decode(@NonNull com.google.firebase.dataconnect.AnyValue); + method @NonNull public static com.google.firebase.dataconnect.AnyValue encode(@NonNull com.google.firebase.dataconnect.AnyValue.Companion, @Nullable T value, @NonNull kotlinx.serialization.SerializationStrategy serializer, @Nullable kotlinx.serialization.modules.SerializersModule serializersModule = null); + method public static inline com.google.firebase.dataconnect.AnyValue encode(@NonNull com.google.firebase.dataconnect.AnyValue.Companion, @Nullable T value); + method @NonNull public static com.google.firebase.dataconnect.AnyValue fromAny(@NonNull com.google.firebase.dataconnect.AnyValue.Companion, @NonNull Object value); + method @Nullable public static com.google.firebase.dataconnect.AnyValue fromNullableAny(@NonNull com.google.firebase.dataconnect.AnyValue.Companion, @Nullable Object value); } public final class ConnectorConfig { ctor public ConnectorConfig(@NonNull String connector, @NonNull String location, @NonNull String serviceId); - method @NonNull public com.google.firebase.dataconnect.ConnectorConfig copy(@NonNull String connector = connector, @NonNull String location = location, @NonNull String serviceId = serviceId); method @NonNull public String getConnector(); method @NonNull public String getLocation(); method @NonNull public String getServiceId(); @@ -32,13 +34,16 @@ package com.google.firebase.dataconnect { property @NonNull public final String serviceId; } + public final class ConnectorConfigKt { + method @NonNull public static com.google.firebase.dataconnect.ConnectorConfig copy(@NonNull com.google.firebase.dataconnect.ConnectorConfig, @NonNull String connector = connector, @NonNull String location = location, @NonNull String serviceId = serviceId); + } + public class DataConnectException extends java.lang.Exception { ctor public DataConnectException(@NonNull String message, @Nullable Throwable cause = null); } public final class DataConnectSettings { ctor public DataConnectSettings(@NonNull String host = "firebasedataconnect.googleapis.com", boolean sslEnabled = true); - method @NonNull public com.google.firebase.dataconnect.DataConnectSettings copy(@NonNull String host = host, boolean sslEnabled = sslEnabled); method @NonNull public String getHost(); method public boolean getSslEnabled(); property @NonNull public final String host; @@ -46,6 +51,7 @@ package com.google.firebase.dataconnect { } public final class DataConnectSettingsKt { + method @NonNull public static com.google.firebase.dataconnect.DataConnectSettings copy(@NonNull com.google.firebase.dataconnect.DataConnectSettings, @NonNull String host = host, boolean sslEnabled = sslEnabled); } public interface FirebaseDataConnect extends java.lang.AutoCloseable { @@ -216,28 +222,6 @@ package com.google.firebase.dataconnect { } -package com.google.firebase.dataconnect.core { - - public final class DataConnectCredentialsTokenManagerKt { - } - - public final class DataConnectGrpcClientKt { - } - - public final class FirebaseDataConnectFactoryKt { - } - - public final class LoggerKt { - } - - public final class MutationRefImplKt { - } - - public final class QueryRefImplKt { - } - -} - package com.google.firebase.dataconnect.generated { public interface GeneratedConnector { @@ -307,19 +291,3 @@ package com.google.firebase.dataconnect.serializers { } -package com.google.firebase.dataconnect.util { - - public final class ProtoStructDecoderKt { - } - - public final class ProtoStructEncoderKt { - } - - public final class ProtoUtilKt { - } - - public final class UtilKt { - } - -} - diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/AnyValue.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/AnyValue.kt index 2657d28d39d..ec22fde7ce8 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/AnyValue.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/AnyValue.kt @@ -16,12 +16,13 @@ package com.google.firebase.dataconnect +import com.google.firebase.dataconnect.AnyValue.Companion.serializer import com.google.firebase.dataconnect.serializers.AnyValueSerializer -import com.google.firebase.dataconnect.util.decodeFromValue -import com.google.firebase.dataconnect.util.encodeToValue -import com.google.firebase.dataconnect.util.toAny -import com.google.firebase.dataconnect.util.toCompactString -import com.google.firebase.dataconnect.util.toValueProto +import com.google.firebase.dataconnect.util.ProtoUtil.decodeFromValue +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToValue +import com.google.firebase.dataconnect.util.ProtoUtil.toAny +import com.google.firebase.dataconnect.util.ProtoUtil.toCompactString +import com.google.firebase.dataconnect.util.ProtoUtil.toValueProto import com.google.protobuf.Struct import com.google.protobuf.Value import kotlinx.serialization.DeserializationStrategy @@ -141,30 +142,6 @@ public class AnyValue internal constructor(internal val protoValue: Value) { // of this class asserts that `protoValue` is not NULL_VALUE. get() = protoValue.toAny()!! - /** - * Decodes the encapsulated value using the given deserializer. - * - * @param deserializer The deserializer for the decoder to use. - * @param serializersModule a [SerializersModule] to use during deserialization; may be `null` - * (the default) to _not_ use a [SerializersModule] to use during deserialization. - * - * @return the object of type `T` created by decoding the encapsulated value using the given - * deserializer. - */ - public fun decode( - deserializer: DeserializationStrategy, - serializersModule: SerializersModule? = null - ): T = decodeFromValue(protoValue, deserializer, serializersModule) - - /** - * Decodes the encapsulated value using the _default_ serializer for the return type, as computed - * by [serializer]. - * - * @return the object of type `T` created by decoding the encapsulated value using the _default_ - * serializer for the return type, as computed by [serializer]. - */ - public inline fun decode(): T = decode(serializer()) - /** * Compares this object with another object for equality. * @@ -196,67 +173,95 @@ public class AnyValue internal constructor(internal val protoValue: Value) { */ override fun toString(): String = protoValue.toCompactString(keySortSelector = { it }) - public companion object { + /** + * Provides extension functions that can be used independently of a specified [AnyValue] instance. + */ + public companion object +} - /** - * Encodes the given value using the given serializer to an [AnyValue] object, and returns it. - * - * @param value the value to serialize. - * @param serializer the serializer for the encoder to use. - * @param serializersModule a [SerializersModule] to use during serialization; may be `null` - * (the default) to _not_ use a [SerializersModule] to use during serialization. - * - * @return a new `AnyValue` object whose encapsulated value is the encoding of the given value - * when decoded with the given serializer. - */ - public fun encode( - value: T, - serializer: SerializationStrategy, - serializersModule: SerializersModule? = null - ): AnyValue = AnyValue(encodeToValue(value, serializer, serializersModule)) +/** + * Decodes the encapsulated value using the given deserializer. + * + * @param deserializer The deserializer for the decoder to use. + * @param serializersModule a [SerializersModule] to use during deserialization; may be `null` (the + * default) to _not_ use a [SerializersModule] to use during deserialization. + * + * @return the object of type `T` created by decoding the encapsulated value using the given + * deserializer. + */ +public fun AnyValue.decode( + deserializer: DeserializationStrategy, + serializersModule: SerializersModule? = null +): T = decodeFromValue(protoValue, deserializer, serializersModule) - /** - * Encodes the given value using the given _default_ serializer for the given object, as - * computed by [serializer]. - * - * @param value the value to serialize. - * @return a new `AnyValue` object whose encapsulated value is the encoding of the given value - * when decoded with the _default_ serializer for the given object, as computed by [serializer]. - */ - public inline fun encode(value: T): AnyValue = encode(value, serializer()) +/** + * Decodes the encapsulated value using the _default_ serializer for the return type, as computed by + * [serializer]. + * + * @return the object of type `T` created by decoding the encapsulated value using the _default_ + * serializer for the return type, as computed by [serializer]. + */ +public inline fun AnyValue.decode(): T = decode(serializer()) - /** - * Creates and returns an `AnyValue` object created using the `AnyValue` constructor that - * corresponds to the runtime type of the given value, or returns `null` if the given value is - * `null`. - * - * @throws IllegalArgumentException if the given value is not supported by `AnyValue`; see the - * `AnyValue` constructor for details. - */ - @JvmName("fromNullableAny") - public fun fromAny(value: Any?): AnyValue? = if (value === null) null else fromAny(value) +/** + * Encodes the given value using the given serializer to an [AnyValue] object, and returns it. + * + * @param value the value to serialize. + * @param serializer the serializer for the encoder to use. + * @param serializersModule a [SerializersModule] to use during serialization; may be `null` (the + * default) to _not_ use a [SerializersModule] to use during serialization. + * + * @return a new `AnyValue` object whose encapsulated value is the encoding of the given value when + * decoded with the given serializer. + */ +public fun AnyValue.Companion.encode( + value: T, + serializer: SerializationStrategy, + serializersModule: SerializersModule? = null +): AnyValue = AnyValue(encodeToValue(value, serializer, serializersModule)) - /** - * Creates and returns an `AnyValue` object created using the `AnyValue` constructor that - * corresponds to the runtime type of the given value. - * - * @throws IllegalArgumentException if the given value is not supported by `AnyValue`; see the - * `AnyValue` constructor for details. - */ - public fun fromAny(value: Any): AnyValue { - @Suppress("UNCHECKED_CAST") - return when (value) { - is String -> AnyValue(value) - is Boolean -> AnyValue(value) - is Double -> AnyValue(value) - is List<*> -> AnyValue(value) - is Map<*, *> -> AnyValue(value as Map) - else -> - throw IllegalArgumentException( - "unsupported type: ${value::class.qualifiedName}" + - " (supported types: null, String, Boolean, Double, List, Map)" - ) - } - } +/** + * Encodes the given value using the given _default_ serializer for the given object, as computed by + * [serializer]. + * + * @param value the value to serialize. + * @return a new `AnyValue` object whose encapsulated value is the encoding of the given value when + * decoded with the _default_ serializer for the given object, as computed by [serializer]. + */ +public inline fun AnyValue.Companion.encode(value: T): AnyValue = + encode(value, serializer()) + +/** + * Creates and returns an `AnyValue` object created using the `AnyValue` constructor that + * corresponds to the runtime type of the given value, or returns `null` if the given value is + * `null`. + * + * @throws IllegalArgumentException if the given value is not supported by `AnyValue`; see the + * `AnyValue` constructor for details. + */ +@JvmName("fromNullableAny") +public fun AnyValue.Companion.fromAny(value: Any?): AnyValue? = + if (value === null) null else fromAny(value) + +/** + * Creates and returns an `AnyValue` object created using the `AnyValue` constructor that + * corresponds to the runtime type of the given value. + * + * @throws IllegalArgumentException if the given value is not supported by `AnyValue`; see the + * `AnyValue` constructor for details. + */ +public fun AnyValue.Companion.fromAny(value: Any): AnyValue { + @Suppress("UNCHECKED_CAST") + return when (value) { + is String -> AnyValue(value) + is Boolean -> AnyValue(value) + is Double -> AnyValue(value) + is List<*> -> AnyValue(value) + is Map<*, *> -> AnyValue(value as Map) + else -> + throw IllegalArgumentException( + "unsupported type: ${value::class.qualifiedName}" + + " (supported types: null, String, Boolean, Double, List, Map)" + ) } } diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/ConnectorConfig.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/ConnectorConfig.kt index c55a61f5d6f..d1fa9f999ab 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/ConnectorConfig.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/ConnectorConfig.kt @@ -37,14 +37,6 @@ public class ConnectorConfig( public val serviceId: String ) { - /** Creates and returns a new [ConnectorConfig] instance with the given property values. */ - public fun copy( - connector: String = this.connector, - location: String = this.location, - serviceId: String = this.serviceId - ): ConnectorConfig = - ConnectorConfig(connector = connector, location = location, serviceId = serviceId) - /** * Compares this object with another object for equality. * @@ -85,3 +77,11 @@ public class ConnectorConfig( override fun toString(): String = "ConnectorConfig(connector=$connector, location=$location, serviceId=$serviceId)" } + +/** Creates and returns a new [ConnectorConfig] instance with the given property values. */ +public fun ConnectorConfig.copy( + connector: String = this.connector, + location: String = this.location, + serviceId: String = this.serviceId +): ConnectorConfig = + ConnectorConfig(connector = connector, location = location, serviceId = serviceId) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/DataConnectSettings.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/DataConnectSettings.kt index ad1de5e260c..2d9f5e1c9eb 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/DataConnectSettings.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/DataConnectSettings.kt @@ -37,12 +37,6 @@ public class DataConnectSettings( public val sslEnabled: Boolean = true ) { - /** Creates and returns a new [DataConnectSettings] instance with the given property values. */ - public fun copy( - host: String = this.host, - sslEnabled: Boolean = this.sslEnabled - ): DataConnectSettings = DataConnectSettings(host = host, sslEnabled = sslEnabled) - /** * Compares this object with another object for equality. * @@ -79,4 +73,10 @@ public class DataConnectSettings( override fun toString(): String = "DataConnectSettings(host=$host, sslEnabled=$sslEnabled)" } +/** Creates and returns a new [DataConnectSettings] instance with the given property values. */ +public fun DataConnectSettings.copy( + host: String = this.host, + sslEnabled: Boolean = this.sslEnabled +): DataConnectSettings = DataConnectSettings(host = host, sslEnabled = sslEnabled) + internal fun DataConnectSettings.isDefaultHost() = host == DataConnectSettings().host diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/FirebaseDataConnect.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/FirebaseDataConnect.kt index 2e5393d2b19..425cd625c63 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/FirebaseDataConnect.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/FirebaseDataConnect.kt @@ -21,6 +21,7 @@ import com.google.firebase.Firebase import com.google.firebase.FirebaseApp import com.google.firebase.app import com.google.firebase.dataconnect.core.FirebaseDataConnectFactory +import com.google.firebase.dataconnect.core.LoggerGlobals import kotlinx.coroutines.CoroutineScope import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy @@ -440,8 +441,4 @@ public fun FirebaseDataConnect.Companion.getInstance( * logging, which is especially useful when reporting issues to Google or investigating problems * yourself. Setting it to [LogLevel.NONE] will disable all logging. */ -public var FirebaseDataConnect.Companion.logLevel: LogLevel - get() = com.google.firebase.dataconnect.core.logLevel - set(newLogLevel) { - com.google.firebase.dataconnect.core.logLevel = newLogLevel - } +public var FirebaseDataConnect.Companion.logLevel: LogLevel by LoggerGlobals::logLevel diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt index 3128dfd078d..176aba53832 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt @@ -20,6 +20,8 @@ import com.google.firebase.annotations.DeferredApi import com.google.firebase.appcheck.AppCheckTokenResult import com.google.firebase.appcheck.interop.AppCheckTokenListener import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider +import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken +import com.google.firebase.dataconnect.core.LoggerGlobals.debug import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.tasks.await diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt index a115c460a06..1efd00af83e 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt @@ -19,6 +19,8 @@ package com.google.firebase.dataconnect.core import com.google.firebase.annotations.DeferredApi import com.google.firebase.auth.internal.IdTokenListener import com.google.firebase.auth.internal.InternalAuthProvider +import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken +import com.google.firebase.dataconnect.core.LoggerGlobals.debug import com.google.firebase.internal.InternalTokenResult import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt index 49c2c536e14..74c80472e52 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt @@ -20,8 +20,11 @@ import com.google.firebase.annotations.DeferredApi import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider import com.google.firebase.auth.internal.InternalAuthProvider import com.google.firebase.dataconnect.DataConnectException +import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken +import com.google.firebase.dataconnect.core.LoggerGlobals.debug +import com.google.firebase.dataconnect.core.LoggerGlobals.warn import com.google.firebase.dataconnect.util.SequencedReference -import com.google.firebase.dataconnect.util.nextSequenceNumber +import com.google.firebase.dataconnect.util.SequencedReference.Companion.nextSequenceNumber import com.google.firebase.inject.Deferred.DeferredHandler import com.google.firebase.inject.Provider import com.google.firebase.internal.api.FirebaseNoSignedInUserException @@ -506,30 +509,3 @@ internal sealed class DataConnectCredentialsTokenManager( } } } - -/** - * Returns a new string that is equal to this string but only includes a chunk from the beginning - * and the end. - * - * This method assumes that the contents of this string are an access token. The returned string - * will have enough information to reason about the access token in logs without giving its value - * away. - */ -internal fun String.toScrubbedAccessToken(): String = - if (this == PLACEHOLDER_APP_CHECK_TOKEN) { - "$this (the \"placeholder\" AppCheck token)" - } else if (length < 30) { - "" - } else { - buildString { - append(this@toScrubbedAccessToken, 0, 6) - append("") - append( - this@toScrubbedAccessToken, - this@toScrubbedAccessToken.length - 6, - this@toScrubbedAccessToken.length - ) - } - } - -private const val PLACEHOLDER_APP_CHECK_TOKEN = "eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==" diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcClient.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcClient.kt index 7a545837bca..b2d2270056b 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcClient.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcClient.kt @@ -17,8 +17,10 @@ package com.google.firebase.dataconnect.core import com.google.firebase.dataconnect.* -import com.google.firebase.dataconnect.util.decodeFromStruct -import com.google.firebase.dataconnect.util.toMap +import com.google.firebase.dataconnect.core.DataConnectGrpcClientGlobals.toDataConnectError +import com.google.firebase.dataconnect.core.LoggerGlobals.warn +import com.google.firebase.dataconnect.util.ProtoUtil.decodeFromStruct +import com.google.firebase.dataconnect.util.ProtoUtil.toMap import com.google.protobuf.ListValue import com.google.protobuf.Struct import com.google.protobuf.Value @@ -125,50 +127,61 @@ internal class DataConnectGrpcClient( } } -internal fun ListValue.toPathSegment() = - valuesList.map { - when (val kind = it.kindCase) { - Value.KindCase.STRING_VALUE -> DataConnectError.PathSegment.Field(it.stringValue) - Value.KindCase.NUMBER_VALUE -> DataConnectError.PathSegment.ListIndex(it.numberValue.toInt()) - else -> DataConnectError.PathSegment.Field("invalid PathSegment kind: $kind") +/** + * Holder for "global" functions related to [DataConnectGrpcClient]. + * + * Technically, these functions _could_ be defined as free functions; however, doing so creates a + * DataConnectGrpcClientKit Java class with public visibility, which pollutes the public API. Using + * an "internal" object, instead, to gather together the top-level functions avoids this public API + * pollution. + */ +internal object DataConnectGrpcClientGlobals { + private fun ListValue.toPathSegment() = + valuesList.map { + when (val kind = it.kindCase) { + Value.KindCase.STRING_VALUE -> DataConnectError.PathSegment.Field(it.stringValue) + Value.KindCase.NUMBER_VALUE -> + DataConnectError.PathSegment.ListIndex(it.numberValue.toInt()) + else -> DataConnectError.PathSegment.Field("invalid PathSegment kind: $kind") + } } - } -internal fun List.toSourceLocations(): List = - buildList { - this@toSourceLocations.forEach { - add(DataConnectError.SourceLocation(line = it.line, column = it.column)) + private fun List.toSourceLocations(): List = + buildList { + this@toSourceLocations.forEach { + add(DataConnectError.SourceLocation(line = it.line, column = it.column)) + } } - } -internal fun GraphqlError.toDataConnectError() = - DataConnectError( - message = message, - path = path.toPathSegment(), - this.locationsList.toSourceLocations() - ) + fun GraphqlError.toDataConnectError() = + DataConnectError( + message = message, + path = path.toPathSegment(), + this.locationsList.toSourceLocations() + ) -internal fun DataConnectGrpcClient.OperationResult.deserialize( - deserializer: DeserializationStrategy, - serializersModule: SerializersModule?, -): T = - if (deserializer === DataConnectUntypedData) { - @Suppress("UNCHECKED_CAST") - DataConnectUntypedData(data?.toMap(), errors) as T - } else if (data === null) { - if (errors.isNotEmpty()) { - throw DataConnectException("operation failed: errors=$errors") + fun DataConnectGrpcClient.OperationResult.deserialize( + deserializer: DeserializationStrategy, + serializersModule: SerializersModule?, + ): T = + if (deserializer === DataConnectUntypedData) { + @Suppress("UNCHECKED_CAST") + DataConnectUntypedData(data?.toMap(), errors) as T + } else if (data === null) { + if (errors.isNotEmpty()) { + throw DataConnectException("operation failed: errors=$errors") + } else { + throw DataConnectException("no data included in result") + } + } else if (errors.isNotEmpty()) { + throw DataConnectException("operation failed: errors=$errors (data=$data)") } else { - throw DataConnectException("no data included in result") - } - } else if (errors.isNotEmpty()) { - throw DataConnectException("operation failed: errors=$errors (data=$data)") - } else { - try { - decodeFromStruct(data, deserializer, serializersModule) - } catch (dataConnectException: DataConnectException) { - throw dataConnectException - } catch (throwable: Throwable) { - throw DataConnectException("decoding response data failed: $throwable", throwable) + try { + decodeFromStruct(data, deserializer, serializersModule) + } catch (dataConnectException: DataConnectException) { + throw dataConnectException + } catch (throwable: Throwable) { + throw DataConnectException("decoding response data failed: $throwable", throwable) + } } - } +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadata.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadata.kt index b47864d3b82..dd5ae849264 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadata.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadata.kt @@ -20,7 +20,10 @@ import android.os.Build import com.google.firebase.FirebaseApp import com.google.firebase.dataconnect.BuildConfig import com.google.firebase.dataconnect.FirebaseDataConnect -import com.google.firebase.dataconnect.util.buildStructProto +import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken +import com.google.firebase.dataconnect.core.LoggerGlobals.Logger +import com.google.firebase.dataconnect.core.LoggerGlobals.debug +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto import com.google.protobuf.Struct import io.grpc.Metadata diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcRPCs.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcRPCs.kt index 97822dbdc2e..c87e276d667 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcRPCs.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcRPCs.kt @@ -20,10 +20,13 @@ import android.content.Context import com.google.android.gms.security.ProviderInstaller import com.google.firebase.dataconnect.FirebaseDataConnect import com.google.firebase.dataconnect.core.DataConnectGrpcMetadata.Companion.toStructProto +import com.google.firebase.dataconnect.core.LoggerGlobals.Logger +import com.google.firebase.dataconnect.core.LoggerGlobals.debug +import com.google.firebase.dataconnect.core.LoggerGlobals.warn +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.toCompactString +import com.google.firebase.dataconnect.util.ProtoUtil.toStructProto import com.google.firebase.dataconnect.util.SuspendingLazy -import com.google.firebase.dataconnect.util.buildStructProto -import com.google.firebase.dataconnect.util.toCompactString -import com.google.firebase.dataconnect.util.toStructProto import com.google.protobuf.Struct import google.firebase.dataconnect.proto.ConnectorServiceGrpc import google.firebase.dataconnect.proto.ConnectorServiceGrpcKt diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/FirebaseDataConnectFactory.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/FirebaseDataConnectFactory.kt index 909847d3ca8..ef410549e7b 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/FirebaseDataConnectFactory.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/FirebaseDataConnectFactory.kt @@ -123,6 +123,27 @@ internal class FirebaseDataConnectFactory( } } } + + private companion object { + private fun throwIfIncompatible( + key: FirebaseDataConnectInstanceKey, + instance: FirebaseDataConnect, + settings: DataConnectSettings? + ) { + val keyStr = key.run { "serviceId=$serviceId, location=$location, connector=$connector" } + if (settings !== null && instance.settings != settings) { + throw IllegalArgumentException( + "The settings of the FirebaseDataConnect instance with [$keyStr] is " + + "'${instance.settings}', which is different from the given settings: $settings; " + + "to get a FirebaseDataConnect with [$keyStr] but different settings, first call " + + "close() on the existing FirebaseDataConnect instance, then call getInstance() " + + "again with the desired settings. Alternately, call getInstance() with null " + + "settings to use whatever settings are configured in the existing " + + "FirebaseDataConnect instance." + ) + } + } + } } private data class FirebaseDataConnectInstanceKey( @@ -132,21 +153,3 @@ private data class FirebaseDataConnectInstanceKey( ) { override fun toString() = "serviceId=$serviceId, location=$location, connector=$connector" } - -private fun throwIfIncompatible( - key: FirebaseDataConnectInstanceKey, - instance: FirebaseDataConnect, - settings: DataConnectSettings? -) { - val keyStr = key.run { "serviceId=$serviceId, location=$location, connector=$connector" } - if (settings !== null && instance.settings != settings) { - throw IllegalArgumentException( - "The settings of the FirebaseDataConnect instance with [$keyStr] is " + - "'${instance.settings}', which is different from the given settings: $settings; " + - "to get a FirebaseDataConnect with [$keyStr] but different settings, first call " + - "close() on the existing FirebaseDataConnect instance, then call getInstance() again " + - "with the desired settings. Alternately, call getInstance() with null settings to " + - "use whatever settings are configured in the existing FirebaseDataConnect instance." - ) - } -} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/FirebaseDataConnectImpl.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/FirebaseDataConnectImpl.kt index 85d9fa34a6b..564cdf3b003 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/FirebaseDataConnectImpl.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/FirebaseDataConnectImpl.kt @@ -25,6 +25,9 @@ import com.google.firebase.dataconnect.DataConnectSettings import com.google.firebase.dataconnect.FirebaseDataConnect import com.google.firebase.dataconnect.FirebaseDataConnect.MutationRefOptionsBuilder import com.google.firebase.dataconnect.FirebaseDataConnect.QueryRefOptionsBuilder +import com.google.firebase.dataconnect.core.LoggerGlobals.Logger +import com.google.firebase.dataconnect.core.LoggerGlobals.debug +import com.google.firebase.dataconnect.core.LoggerGlobals.warn import com.google.firebase.dataconnect.isDefaultHost import com.google.firebase.dataconnect.querymgr.LiveQueries import com.google.firebase.dataconnect.querymgr.LiveQuery diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Globals.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Globals.kt new file mode 100644 index 00000000000..0bbaa2b6038 --- /dev/null +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Globals.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.dataconnect.core + +import com.google.firebase.dataconnect.FirebaseDataConnect +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.SerializersModule + +/** + * Holder for "global" functions in this package. + * + * Technically, these functions _could_ be defined as free functions; however, doing so creates + * XXXKt Java classes whose visibility cannot be controlled. Using an "internal" object, instead, to + * gather together the top-level functions avoids this public API pollution. + */ +internal object Globals { + @Suppress("SpellCheckingInspection") + private const val PLACEHOLDER_APP_CHECK_TOKEN = "eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==" + + /** + * Returns a new string that is equal to this string but only includes a chunk from the beginning + * and the end. + * + * This method assumes that the contents of this string are an access token. The returned string + * will have enough information to reason about the access token in logs without giving its value + * away. + */ + fun String.toScrubbedAccessToken(): String = + if (this == PLACEHOLDER_APP_CHECK_TOKEN) { + "$this (the \"placeholder\" AppCheck token)" + } else if (length < 30) { + "" + } else { + buildString { + append(this@toScrubbedAccessToken, 0, 6) + append("") + append( + this@toScrubbedAccessToken, + this@toScrubbedAccessToken.length - 6, + this@toScrubbedAccessToken.length + ) + } + } + + fun MutationRefImpl.copy( + dataConnect: FirebaseDataConnectInternal = this.dataConnect, + operationName: String = this.operationName, + variables: Variables = this.variables, + dataDeserializer: DeserializationStrategy = this.dataDeserializer, + variablesSerializer: SerializationStrategy = this.variablesSerializer, + callerSdkType: FirebaseDataConnect.CallerSdkType = this.callerSdkType, + variablesSerializersModule: SerializersModule? = this.variablesSerializersModule, + dataSerializersModule: SerializersModule? = this.dataSerializersModule, + ) = + MutationRefImpl( + dataConnect = dataConnect, + operationName = operationName, + variables = variables, + dataDeserializer = dataDeserializer, + variablesSerializer = variablesSerializer, + callerSdkType = callerSdkType, + variablesSerializersModule = variablesSerializersModule, + dataSerializersModule = dataSerializersModule, + ) + + fun MutationRefImpl.withVariablesSerializer( + variables: NewVariables, + variablesSerializer: SerializationStrategy, + variablesSerializersModule: SerializersModule? = this.variablesSerializersModule, + ): MutationRefImpl = + MutationRefImpl( + dataConnect = dataConnect, + operationName = operationName, + variables = variables, + dataDeserializer = dataDeserializer, + variablesSerializer = variablesSerializer, + callerSdkType = callerSdkType, + variablesSerializersModule = variablesSerializersModule, + dataSerializersModule = dataSerializersModule, + ) + + fun MutationRefImpl<*, Variables>.withDataDeserializer( + dataDeserializer: DeserializationStrategy, + dataSerializersModule: SerializersModule? = this.dataSerializersModule, + ): MutationRefImpl = + MutationRefImpl( + dataConnect = dataConnect, + operationName = operationName, + variables = variables, + dataDeserializer = dataDeserializer, + variablesSerializer = variablesSerializer, + callerSdkType = callerSdkType, + variablesSerializersModule = variablesSerializersModule, + dataSerializersModule = dataSerializersModule, + ) + + fun QueryRefImpl.copy( + dataConnect: FirebaseDataConnectInternal = this.dataConnect, + operationName: String = this.operationName, + variables: Variables = this.variables, + dataDeserializer: DeserializationStrategy = this.dataDeserializer, + variablesSerializer: SerializationStrategy = this.variablesSerializer, + callerSdkType: FirebaseDataConnect.CallerSdkType = this.callerSdkType, + variablesSerializersModule: SerializersModule? = this.variablesSerializersModule, + dataSerializersModule: SerializersModule? = this.dataSerializersModule, + ) = + QueryRefImpl( + dataConnect = dataConnect, + operationName = operationName, + variables = variables, + dataDeserializer = dataDeserializer, + variablesSerializer = variablesSerializer, + callerSdkType = callerSdkType, + variablesSerializersModule = variablesSerializersModule, + dataSerializersModule = dataSerializersModule, + ) +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Logger.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Logger.kt index f9fd2016fc9..d68cb8492c1 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Logger.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Logger.kt @@ -19,11 +19,10 @@ package com.google.firebase.dataconnect.core import android.util.Log import com.google.firebase.dataconnect.BuildConfig import com.google.firebase.dataconnect.LogLevel +import com.google.firebase.dataconnect.core.LoggerGlobals.LOG_TAG import com.google.firebase.util.nextAlphanumericString import kotlin.random.Random -@Volatile internal var logLevel: LogLevel = LogLevel.WARN - internal interface Logger { val name: String val id: String @@ -32,34 +31,6 @@ internal interface Logger { fun log(exception: Throwable?, level: LogLevel, message: String) } -internal inline fun Logger.debug(message: () -> Any?) { - if (logLevel <= LogLevel.DEBUG) debug("${message()}") -} - -internal fun Logger.debug(message: String) { - if (logLevel <= LogLevel.DEBUG) log(null, LogLevel.DEBUG, message) -} - -internal inline fun Logger.warn(message: () -> Any?) { - if (logLevel <= LogLevel.WARN) warn("${message()}") -} - -internal inline fun Logger.warn(exception: Throwable?, message: () -> Any?) { - if (logLevel <= LogLevel.WARN) warn(exception, "${message()}") -} - -internal fun Logger.warn(message: String) { - warn(null, message) -} - -internal fun Logger.warn(exception: Throwable?, message: String) { - if (logLevel <= LogLevel.WARN) log(exception, LogLevel.WARN, message) -} - -internal fun Logger(name: String): Logger = LoggerImpl(name) - -private const val LOG_TAG = "FirebaseDataConnect" - private class LoggerImpl(override val name: String) : Logger { override val id: String by @@ -76,3 +47,42 @@ private class LoggerImpl(override val name: String) : Logger { } } } + +/** + * Holder for "global" functions related to [Logger]. + * + * Technically, these functions _could_ be defined as free functions; however, doing so creates a + * LoggerKt Java class with public visibility, which pollutes the public API. Using an "internal" + * object, instead, to gather together the top-level functions avoids this public API pollution. + */ +internal object LoggerGlobals { + const val LOG_TAG = "FirebaseDataConnect" + + @Volatile var logLevel: LogLevel = LogLevel.WARN + + inline fun Logger.debug(message: () -> Any?) { + if (logLevel <= LogLevel.DEBUG) debug("${message()}") + } + + fun Logger.debug(message: String) { + if (logLevel <= LogLevel.DEBUG) log(null, LogLevel.DEBUG, message) + } + + inline fun Logger.warn(message: () -> Any?) { + if (logLevel <= LogLevel.WARN) warn("${message()}") + } + + inline fun Logger.warn(exception: Throwable?, message: () -> Any?) { + if (logLevel <= LogLevel.WARN) warn(exception, "${message()}") + } + + fun Logger.warn(message: String) { + warn(null, message) + } + + fun Logger.warn(exception: Throwable?, message: String) { + if (logLevel <= LogLevel.WARN) log(exception, LogLevel.WARN, message) + } + + fun Logger(name: String): Logger = LoggerImpl(name) +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/MutationRefImpl.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/MutationRefImpl.kt index 1b3d2d2edac..59330ab4d3b 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/MutationRefImpl.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/MutationRefImpl.kt @@ -17,8 +17,11 @@ package com.google.firebase.dataconnect.core import com.google.firebase.dataconnect.* -import com.google.firebase.dataconnect.util.encodeToStruct -import com.google.firebase.dataconnect.util.toStructProto +import com.google.firebase.dataconnect.core.DataConnectGrpcClientGlobals.deserialize +import com.google.firebase.dataconnect.core.LoggerGlobals.Logger +import com.google.firebase.dataconnect.core.LoggerGlobals.warn +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToStruct +import com.google.firebase.dataconnect.util.ProtoUtil.toStructProto import com.google.firebase.util.nextAlphanumericString import java.util.Objects import kotlin.random.Random @@ -109,55 +112,3 @@ internal class MutationRefImpl( override fun toString() = "MutationResultImpl(data=$data, ref=$ref)" } } - -internal fun MutationRefImpl.copy( - dataConnect: FirebaseDataConnectInternal = this.dataConnect, - operationName: String = this.operationName, - variables: Variables = this.variables, - dataDeserializer: DeserializationStrategy = this.dataDeserializer, - variablesSerializer: SerializationStrategy = this.variablesSerializer, - callerSdkType: FirebaseDataConnect.CallerSdkType = this.callerSdkType, - variablesSerializersModule: SerializersModule? = this.variablesSerializersModule, - dataSerializersModule: SerializersModule? = this.dataSerializersModule, -) = - MutationRefImpl( - dataConnect = dataConnect, - operationName = operationName, - variables = variables, - dataDeserializer = dataDeserializer, - variablesSerializer = variablesSerializer, - callerSdkType = callerSdkType, - variablesSerializersModule = variablesSerializersModule, - dataSerializersModule = dataSerializersModule, - ) - -internal fun MutationRefImpl.withVariablesSerializer( - variables: NewVariables, - variablesSerializer: SerializationStrategy, - variablesSerializersModule: SerializersModule? = this.variablesSerializersModule, -): MutationRefImpl = - MutationRefImpl( - dataConnect = dataConnect, - operationName = operationName, - variables = variables, - dataDeserializer = dataDeserializer, - variablesSerializer = variablesSerializer, - callerSdkType = callerSdkType, - variablesSerializersModule = variablesSerializersModule, - dataSerializersModule = dataSerializersModule, - ) - -internal fun MutationRefImpl<*, Variables>.withDataDeserializer( - dataDeserializer: DeserializationStrategy, - dataSerializersModule: SerializersModule? = this.dataSerializersModule, -): MutationRefImpl = - MutationRefImpl( - dataConnect = dataConnect, - operationName = operationName, - variables = variables, - dataDeserializer = dataDeserializer, - variablesSerializer = variablesSerializer, - callerSdkType = callerSdkType, - variablesSerializersModule = variablesSerializersModule, - dataSerializersModule = dataSerializersModule, - ) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/QueryRefImpl.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/QueryRefImpl.kt index 4f29fc88bd3..fc35592bba7 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/QueryRefImpl.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/QueryRefImpl.kt @@ -77,24 +77,3 @@ internal class QueryRefImpl( override fun toString() = "QueryResultImpl(data=$data, ref=$ref)" } } - -internal fun QueryRefImpl.copy( - dataConnect: FirebaseDataConnectInternal = this.dataConnect, - operationName: String = this.operationName, - variables: Variables = this.variables, - dataDeserializer: DeserializationStrategy = this.dataDeserializer, - variablesSerializer: SerializationStrategy = this.variablesSerializer, - callerSdkType: FirebaseDataConnect.CallerSdkType = this.callerSdkType, - variablesSerializersModule: SerializersModule? = this.variablesSerializersModule, - dataSerializersModule: SerializersModule? = this.dataSerializersModule, -) = - QueryRefImpl( - dataConnect = dataConnect, - operationName = operationName, - variables = variables, - dataDeserializer = dataDeserializer, - variablesSerializer = variablesSerializer, - callerSdkType = callerSdkType, - variablesSerializersModule = variablesSerializersModule, - dataSerializersModule = dataSerializersModule, - ) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/QuerySubscriptionImpl.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/QuerySubscriptionImpl.kt index ca8496be8ec..58c7fd63e39 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/QuerySubscriptionImpl.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/QuerySubscriptionImpl.kt @@ -17,6 +17,7 @@ package com.google.firebase.dataconnect.core import com.google.firebase.dataconnect.* +import com.google.firebase.dataconnect.core.Globals.copy import com.google.firebase.dataconnect.util.NullableReference import com.google.firebase.dataconnect.util.SequencedReference import java.util.Objects diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/LiveQueries.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/LiveQueries.kt index 0090dda7396..9fbb587a50b 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/LiveQueries.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/LiveQueries.kt @@ -18,12 +18,13 @@ package com.google.firebase.dataconnect.querymgr import com.google.firebase.dataconnect.* import com.google.firebase.dataconnect.core.Logger -import com.google.firebase.dataconnect.core.debug +import com.google.firebase.dataconnect.core.LoggerGlobals.Logger +import com.google.firebase.dataconnect.core.LoggerGlobals.debug +import com.google.firebase.dataconnect.util.AlphanumericStringUtil.toAlphaNumericString +import com.google.firebase.dataconnect.util.ProtoUtil.calculateSha512 +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToStruct +import com.google.firebase.dataconnect.util.ProtoUtil.toStructProto import com.google.firebase.dataconnect.util.ReferenceCounted -import com.google.firebase.dataconnect.util.calculateSha512 -import com.google.firebase.dataconnect.util.encodeToStruct -import com.google.firebase.dataconnect.util.toAlphaNumericString -import com.google.firebase.dataconnect.util.toStructProto import com.google.protobuf.Struct import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.NonCancellable diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/LiveQuery.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/LiveQuery.kt index 252e072d509..25d6344524d 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/LiveQuery.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/LiveQuery.kt @@ -20,11 +20,12 @@ import com.google.firebase.dataconnect.FirebaseDataConnect import com.google.firebase.dataconnect.core.DataConnectGrpcClient import com.google.firebase.dataconnect.core.DataConnectGrpcClient.OperationResult import com.google.firebase.dataconnect.core.Logger -import com.google.firebase.dataconnect.core.debug +import com.google.firebase.dataconnect.core.LoggerGlobals.Logger +import com.google.firebase.dataconnect.core.LoggerGlobals.debug import com.google.firebase.dataconnect.util.NullableReference import com.google.firebase.dataconnect.util.SequencedReference -import com.google.firebase.dataconnect.util.map -import com.google.firebase.dataconnect.util.nextSequenceNumber +import com.google.firebase.dataconnect.util.SequencedReference.Companion.map +import com.google.firebase.dataconnect.util.SequencedReference.Companion.nextSequenceNumber import com.google.firebase.util.nextAlphanumericString import com.google.protobuf.Struct import java.util.concurrent.CopyOnWriteArrayList diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/RegisteredDataDeserialzer.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/RegisteredDataDeserialzer.kt index 5f539d11f42..3f94a7f95a0 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/RegisteredDataDeserialzer.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/querymgr/RegisteredDataDeserialzer.kt @@ -17,14 +17,15 @@ package com.google.firebase.dataconnect.querymgr import com.google.firebase.dataconnect.core.DataConnectGrpcClient.OperationResult +import com.google.firebase.dataconnect.core.DataConnectGrpcClientGlobals.deserialize import com.google.firebase.dataconnect.core.Logger -import com.google.firebase.dataconnect.core.debug -import com.google.firebase.dataconnect.core.deserialize -import com.google.firebase.dataconnect.core.warn +import com.google.firebase.dataconnect.core.LoggerGlobals.Logger +import com.google.firebase.dataconnect.core.LoggerGlobals.debug +import com.google.firebase.dataconnect.core.LoggerGlobals.warn import com.google.firebase.dataconnect.util.NullableReference import com.google.firebase.dataconnect.util.SequencedReference +import com.google.firebase.dataconnect.util.SequencedReference.Companion.mapSuspending import com.google.firebase.dataconnect.util.SuspendingLazy -import com.google.firebase.dataconnect.util.mapSuspending import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/serializers/TimestampSerializer.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/serializers/TimestampSerializer.kt index d02cd85a005..729cba4c230 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/serializers/TimestampSerializer.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/serializers/TimestampSerializer.kt @@ -20,7 +20,9 @@ import com.google.firebase.Timestamp import java.text.DateFormat import java.text.ParsePosition import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale +import java.util.TimeZone import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor @@ -35,6 +37,23 @@ import kotlinx.serialization.encoding.Encoder * wire format expected by the Firebase Data Connect backend. */ public object TimestampSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Timestamp", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Timestamp) { + val rfc3339String = TimestampSerializerImpl.timestampToString(value) + encoder.encodeString(rfc3339String) + } + + override fun deserialize(decoder: Decoder): Timestamp { + val rfc3339String = decoder.decodeString() + return TimestampSerializerImpl.timestampFromString(rfc3339String) + } +} + +internal object TimestampSerializerImpl { + private val threadLocalDateFormatter = object : ThreadLocal() { override fun initialValue(): SimpleDateFormat { @@ -47,23 +66,9 @@ public object TimestampSerializer : KSerializer { private val dateFormatter: DateFormat get() = threadLocalDateFormatter.get()!! - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("Timestamp", PrimitiveKind.STRING) - - /** - * The expected serialized timestamp format is RFC3339: `yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'`, it - * can be constructed by two parts. First, we use `dateFormatter` to serialize seconds. Then, we - * pad nanoseconds into a 9 digits string. - */ - private fun timestampToString(timestamp: Timestamp): String { - val serializedSecond = dateFormatter.format(Date(timestamp.seconds * 1000)) - val serializedNano = timestamp.nanoseconds.toString().padStart(9, '0') - return "$serializedSecond.${serializedNano}Z" - } - // TODO: Replace this implementation with Instant.parse() once minSdkVersion is bumped to at // least 26 (Build.VERSION_CODES.O). - private fun timestampFromString(str: String): Timestamp { + fun timestampFromString(str: String): Timestamp { val strUppercase = str.uppercase() // If the timestamp string is 1985-04-12T23:20:50.123456789-07:00, the time-secfrac part @@ -111,13 +116,14 @@ public object TimestampSerializer : KSerializer { return Timestamp(seconds + if (addTimeDiffer) -timeZoneDiffer else timeZoneDiffer, nanoseconds) } - override fun serialize(encoder: Encoder, value: Timestamp) { - val rfc3339String = timestampToString(value) - encoder.encodeString(rfc3339String) - } - - override fun deserialize(decoder: Decoder): Timestamp { - val rfc3339String = decoder.decodeString() - return timestampFromString(rfc3339String) + /** + * The expected serialized timestamp format is RFC3339: `yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'`, it + * can be constructed by two parts. First, we use `dateFormatter` to serialize seconds. Then, we + * pad nanoseconds into a 9 digits string. + */ + fun timestampToString(timestamp: Timestamp): String { + val serializedSecond = dateFormatter.format(Date(timestamp.seconds * 1000)) + val serializedNano = timestamp.nanoseconds.toString().padStart(9, '0') + return "$serializedSecond.${serializedNano}Z" } } diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/serializers/UUIDSerializer.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/serializers/UUIDSerializer.kt index 5dbcceef892..41418ca975e 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/serializers/UUIDSerializer.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/serializers/UUIDSerializer.kt @@ -16,7 +16,6 @@ package com.google.firebase.dataconnect.serializers -import androidx.annotation.VisibleForTesting import java.util.UUID import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind @@ -34,22 +33,22 @@ public object UUIDSerializer : KSerializer { PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: UUID) { - val uuidString = serialize(value) + val uuidString = UUIDSerializerImpl.serialize(value) encoder.encodeString(uuidString) } - @VisibleForTesting + override fun deserialize(decoder: Decoder): UUID { + val decodedString = decoder.decodeString() + return UUIDSerializerImpl.deserialize(decodedString) + } +} + +internal object UUIDSerializerImpl { internal fun serialize(value: UUID): String { // Remove dashes from the UUID since the server will remove them anyways (see cl/629562890). return value.toString().replace("-", "") } - override fun deserialize(decoder: Decoder): UUID { - val decodedString = decoder.decodeString() - return deserialize(decodedString) - } - - @VisibleForTesting internal fun deserialize(decodedString: String): UUID { require(decodedString.length == 32) { "invalid UUID string: $decodedString (length=${decodedString.length}, expected=32)" diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/AlphanumericStringUtil.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/AlphanumericStringUtil.kt new file mode 100644 index 00000000000..81e46fb10b5 --- /dev/null +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/AlphanumericStringUtil.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.firebase.dataconnect.util + +/** + * Holder for "global" functions related to [ProtoStructValueDecoder]. + * + * Technically, these functions _could_ be defined as free functions; however, doing so creates a + * AlphanumericStringUtilKt Java class with public visibility, which pollutes the public API. Using + * an "internal" object, instead, to gather together the top-level functions avoids this public API + * pollution. + */ +internal object AlphanumericStringUtil { + + // NOTE: `ALPHANUMERIC_ALPHABET` MUST have a length of 32 (since 2^5=32). This allows encoding 5 + // bits as a single digit from this alphabet. Note that some numbers and letters were removed, + // especially those that can look similar in different fonts, like '1', 'l', and 'i'. + private const val ALPHANUMERIC_ALPHABET = "23456789abcdefghjkmnopqrstuvwxyz" + + /** + * Converts this byte array to a base-36 string, which uses the 26 letters from the English + * alphabet and the 10 numeric digits. + */ + fun ByteArray.toAlphaNumericString(): String = buildString { + val numBits = size * 8 + for (bitIndex in 0 until numBits step 5) { + val byteIndex = bitIndex.div(8) + val bitOffset = bitIndex.rem(8) + val b = this@toAlphaNumericString[byteIndex].toUByte().toInt() + + val intValue = + if (bitOffset <= 3) { + b shr (3 - bitOffset) + } else { + val upperBits = + when (bitOffset) { + 4 -> b and 0x0f + 5 -> b and 0x07 + 6 -> b and 0x03 + 7 -> b and 0x01 + else -> error("internal error: invalid bitOffset: $bitOffset") + } + if (byteIndex + 1 == size) { + upperBits + } else { + val b2 = this@toAlphaNumericString[byteIndex + 1].toUByte().toInt() + when (bitOffset) { + 4 -> ((b2 shr 7) and 0x01) or (upperBits shl 1) + 5 -> ((b2 shr 6) and 0x03) or (upperBits shl 2) + 6 -> ((b2 shr 5) and 0x07) or (upperBits shl 3) + 7 -> ((b2 shr 4) and 0x0f) or (upperBits shl 4) + else -> error("internal error: invalid bitOffset: $bitOffset") + } + } + } + + append(ALPHANUMERIC_ALPHABET[intValue and 0x1f]) + } + } +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/NullOutputStream.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/NullOutputStream.kt new file mode 100644 index 00000000000..14e09a43c35 --- /dev/null +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/NullOutputStream.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.firebase.dataconnect.util + +import java.io.OutputStream + +internal object NullOutputStream : OutputStream() { + override fun write(b: Int) {} + override fun write(b: ByteArray?) {} + override fun write(b: ByteArray?, off: Int, len: Int) {} +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/NullableReference.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/NullableReference.kt new file mode 100644 index 00000000000..9a67442e263 --- /dev/null +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/NullableReference.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.firebase.dataconnect.util + +internal class NullableReference(val ref: T? = null) { + override fun equals(other: Any?) = (other is NullableReference<*>) && other.ref == ref + override fun hashCode() = ref?.hashCode() ?: 0 + override fun toString() = ref?.toString() ?: "null" +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructDecoder.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructDecoder.kt index bd5a18553a2..1dbb0ea0ec5 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructDecoder.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructDecoder.kt @@ -20,6 +20,20 @@ package com.google.firebase.dataconnect.util import com.google.firebase.dataconnect.AnyValue import com.google.firebase.dataconnect.serializers.AnyValueSerializer +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeBoolean +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeByte +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeChar +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeDouble +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeEnum +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeFloat +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeInt +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeList +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeLong +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeNull +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeShort +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeString +import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeStruct +import com.google.firebase.dataconnect.util.ProtoUtil.toAny import com.google.protobuf.ListValue import com.google.protobuf.NullValue import com.google.protobuf.Struct @@ -33,82 +47,66 @@ import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.descriptors.elementNames import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.serializer - -internal inline fun decodeFromStruct(struct: Struct): T = - decodeFromStruct(struct, serializer(), serializersModule = null) - -internal fun decodeFromStruct( - struct: Struct, - deserializer: DeserializationStrategy, - serializersModule: SerializersModule? -): T { - val protoValue = Value.newBuilder().setStructValue(struct).build() - return decodeFromValue(protoValue, deserializer, serializersModule) -} - -internal inline fun decodeFromValue(value: Value): T = - decodeFromValue(value, serializer(), serializersModule = null) - -internal fun decodeFromValue( - value: Value, - deserializer: DeserializationStrategy, - serializersModule: SerializersModule? -): T { - val decoder = ProtoValueDecoder(value, path = null, serializersModule ?: EmptySerializersModule()) - return decoder.decodeSerializableValue(deserializer) -} -private fun Value.decode(path: String?, expectedKindCase: KindCase, block: (Value) -> T): T = - if (kindCase != expectedKindCase) { - throw SerializationException( - (if (path === null) "" else "decoding \"$path\" failed: ") + - "expected $expectedKindCase, but got $kindCase (${toAny()})" - ) - } else { - block(this) - } +/** + * Holder for "global" functions related to [ProtoStructValueDecoder]. + * + * Technically, these functions _could_ be defined as free functions; however, doing so creates a + * ProtoStructDecoderKt, ProtoUtilKt, etc. Java class with public visibility, which pollutes the + * public API. Using an "internal" object, instead, to gather together the top-level functions + * avoids this public API pollution. + */ +private object ProtoDecoderUtil { + fun decode(value: Value, path: String?, expectedKindCase: KindCase, block: (Value) -> T): T = + if (value.kindCase != expectedKindCase) { + throw SerializationException( + (if (path === null) "" else "decoding \"$path\" failed: ") + + "expected $expectedKindCase, but got ${value.kindCase} (${value.toAny()})" + ) + } else { + block(value) + } -private fun Value.decodeBoolean(path: String?): Boolean = - decode(path, KindCase.BOOL_VALUE) { it.boolValue } + fun decodeBoolean(value: Value, path: String?): Boolean = + decode(value, path, KindCase.BOOL_VALUE) { it.boolValue } -private fun Value.decodeByte(path: String?): Byte = - decode(path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toByte() } + fun decodeByte(value: Value, path: String?): Byte = + decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toByte() } -private fun Value.decodeChar(path: String?): Char = - decode(path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toChar() } + fun decodeChar(value: Value, path: String?): Char = + decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toChar() } -private fun Value.decodeDouble(path: String?): Double = - decode(path, KindCase.NUMBER_VALUE) { it.numberValue } + fun decodeDouble(value: Value, path: String?): Double = + decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue } -private fun Value.decodeEnum(path: String?): Int = - decode(path, KindCase.NUMBER_VALUE) { it.numberValue.toInt() } + fun decodeEnum(value: Value, path: String?): Int = + decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt() } -private fun Value.decodeFloat(path: String?): Float = - decode(path, KindCase.NUMBER_VALUE) { it.numberValue.toFloat() } + fun decodeFloat(value: Value, path: String?): Float = + decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toFloat() } -private fun Value.decodeString(path: String?): String = - decode(path, KindCase.STRING_VALUE) { it.stringValue } + fun decodeString(value: Value, path: String?): String = + decode(value, path, KindCase.STRING_VALUE) { it.stringValue } -private fun Value.decodeStruct(path: String?): Struct = - decode(path, KindCase.STRUCT_VALUE) { it.structValue } + fun decodeStruct(value: Value, path: String?): Struct = + decode(value, path, KindCase.STRUCT_VALUE) { it.structValue } -private fun Value.decodeList(path: String?): ListValue = - decode(path, KindCase.LIST_VALUE) { it.listValue } + fun decodeList(value: Value, path: String?): ListValue = + decode(value, path, KindCase.LIST_VALUE) { it.listValue } -private fun Value.decodeNull(path: String?): NullValue = - decode(path, KindCase.NULL_VALUE) { it.nullValue } + fun decodeNull(value: Value, path: String?): NullValue = + decode(value, path, KindCase.NULL_VALUE) { it.nullValue } -private fun Value.decodeInt(path: String?): Int = - decode(path, KindCase.NUMBER_VALUE) { it.numberValue.toInt() } + fun decodeInt(value: Value, path: String?): Int = + decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt() } -private fun Value.decodeLong(path: String?): Long = - decode(path, KindCase.STRING_VALUE) { it.stringValue.toLong() } + fun decodeLong(value: Value, path: String?): Long = + decode(value, path, KindCase.STRING_VALUE) { it.stringValue.toLong() } -private fun Value.decodeShort(path: String?): Short = - decode(path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toShort() } + fun decodeShort(value: Value, path: String?): Short = + decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toShort() } +} internal class ProtoValueDecoder( internal val valueProto: Value, @@ -119,42 +117,42 @@ internal class ProtoValueDecoder( override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = when (val kind = descriptor.kind) { is StructureKind.CLASS -> - ProtoStructValueDecoder(valueProto.decodeStruct(path), path, serializersModule) + ProtoStructValueDecoder(decodeStruct(valueProto, path), path, serializersModule) is StructureKind.LIST -> - ProtoListValueDecoder(valueProto.decodeList(path), path, serializersModule) + ProtoListValueDecoder(decodeList(valueProto, path), path, serializersModule) is StructureKind.MAP -> - ProtoMapValueDecoder(valueProto.decodeStruct(path), path, serializersModule) + ProtoMapValueDecoder(decodeStruct(valueProto, path), path, serializersModule) is StructureKind.OBJECT -> ProtoObjectValueDecoder(path, serializersModule) else -> throw IllegalArgumentException("unsupported SerialKind: ${kind::class.qualifiedName}") } - override fun decodeBoolean() = valueProto.decodeBoolean(path) + override fun decodeBoolean() = decodeBoolean(valueProto, path) - override fun decodeByte() = valueProto.decodeByte(path) + override fun decodeByte() = decodeByte(valueProto, path) - override fun decodeChar() = valueProto.decodeChar(path) + override fun decodeChar() = decodeChar(valueProto, path) - override fun decodeDouble() = valueProto.decodeDouble(path) + override fun decodeDouble() = decodeDouble(valueProto, path) - override fun decodeEnum(enumDescriptor: SerialDescriptor) = valueProto.decodeEnum(path) + override fun decodeEnum(enumDescriptor: SerialDescriptor) = decodeEnum(valueProto, path) - override fun decodeFloat() = valueProto.decodeFloat(path) + override fun decodeFloat() = decodeFloat(valueProto, path) override fun decodeInline(descriptor: SerialDescriptor) = ProtoValueDecoder(valueProto, path, serializersModule) - override fun decodeInt(): Int = valueProto.decodeInt(path) + override fun decodeInt(): Int = decodeInt(valueProto, path) - override fun decodeLong() = valueProto.decodeLong(path) + override fun decodeLong() = decodeLong(valueProto, path) - override fun decodeShort() = valueProto.decodeShort(path) + override fun decodeShort() = decodeShort(valueProto, path) - override fun decodeString() = valueProto.decodeString(path) + override fun decodeString() = decodeString(valueProto, path) override fun decodeNotNullMark() = !valueProto.hasNullValue() override fun decodeNull(): Nothing? { - valueProto.decodeNull(path) + decodeNull(valueProto, path) return null } } @@ -188,39 +186,41 @@ private class ProtoStructValueDecoder( } override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeBoolean) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeBoolean) override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeByte) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeByte) override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeChar) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeChar) override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeDouble) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeDouble) override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeFloat) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeFloat) override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index) { ProtoValueDecoder(this, it, serializersModule) } + decodeValueElement(descriptor, index) { valueProto, elementPath -> + ProtoValueDecoder(valueProto, elementPath, serializersModule) + } override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeInt) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeInt) override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeLong) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeLong) override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeShort) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeShort) override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(descriptor, index, Value::decodeString) + decodeValueElement(descriptor, index, ProtoDecoderUtil::decodeString) private fun decodeValueElement( descriptor: SerialDescriptor, index: Int, - block: Value.(String?) -> T + block: (Value, String?) -> T ): T { val elementName = descriptor.getElementName(index) val elementPath = elementPathForName(elementName) @@ -300,36 +300,38 @@ private class ProtoListValueDecoder( if (elementIndexes.hasNext()) elementIndexes.next() else CompositeDecoder.DECODE_DONE override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeBoolean) + decodeValueElement(index, ProtoDecoderUtil::decodeBoolean) override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeByte) + decodeValueElement(index, ProtoDecoderUtil::decodeByte) override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeChar) + decodeValueElement(index, ProtoDecoderUtil::decodeChar) override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeDouble) + decodeValueElement(index, ProtoDecoderUtil::decodeDouble) override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeFloat) + decodeValueElement(index, ProtoDecoderUtil::decodeFloat) override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index) { ProtoValueDecoder(this, it, serializersModule) } + decodeValueElement(index) { protoValue, elementPath -> + ProtoValueDecoder(protoValue, elementPath, serializersModule) + } override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeInt) + decodeValueElement(index, ProtoDecoderUtil::decodeInt) override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeLong) + decodeValueElement(index, ProtoDecoderUtil::decodeLong) override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeShort) + decodeValueElement(index, ProtoDecoderUtil::decodeShort) override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeString) + decodeValueElement(index, ProtoDecoderUtil::decodeString) - private inline fun decodeValueElement(index: Int, block: Value.(String?) -> T): T = + private inline fun decodeValueElement(index: Int, block: (Value, String?) -> T): T = block(list.valuesList[index], elementPathForIndex(index)) override fun decodeSerializableElement( @@ -390,40 +392,42 @@ private class ProtoMapValueDecoder( if (elementIndexes.hasNext()) elementIndexes.next() else CompositeDecoder.DECODE_DONE override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeBoolean) + decodeValueElement(index, ProtoDecoderUtil::decodeBoolean) override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeByte) + decodeValueElement(index, ProtoDecoderUtil::decodeByte) override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeChar) + decodeValueElement(index, ProtoDecoderUtil::decodeChar) override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeDouble) + decodeValueElement(index, ProtoDecoderUtil::decodeDouble) override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeFloat) + decodeValueElement(index, ProtoDecoderUtil::decodeFloat) override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index) { ProtoValueDecoder(this, it, serializersModule) } + decodeValueElement(index) { valueProto, elementPath -> + ProtoValueDecoder(valueProto, elementPath, serializersModule) + } override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeInt) + decodeValueElement(index, ProtoDecoderUtil::decodeInt) override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeLong) + decodeValueElement(index, ProtoDecoderUtil::decodeLong) override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = - decodeValueElement(index, Value::decodeShort) + decodeValueElement(index, ProtoDecoderUtil::decodeShort) override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = if (index % 2 == 0) { structEntryByElementIndex(index).key } else { - decodeValueElement(index, Value::decodeString) + decodeValueElement(index, ProtoDecoderUtil::decodeString) } - private inline fun decodeValueElement(index: Int, block: Value.(String?) -> T): T { + private inline fun decodeValueElement(index: Int, block: (Value, String?) -> T): T { require(index % 2 != 0) { "invalid value index: $index" } val value = structEntryByElementIndex(index).value val elementPath = elementPathForIndex(index) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructEncoder.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructEncoder.kt index da72d4913c4..431f50159c0 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructEncoder.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructEncoder.kt @@ -20,10 +20,11 @@ package com.google.firebase.dataconnect.util import com.google.firebase.dataconnect.AnyValue import com.google.firebase.dataconnect.serializers.AnyValueSerializer +import com.google.firebase.dataconnect.util.ProtoUtil.nullProtoValue +import com.google.firebase.dataconnect.util.ProtoUtil.toValueProto import com.google.protobuf.ListValue import com.google.protobuf.Struct import com.google.protobuf.Value -import com.google.protobuf.Value.KindCase import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationStrategy @@ -35,45 +36,6 @@ import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer -internal inline fun encodeToStruct(value: T): Struct = - encodeToStruct(value, serializer(), serializersModule = null) - -internal fun encodeToStruct( - value: T, - serializer: SerializationStrategy, - serializersModule: SerializersModule? -): Struct { - val valueProto = encodeToValue(value, serializer, serializersModule) - if (valueProto.kindCase == KindCase.KIND_NOT_SET) { - return Struct.getDefaultInstance() - } - require(valueProto.hasStructValue()) { - "encoding produced ${valueProto.kindCase}, " + - "but expected ${KindCase.STRUCT_VALUE} or ${KindCase.KIND_NOT_SET}" - } - return valueProto.structValue -} - -internal inline fun encodeToValue(value: T): Value = - encodeToValue(value, serializer(), serializersModule = null) - -internal fun encodeToValue( - value: T, - serializer: SerializationStrategy, - serializersModule: SerializersModule? -): Value { - val values = mutableListOf() - ProtoValueEncoder(null, serializersModule ?: EmptySerializersModule(), values::add) - .encodeSerializableValue(serializer, value) - if (values.isEmpty()) { - return Value.getDefaultInstance() - } - require(values.size == 1) { - "encoding produced ${values.size} Value objects, but expected either 0 or 1" - } - return values.single() -} - internal class ProtoValueEncoder( private val path: String?, override val serializersModule: SerializersModule, diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoUtil.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoUtil.kt index 23b4d734a91..4cfe1ffd288 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoUtil.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoUtil.kt @@ -16,7 +16,9 @@ package com.google.firebase.dataconnect.util -import com.google.firebase.dataconnect.core.toDataConnectError +import com.google.firebase.dataconnect.core.DataConnectGrpcClientGlobals.toDataConnectError +import com.google.firebase.dataconnect.util.ProtoUtil.nullProtoValue +import com.google.firebase.dataconnect.util.ProtoUtil.toValueProto import com.google.protobuf.ListValue import com.google.protobuf.NullValue import com.google.protobuf.Struct @@ -37,77 +39,376 @@ import java.io.CharArrayWriter import java.io.DataOutputStream import java.security.DigestOutputStream import java.security.MessageDigest +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.serializer + +/** + * Holder for "global" functions related to protocol buffers. + * + * Technically, these functions _could_ be defined as free functions; however, doing so creates a + * ProtoStructEncoderKt, ProtoUtilKt, etc. Java class with public visibility, which pollutes the + * public API. Using an "internal" object, instead, to gather together the top-level functions + * avoids this public API pollution. + */ +internal object ProtoUtil { -/** Calculates a SHA-512 digest of a [Struct]. */ -internal fun Struct.calculateSha512(): ByteArray = - Value.newBuilder().setStructValue(this).build().calculateSha512() + /** Calculates a SHA-512 digest of a [Struct]. */ + fun Struct.calculateSha512(): ByteArray = + Value.newBuilder().setStructValue(this).build().calculateSha512() -/** Calculates a SHA-512 digest of a [Value]. */ -internal fun Value.calculateSha512(): ByteArray { - val digest = MessageDigest.getInstance("SHA-512") - val out = DataOutputStream(DigestOutputStream(NullOutputStream, digest)) + /** Calculates a SHA-512 digest of a [Value]. */ + fun Value.calculateSha512(): ByteArray { + val digest = MessageDigest.getInstance("SHA-512") + val out = DataOutputStream(DigestOutputStream(NullOutputStream, digest)) - val calculateDigest = - DeepRecursiveFunction { - val kind = it.kindCase - out.writeInt(kind.ordinal) + val calculateDigest = + DeepRecursiveFunction { + val kind = it.kindCase + out.writeInt(kind.ordinal) - when (kind) { - KindCase.NULL_VALUE -> { - /* nothing to write for null */ - } - KindCase.BOOL_VALUE -> out.writeBoolean(it.boolValue) - KindCase.NUMBER_VALUE -> out.writeDouble(it.numberValue) - KindCase.STRING_VALUE -> out.writeUTF(it.stringValue) - KindCase.LIST_VALUE -> - it.listValue.valuesList.forEachIndexed { index, elementValue -> - out.writeInt(index) - callRecursive(elementValue) + when (kind) { + KindCase.NULL_VALUE -> { + /* nothing to write for null */ } - KindCase.STRUCT_VALUE -> - it.structValue.fieldsMap.entries - .sortedBy { (key, _) -> key } - .forEach { (key, elementValue) -> - out.writeUTF(key) + KindCase.BOOL_VALUE -> out.writeBoolean(it.boolValue) + KindCase.NUMBER_VALUE -> out.writeDouble(it.numberValue) + KindCase.STRING_VALUE -> out.writeUTF(it.stringValue) + KindCase.LIST_VALUE -> + it.listValue.valuesList.forEachIndexed { index, elementValue -> + out.writeInt(index) callRecursive(elementValue) } - else -> throw IllegalArgumentException("unsupported kind: $kind") + KindCase.STRUCT_VALUE -> + it.structValue.fieldsMap.entries + .sortedBy { (key, _) -> key } + .forEach { (key, elementValue) -> + out.writeUTF(key) + callRecursive(elementValue) + } + else -> throw IllegalArgumentException("unsupported kind: $kind") + } + + out.writeInt(kind.ordinal) } - out.writeInt(kind.ordinal) + calculateDigest(this) + + return digest.digest() + } + + fun Boolean.toValueProto(): Value = Value.newBuilder().setBoolValue(this).build() + + fun Byte.toValueProto(): Value = toInt().toValueProto() + + fun Char.toValueProto(): Value = code.toValueProto() + + fun Double.toValueProto(): Value = Value.newBuilder().setNumberValue(this).build() + + fun Float.toValueProto(): Value = toDouble().toValueProto() + + fun Int.toValueProto(): Value = toDouble().toValueProto() + + fun Long.toValueProto(): Value = toString().toValueProto() + + fun Short.toValueProto(): Value = toInt().toValueProto() + + fun String.toValueProto(): Value = Value.newBuilder().setStringValue(this).build() + + fun ListValue.toValueProto(): Value = Value.newBuilder().setListValue(this).build() + + fun Struct.toValueProto(): Value = Value.newBuilder().setStructValue(this).build() + + val nullProtoValue: Value + get() { + return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build() } - calculateDigest(this) + /** A more convenient builder for [Struct] than [com.google.protobuf.struct]. */ + fun buildStructProto( + initialValues: Struct? = null, + block: StructProtoBuilder.() -> Unit + ): Struct = StructProtoBuilder(initialValues).apply(block).build() - return digest.digest() -} + /** Generates and returns a string similar to [Struct.toString] but more compact. */ + fun Struct.toCompactString(keySortSelector: ((String) -> String)? = null): String = + Value.newBuilder().setStructValue(this).build().toCompactString(keySortSelector) + + /** Generates and returns a string similar to [Value.toString] but more compact. */ + fun Value.toCompactString(keySortSelector: ((String) -> String)? = null): String { + val charArrayWriter = CharArrayWriter() + val out = BufferedWriter(charArrayWriter) + var indent = 0 + + fun BufferedWriter.writeIndent() { + repeat(indent * 2) { write(" ") } + } + + val calculateCompactString = + DeepRecursiveFunction { + when (val kind = it.kindCase) { + KindCase.NULL_VALUE -> out.write("null") + KindCase.BOOL_VALUE -> out.write(if (it.boolValue) "true" else "false") + KindCase.NUMBER_VALUE -> out.write(it.numberValue.toString()) + KindCase.STRING_VALUE -> out.write("\"${it.stringValue}\"") + KindCase.LIST_VALUE -> { + out.write("[") + indent++ + it.listValue.valuesList.forEach { listElementValue -> + out.newLine() + out.writeIndent() + callRecursive(listElementValue) + } + indent-- + out.newLine() + out.writeIndent() + out.write("]") + } + KindCase.STRUCT_VALUE -> { + out.write("{") + indent++ + it.structValue.fieldsMap.entries + .sortedBy { (key, _) -> keySortSelector?.invoke(key) ?: key } + .forEach { (structElementKey, structElementValue) -> + out.newLine() + out.writeIndent() + out.write("$structElementKey: ") + callRecursive(structElementValue) + } + indent-- + out.newLine() + out.writeIndent() + out.write("}") + } + else -> throw IllegalArgumentException("unsupported kind: $kind") + } + } + + calculateCompactString(this) + + out.close() + return charArrayWriter.toString() + } + + fun ExecuteQueryRequest.toCompactString(): String = toStructProto().toCompactString() + + fun ExecuteQueryRequest.toStructProto(): Struct = buildStructProto { + put("name", name) + put("operationName", operationName) + if (hasVariables()) put("variables", variables) + } + + fun ExecuteQueryResponse.toCompactString(): String = toStructProto().toCompactString() + + fun ExecuteQueryResponse.toStructProto(): Struct = buildStructProto { + if (hasData()) put("data", data) + putList("errors") { errorsList.forEach { add(it.toDataConnectError().toString()) } } + } + + fun ExecuteMutationRequest.toCompactString(): String = toStructProto().toCompactString() + + fun ExecuteMutationRequest.toStructProto(): Struct = buildStructProto { + put("name", name) + put("operationName", operationName) + if (hasVariables()) put("variables", variables) + } -internal fun Boolean.toValueProto(): Value = Value.newBuilder().setBoolValue(this).build() + fun ExecuteMutationResponse.toCompactString(): String = toStructProto().toCompactString() -internal fun Byte.toValueProto(): Value = toInt().toValueProto() + fun ExecuteMutationResponse.toStructProto(): Struct = buildStructProto { + if (hasData()) put("data", data) + putList("errors") { errorsList.forEach { add(it.toDataConnectError().toString()) } } + } + + fun EmulatorInfo.toStructProto(): Struct = buildStructProto { + put("version", version) + putList("services") { servicesList.forEach { add(it.toStructProto()) } } + } + + fun ServiceInfo.toStructProto(): Struct = buildStructProto { + put("service_id", serviceId) + put("connection_string", connectionString) + } + + fun EmulatorIssuesResponse.toStructProto(): Struct = buildStructProto { + putList("issues") { issuesList.forEach { add(it.toStructProto()) } } + } + + fun EmulatorIssue.toStructProto(): Struct = buildStructProto { + put("kind", kind.name) + put("severity", severity.name) + put("message", message) + } -internal fun Char.toValueProto(): Value = code.toValueProto() + fun ListValue.toListOfAny(): List = valueToAnyMutualRecursion.anyFromListValue(this) -internal fun Double.toValueProto(): Value = Value.newBuilder().setNumberValue(this).build() + fun Struct.toMap(): Map = valueToAnyMutualRecursion.anyValueFromStruct(this) -internal fun Float.toValueProto(): Value = toDouble().toValueProto() + fun Value.toAny(): Any? = valueToAnyMutualRecursion.anyValueFromValue(this) -internal fun Int.toValueProto(): Value = toDouble().toValueProto() + fun List.toValueProto(): Value { + val key = "y8czq9rh75" + return mapOf(key to this).toStructProto().getFieldsOrThrow(key) + } + + fun Map.toValueProto(): Value = + Value.newBuilder().setStructValue(toStructProto()).build() + + fun Map.toStructProto(): Struct = mapToStructProtoMutualRecursion.structForMap(this) + + private val mapToStructProtoMutualRecursion = + object { + val listValueForList: DeepRecursiveFunction, ListValue> = DeepRecursiveFunction { + val listValueProtoBuilder = ListValue.newBuilder() + it.forEach { value -> + listValueProtoBuilder.addValues( + when (value) { + null -> nullProtoValue + is Boolean -> value.toValueProto() + is Double -> value.toValueProto() + is String -> value.toValueProto() + is List<*> -> callRecursive(value).toValueProto() + is Map<*, *> -> structForMap.callRecursive(value).toValueProto() + else -> + throw IllegalArgumentException( + "unsupported type: ${value::class.qualifiedName}; " + + "supported types are: Boolean, Double, String, List, and Map" + ) + } + ) + } + listValueProtoBuilder.build() + } + + val structForMap: DeepRecursiveFunction, Struct> = DeepRecursiveFunction { + val structProtoBuilder = Struct.newBuilder() + it.entries.forEach { (untypedKey, value) -> + val key = + (untypedKey as? String) + ?: throw IllegalArgumentException( + "map keys must be string, but got: " + + if (untypedKey === null) "null" else untypedKey::class.qualifiedName + ) + structProtoBuilder.putFields( + key, + when (value) { + null -> nullProtoValue + is Double -> value.toValueProto() + is Boolean -> value.toValueProto() + is String -> value.toValueProto() + is List<*> -> listValueForList.callRecursive(value).toValueProto() + is Map<*, *> -> callRecursive(value).toValueProto() + else -> + throw IllegalArgumentException( + "unsupported type: ${value::class.qualifiedName}; " + + "supported types are: Boolean, Double, String, List, and Map" + ) + } + ) + } + structProtoBuilder.build() + } + } + + private val valueToAnyMutualRecursion = + object { + val anyFromListValue: DeepRecursiveFunction> = + DeepRecursiveFunction { listValue -> + buildList { + for (element in listValue.valuesList) { + add(anyValueFromValue.callRecursive(element)) + } + } + } + + val anyValueFromStruct: DeepRecursiveFunction> = + DeepRecursiveFunction { struct -> + buildMap { + for (entry in struct.fieldsMap) { + put(entry.key, anyValueFromValue.callRecursive(entry.value)) + } + } + } -internal fun Long.toValueProto(): Value = toString().toValueProto() + val anyValueFromValue: DeepRecursiveFunction = DeepRecursiveFunction { value -> + when (value.kindCase) { + KindCase.BOOL_VALUE -> value.boolValue + KindCase.NUMBER_VALUE -> value.numberValue + KindCase.STRING_VALUE -> value.stringValue + KindCase.LIST_VALUE -> anyFromListValue.callRecursive(value.listValue) + KindCase.STRUCT_VALUE -> anyValueFromStruct.callRecursive(value.structValue) + KindCase.NULL_VALUE -> null + else -> "ERROR: unsupported kindCase: ${value.kindCase}" + } + } + } -internal fun Short.toValueProto(): Value = toInt().toValueProto() + inline fun encodeToStruct(value: T): Struct = + encodeToStruct(value, serializer(), serializersModule = null) + + fun encodeToStruct( + value: T, + serializer: SerializationStrategy, + serializersModule: SerializersModule? + ): Struct { + val valueProto = encodeToValue(value, serializer, serializersModule) + if (valueProto.kindCase == KindCase.KIND_NOT_SET) { + return Struct.getDefaultInstance() + } + require(valueProto.hasStructValue()) { + "encoding produced ${valueProto.kindCase}, " + + "but expected ${KindCase.STRUCT_VALUE} or ${KindCase.KIND_NOT_SET}" + } + return valueProto.structValue + } -internal fun String.toValueProto(): Value = Value.newBuilder().setStringValue(this).build() + inline fun encodeToValue(value: T): Value = + encodeToValue(value, serializer(), serializersModule = null) + + fun encodeToValue( + value: T, + serializer: SerializationStrategy, + serializersModule: SerializersModule? + ): Value { + val values = mutableListOf() + ProtoValueEncoder(null, serializersModule ?: EmptySerializersModule(), values::add) + .encodeSerializableValue(serializer, value) + if (values.isEmpty()) { + return Value.getDefaultInstance() + } + require(values.size == 1) { + "encoding produced ${values.size} Value objects, but expected either 0 or 1" + } + return values.single() + } -internal fun ListValue.toValueProto(): Value = Value.newBuilder().setListValue(this).build() + inline fun decodeFromStruct(struct: Struct): T = + decodeFromStruct(struct, serializer(), serializersModule = null) -internal fun Struct.toValueProto(): Value = Value.newBuilder().setStructValue(this).build() + fun decodeFromStruct( + struct: Struct, + deserializer: DeserializationStrategy, + serializersModule: SerializersModule? + ): T { + val protoValue = Value.newBuilder().setStructValue(struct).build() + return decodeFromValue(protoValue, deserializer, serializersModule) + } -internal val nullProtoValue: Value - get() { - return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build() + inline fun decodeFromValue(value: Value): T = + decodeFromValue(value, serializer(), serializersModule = null) + + fun decodeFromValue( + value: Value, + deserializer: DeserializationStrategy, + serializersModule: SerializersModule? + ): T { + val decoder = + ProtoValueDecoder(value, path = null, serializersModule ?: EmptySerializersModule()) + return decoder.decodeSerializableValue(deserializer) } +} @DslMarker internal annotation class StructProtoBuilderDslMarker @@ -214,223 +515,3 @@ internal class ListValueProtoBuilder(listValue: ListValue? = null) { builder.addValues(nullProtoValue) } } - -/** A more convenient builder for [Struct] than [com.google.protobuf.struct]. */ -internal fun buildStructProto( - initialValues: Struct? = null, - block: StructProtoBuilder.() -> Unit -): Struct = StructProtoBuilder(initialValues).apply(block).build() - -/** Generates and returns a string similar to [Struct.toString] but more compact. */ -internal fun Struct.toCompactString(keySortSelector: ((String) -> String)? = null): String = - Value.newBuilder().setStructValue(this).build().toCompactString(keySortSelector) - -/** Generates and returns a string similar to [Value.toString] but more compact. */ -internal fun Value.toCompactString(keySortSelector: ((String) -> String)? = null): String { - val charArrayWriter = CharArrayWriter() - val out = BufferedWriter(charArrayWriter) - var indent = 0 - - fun BufferedWriter.writeIndent() { - repeat(indent * 2) { write(" ") } - } - - val calculateCompactString = - DeepRecursiveFunction { - when (val kind = it.kindCase) { - KindCase.NULL_VALUE -> out.write("null") - KindCase.BOOL_VALUE -> out.write(if (it.boolValue) "true" else "false") - KindCase.NUMBER_VALUE -> out.write(it.numberValue.toString()) - KindCase.STRING_VALUE -> out.write("\"${it.stringValue}\"") - KindCase.LIST_VALUE -> { - out.write("[") - indent++ - it.listValue.valuesList.forEach { listElementValue -> - out.newLine() - out.writeIndent() - callRecursive(listElementValue) - } - indent-- - out.newLine() - out.writeIndent() - out.write("]") - } - KindCase.STRUCT_VALUE -> { - out.write("{") - indent++ - it.structValue.fieldsMap.entries - .sortedBy { (key, _) -> keySortSelector?.invoke(key) ?: key } - .forEach { (structElementKey, structElementValue) -> - out.newLine() - out.writeIndent() - out.write("$structElementKey: ") - callRecursive(structElementValue) - } - indent-- - out.newLine() - out.writeIndent() - out.write("}") - } - else -> throw IllegalArgumentException("unsupported kind: $kind") - } - } - - calculateCompactString(this) - - out.close() - return charArrayWriter.toString() -} - -internal fun ExecuteQueryRequest.toCompactString(): String = toStructProto().toCompactString() - -internal fun ExecuteQueryRequest.toStructProto(): Struct = buildStructProto { - put("name", name) - put("operationName", operationName) - if (hasVariables()) put("variables", variables) -} - -internal fun ExecuteQueryResponse.toCompactString(): String = toStructProto().toCompactString() - -internal fun ExecuteQueryResponse.toStructProto(): Struct = buildStructProto { - if (hasData()) put("data", data) - putList("errors") { errorsList.forEach { add(it.toDataConnectError().toString()) } } -} - -internal fun ExecuteMutationRequest.toCompactString(): String = toStructProto().toCompactString() - -internal fun ExecuteMutationRequest.toStructProto(): Struct = buildStructProto { - put("name", name) - put("operationName", operationName) - if (hasVariables()) put("variables", variables) -} - -internal fun ExecuteMutationResponse.toCompactString(): String = toStructProto().toCompactString() - -internal fun ExecuteMutationResponse.toStructProto(): Struct = buildStructProto { - if (hasData()) put("data", data) - putList("errors") { errorsList.forEach { add(it.toDataConnectError().toString()) } } -} - -internal fun EmulatorInfo.toStructProto(): Struct = buildStructProto { - put("version", version) - putList("services") { servicesList.forEach { add(it.toStructProto()) } } -} - -internal fun ServiceInfo.toStructProto(): Struct = buildStructProto { - put("service_id", serviceId) - put("connection_string", connectionString) -} - -internal fun EmulatorIssuesResponse.toStructProto(): Struct = buildStructProto { - putList("issues") { issuesList.forEach { add(it.toStructProto()) } } -} - -internal fun EmulatorIssue.toStructProto(): Struct = buildStructProto { - put("kind", kind.name) - put("severity", severity.name) - put("message", message) -} - -internal fun ListValue.toListOfAny(): List = valueToAnyMutualRecursion.anyFromListValue(this) - -internal fun Struct.toMap(): Map = valueToAnyMutualRecursion.anyValueFromStruct(this) - -internal fun Value.toAny(): Any? = valueToAnyMutualRecursion.anyValueFromValue(this) - -internal fun List.toValueProto(): Value { - val key = "y8czq9rh75" - return mapOf(key to this).toStructProto().getFieldsOrThrow(key) -} - -internal fun Map.toValueProto(): Value = - Value.newBuilder().setStructValue(toStructProto()).build() - -internal fun Map.toStructProto(): Struct = - mapToStructProtoMutualRecursion.structForMap(this) - -private val mapToStructProtoMutualRecursion = - object { - val listValueForList: DeepRecursiveFunction, ListValue> = DeepRecursiveFunction { - val listValueProtoBuilder = ListValue.newBuilder() - it.forEach { value -> - listValueProtoBuilder.addValues( - when (value) { - null -> nullProtoValue - is Boolean -> value.toValueProto() - is Double -> value.toValueProto() - is String -> value.toValueProto() - is List<*> -> callRecursive(value).toValueProto() - is Map<*, *> -> structForMap.callRecursive(value).toValueProto() - else -> - throw IllegalArgumentException( - "unsupported type: ${value::class.qualifiedName}; " + - "supported types are: Boolean, Double, String, List, and Map" - ) - } - ) - } - listValueProtoBuilder.build() - } - - val structForMap: DeepRecursiveFunction, Struct> = DeepRecursiveFunction { - val structProtoBuilder = Struct.newBuilder() - it.entries.forEach { (untypedKey, value) -> - val key = - (untypedKey as? String) - ?: throw IllegalArgumentException( - "map keys must be string, but got: " + - if (untypedKey === null) "null" else untypedKey::class.qualifiedName - ) - structProtoBuilder.putFields( - key, - when (value) { - null -> nullProtoValue - is Double -> value.toValueProto() - is Boolean -> value.toValueProto() - is String -> value.toValueProto() - is List<*> -> listValueForList.callRecursive(value).toValueProto() - is Map<*, *> -> callRecursive(value).toValueProto() - else -> - throw IllegalArgumentException( - "unsupported type: ${value::class.qualifiedName}; " + - "supported types are: Boolean, Double, String, List, and Map" - ) - } - ) - } - structProtoBuilder.build() - } - } - -private val valueToAnyMutualRecursion = - object { - val anyFromListValue: DeepRecursiveFunction> = - DeepRecursiveFunction { listValue -> - buildList { - for (element in listValue.valuesList) { - add(anyValueFromValue.callRecursive(element)) - } - } - } - - val anyValueFromStruct: DeepRecursiveFunction> = - DeepRecursiveFunction { struct -> - buildMap { - for (entry in struct.fieldsMap) { - put(entry.key, anyValueFromValue.callRecursive(entry.value)) - } - } - } - - val anyValueFromValue: DeepRecursiveFunction = DeepRecursiveFunction { value -> - when (value.kindCase) { - KindCase.BOOL_VALUE -> value.boolValue - KindCase.NUMBER_VALUE -> value.numberValue - KindCase.STRING_VALUE -> value.stringValue - KindCase.LIST_VALUE -> anyFromListValue.callRecursive(value.listValue) - KindCase.STRUCT_VALUE -> anyValueFromStruct.callRecursive(value.structValue) - KindCase.NULL_VALUE -> null - else -> "ERROR: unsupported kindCase: ${value.kindCase}" - } - } - } diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ReferenceCounted.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ReferenceCounted.kt new file mode 100644 index 00000000000..1d0e1233a01 --- /dev/null +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ReferenceCounted.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.firebase.dataconnect.util + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +internal class ReferenceCounted(val obj: T, var refCount: Int) + +internal abstract class ReferenceCountedSet { + + private val mutex = Mutex() + private val map = mutableMapOf>() + + suspend fun acquire(key: K): Entry { + val entry = + mutex.withLock { + map.getOrPut(key) { EntryImpl(this, key, valueForKey(key)) }.apply { refCount++ } + } + + if (entry.refCount == 1) { + onAllocate(entry) + } + + return entry + } + + suspend fun release(entry: Entry) { + require(entry is EntryImpl) { + "The given entry was expected to be an instance of ${EntryImpl::class.qualifiedName}, " + + "but was ${entry::class.qualifiedName}" + } + require(entry.set === this) { + "The given entry must be created by this object ($this), " + + "but was created by a different object (${entry.set})" + } + + val newRefCount = + mutex.withLock { + val entryFromMap = map[entry.key] + requireNotNull(entryFromMap) { "The given entry was not found in this set" } + require(entryFromMap === entry) { + "The key from the given entry was found in this set, but it was a different object" + } + require(entry.refCount > 0) { + "The refCount of the given entry was expected to be strictly greater than zero, " + + "but was ${entry.refCount}" + } + + entry.refCount-- + + if (entry.refCount == 0) { + map.remove(entry.key) + } + + entry.refCount + } + + if (newRefCount == 0) { + onFree(entry) + } + } + + protected abstract fun valueForKey(key: K): V + + protected open fun onAllocate(entry: Entry) {} + + protected open fun onFree(entry: Entry) {} + + interface Entry { + val key: K + val value: V + } + + private data class EntryImpl( + val set: ReferenceCountedSet, + override val key: K, + override val value: V, + var refCount: Int = 0, + ) : Entry + + companion object { + suspend fun ReferenceCountedSet.withAcquiredValue( + key: K, + callback: suspend (V) -> R + ): R { + val entry = acquire(key) + return try { + callback(entry.value) + } finally { + release(entry) + } + } + } +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/SequencedReference.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/SequencedReference.kt new file mode 100644 index 00000000000..94371b43e46 --- /dev/null +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/SequencedReference.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.dataconnect.util + +import java.util.concurrent.atomic.AtomicLong + +internal data class SequencedReference(val sequenceNumber: Long, val ref: T) { + + companion object { + + private val nextSequenceId = AtomicLong(0) + + /** + * Returns a positive number on each invocation, with each returned value being strictly greater + * than any value previously returned in this process. + * + * This function is thread-safe and may be called concurrently by multiple threads and/or + * coroutines. + */ + fun nextSequenceNumber(): Long { + return nextSequenceId.incrementAndGet() + } + + fun SequencedReference.map(block: (T) -> U): SequencedReference = + SequencedReference(sequenceNumber, block(ref)) + + suspend fun SequencedReference.mapSuspending( + block: suspend (T) -> U + ): SequencedReference = SequencedReference(sequenceNumber, block(ref)) + + fun ?> U.newerOfThisAnd(other: U): U = + if (this == null && other == null) { + // Suppress the warning that `this` is guaranteed to be null because the `null` literal + // cannot + // be used in place of `this` because if this extension function is called on a non-nullable + // reference then `null` is a forbidden return value and compilation will fail. + @Suppress("KotlinConstantConditions") this + } else if (this == null) { + other + } else if (other == null) { + this + } else if (this.sequenceNumber > other.sequenceNumber) { + this + } else { + other + } + + inline fun SequencedReference.asTypeOrNull(): + SequencedReference? = + if (ref is U) { + @Suppress("UNCHECKED_CAST") + this as SequencedReference + } else { + null + } + + inline fun SequencedReference.asTypeOrThrow(): + SequencedReference = + asTypeOrNull() + ?: throw IllegalStateException( + "expected ref to have type ${U::class.qualifiedName}, " + + "but got ${ref::class.qualifiedName} ($ref)" + ) + } +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/SuspendingLazy.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/SuspendingLazy.kt new file mode 100644 index 00000000000..bac902f28ba --- /dev/null +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/SuspendingLazy.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.dataconnect.util + +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext + +/** + * An adaptation of the standard library [lazy] builder that implements + * [LazyThreadSafetyMode.SYNCHRONIZED] with a suspending function and a [Mutex] rather than a + * blocking synchronization call. + * + * @param mutex the mutex to have locked when `initializer` is invoked; if null (the default) then a + * new lock will be used. + * @param coroutineContext the coroutine context with which to invoke `initializer`; if null (the + * default) then the context of the coroutine that calls [get] or [getLocked] will be used. + * @param initializer the block to invoke at most once to initialize this object's value. + */ +internal class SuspendingLazy( + mutex: Mutex? = null, + private val coroutineContext: CoroutineContext? = null, + initializer: suspend () -> T +) { + private val mutex = mutex ?: Mutex() + private var initializer: (suspend () -> T)? = initializer + @Volatile private var value: T? = null + + val initializedValueOrNull: T? + get() = value + + suspend inline fun get(): T = value ?: mutex.withLock { getLocked() } + + // This function _must_ be called by a coroutine that has locked the mutex given to the + // constructor; otherwise, a data race will occur, resulting in undefined behavior. + suspend fun getLocked(): T = + if (coroutineContext === null) { + getLockedInContext() + } else { + withContext(coroutineContext) { getLockedInContext() } + } + + private suspend inline fun getLockedInContext(): T = + value + ?: initializer!!().also { + value = it + initializer = null + } + + override fun toString(): String = + if (value !== null) value.toString() else "SuspendingLazy value not initialized yet." +} diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/Util.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/Util.kt deleted file mode 100644 index a8b0ade70a7..00000000000 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/Util.kt +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.dataconnect.util - -import java.io.OutputStream -import java.util.concurrent.atomic.AtomicLong -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock - -internal object NullOutputStream : OutputStream() { - override fun write(b: Int) {} - override fun write(b: ByteArray?) {} - override fun write(b: ByteArray?, off: Int, len: Int) {} -} - -internal class ReferenceCounted(val obj: T, var refCount: Int) - -private val nextSequenceId = AtomicLong(0) - -/** - * Returns a positive number on each invocation, with each returned value being strictly greater - * than any value previously returned in this process. - * - * This function is thread-safe and may be called concurrently by multiple threads and/or - * coroutines. - */ -internal fun nextSequenceNumber(): Long { - return nextSequenceId.incrementAndGet() -} - -internal data class SequencedReference(val sequenceNumber: Long, val ref: T) - -internal fun SequencedReference.map(block: (T) -> U): SequencedReference = - SequencedReference(sequenceNumber, block(ref)) - -internal suspend fun SequencedReference.mapSuspending( - block: suspend (T) -> U -): SequencedReference = SequencedReference(sequenceNumber, block(ref)) - -internal fun ?> U.newerOfThisAnd(other: U): U = - if (this == null && other == null) { - // Suppress the warning that `this` is guaranteed to be null because the `null` literal cannot - // be used in place of `this` because if this extension function is called on a non-nullable - // reference then `null` is a forbidden return value and compilation will fail. - @Suppress("KotlinConstantConditions") this - } else if (this == null) { - other - } else if (other == null) { - this - } else if (this.sequenceNumber > other.sequenceNumber) { - this - } else { - other - } - -internal inline fun SequencedReference.asTypeOrNull(): - SequencedReference? = - if (ref is U) { - @Suppress("UNCHECKED_CAST") - this as SequencedReference - } else { - null - } - -internal inline fun SequencedReference.asTypeOrThrow(): - SequencedReference = - asTypeOrNull() - ?: throw IllegalStateException( - "expected ref to have type ${U::class.qualifiedName}, " + - "but got ${ref::class.qualifiedName} ($ref)" - ) - -// NOTE: `ALPHANUMERIC_ALPHABET` MUST have a length of 32 (since 2^5=32). This allows encoding 5 -// bits as a single digit from this alphabet. Note that some numbers and letters were removed, -// especially those that can look similar in different fonts, like '1', 'l', and 'i'. -private const val ALPHANUMERIC_ALPHABET = "23456789abcdefghjkmnopqrstuvwxyz" - -/** - * Converts this byte array to a base-36 string, which uses the 26 letters from the English alphabet - * and the 10 numeric digits. - */ -internal fun ByteArray.toAlphaNumericString(): String = buildString { - val numBits = size * 8 - for (bitIndex in 0 until numBits step 5) { - val byteIndex = bitIndex.div(8) - val bitOffset = bitIndex.rem(8) - val b = this@toAlphaNumericString[byteIndex].toUByte().toInt() - - val intValue = - if (bitOffset <= 3) { - b shr (3 - bitOffset) - } else { - val upperBits = - when (bitOffset) { - 4 -> b and 0x0f - 5 -> b and 0x07 - 6 -> b and 0x03 - 7 -> b and 0x01 - else -> error("internal error: invalid bitOffset: $bitOffset") - } - if (byteIndex + 1 == size) { - upperBits - } else { - val b2 = this@toAlphaNumericString[byteIndex + 1].toUByte().toInt() - when (bitOffset) { - 4 -> ((b2 shr 7) and 0x01) or (upperBits shl 1) - 5 -> ((b2 shr 6) and 0x03) or (upperBits shl 2) - 6 -> ((b2 shr 5) and 0x07) or (upperBits shl 3) - 7 -> ((b2 shr 4) and 0x0f) or (upperBits shl 4) - else -> error("internal error: invalid bitOffset: $bitOffset") - } - } - } - - append(ALPHANUMERIC_ALPHABET[intValue and 0x1f]) - } -} - -/** - * An adaptation of the standard library [lazy] builder that implements - * [LazyThreadSafetyMode.SYNCHRONIZED] with a suspending function and a [Mutex] rather than a - * blocking synchronization call. - * - * @param mutex the mutex to have locked when `initializer` is invoked; if null (the default) then a - * new lock will be used. - * @param coroutineContext the coroutine context with which to invoke `initializer`; if null (the - * default) then the context of the coroutine that calls [get] or [getLocked] will be used. - * @param initializer the block to invoke at most once to initialize this object's value. - */ -internal class SuspendingLazy( - mutex: Mutex? = null, - private val coroutineContext: CoroutineContext? = null, - initializer: suspend () -> T -) { - private val mutex = mutex ?: Mutex() - private var initializer: (suspend () -> T)? = initializer - @Volatile private var value: T? = null - - val initializedValueOrNull: T? - get() = value - - suspend inline fun get(): T = value ?: mutex.withLock { getLocked() } - - // This function _must_ be called by a coroutine that has locked the mutex given to the - // constructor; otherwise, a data race will occur, resulting in undefined behavior. - suspend fun getLocked(): T = - if (coroutineContext === null) { - getLockedInContext() - } else { - withContext(coroutineContext) { getLockedInContext() } - } - - private suspend inline fun getLockedInContext(): T = - value - ?: initializer!!().also { - value = it - initializer = null - } - - override fun toString(): String = - if (value !== null) value.toString() else "SuspendingLazy value not initialized yet." -} - -internal class NullableReference(val ref: T? = null) { - override fun equals(other: Any?) = (other is NullableReference<*>) && other.ref == ref - override fun hashCode() = ref?.hashCode() ?: 0 - override fun toString() = ref?.toString() ?: "null" -} - -internal abstract class ReferenceCountedSet { - - private val mutex = Mutex() - private val map = mutableMapOf>() - - suspend fun acquire(key: K): Entry { - val entry = - mutex.withLock { - map.getOrPut(key) { EntryImpl(this, key, valueForKey(key)) }.apply { refCount++ } - } - - if (entry.refCount == 1) { - onAllocate(entry) - } - - return entry - } - - suspend fun release(entry: Entry) { - require(entry is EntryImpl) { - "The given entry was expected to be an instance of ${EntryImpl::class.qualifiedName}, " + - "but was ${entry::class.qualifiedName}" - } - require(entry.set === this) { - "The given entry must be created by this object ($this), " + - "but was created by a different object (${entry.set})" - } - - val newRefCount = - mutex.withLock { - val entryFromMap = map[entry.key] - requireNotNull(entryFromMap) { "The given entry was not found in this set" } - require(entryFromMap === entry) { - "The key from the given entry was found in this set, but it was a different object" - } - require(entry.refCount > 0) { - "The refCount of the given entry was expected to be strictly greater than zero, " + - "but was ${entry.refCount}" - } - - entry.refCount-- - - if (entry.refCount == 0) { - map.remove(entry.key) - } - - entry.refCount - } - - if (newRefCount == 0) { - onFree(entry) - } - } - - protected abstract fun valueForKey(key: K): V - - protected open fun onAllocate(entry: Entry) {} - - protected open fun onFree(entry: Entry) {} - - interface Entry { - val key: K - val value: V - } - - private data class EntryImpl( - val set: ReferenceCountedSet, - override val key: K, - override val value: V, - var refCount: Int = 0, - ) : Entry -} - -internal suspend fun ReferenceCountedSet.withAcquiredValue( - key: K, - callback: suspend (V) -> R -): R { - val entry = acquire(key) - return try { - callback(entry.value) - } finally { - release(entry) - } -} - -internal fun String.ellipsize(maxLength: Int = 13, ellipsis: String = "..."): String { - check(maxLength > ellipsis.length) { - "maxLength ($maxLength) must be greater than" + " the length of ellipsis (${ellipsis.length})" - } - - if (length <= maxLength) { - return this - } - val numCharsOnEitherEnd = (maxLength - ellipsis.length) / 2 - return substring(0, numCharsOnEitherEnd) + ellipsis + substring(length - numCharsOnEitherEnd) -} diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/AnyValueUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/AnyValueUnitTest.kt index cdd75b42f58..840090c9765 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/AnyValueUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/AnyValueUnitTest.kt @@ -25,7 +25,7 @@ import com.google.firebase.dataconnect.testutil.anyNumberScalar import com.google.firebase.dataconnect.testutil.anyScalar import com.google.firebase.dataconnect.testutil.anyStringScalar import com.google.firebase.dataconnect.testutil.filterNotNull -import com.google.firebase.dataconnect.util.encodeToValue +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToValue import io.kotest.assertions.assertSoftly import io.kotest.common.ExperimentalKotest import io.kotest.matchers.booleans.shouldBeFalse diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/ProtoStructDecoderUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/ProtoStructDecoderUnitTest.kt index 256489aaa01..fabfca07d56 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/ProtoStructDecoderUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/ProtoStructDecoderUnitTest.kt @@ -19,9 +19,9 @@ package com.google.firebase.dataconnect import com.google.common.truth.Truth.assertThat -import com.google.firebase.dataconnect.util.buildStructProto -import com.google.firebase.dataconnect.util.decodeFromStruct -import com.google.firebase.dataconnect.util.encodeToStruct +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.decodeFromStruct +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToStruct import com.google.protobuf.Struct import com.google.protobuf.Value import com.google.protobuf.Value.KindCase diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/ProtoStructEncoderUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/ProtoStructEncoderUnitTest.kt index f93bf50da9f..c73a31aa732 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/ProtoStructEncoderUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/ProtoStructEncoderUnitTest.kt @@ -20,8 +20,8 @@ package com.google.firebase.dataconnect import com.google.common.truth.Truth.assertThat import com.google.common.truth.extensions.proto.LiteProtoTruth.assertThat -import com.google.firebase.dataconnect.util.buildStructProto -import com.google.firebase.dataconnect.util.encodeToStruct +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToStruct import com.google.protobuf.Struct import java.util.concurrent.atomic.AtomicLong import kotlinx.serialization.ExperimentalSerializationApi diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/UtilUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/UtilUnitTest.kt index acad13306c7..4aa1cde83df 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/UtilUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/UtilUnitTest.kt @@ -17,7 +17,7 @@ package com.google.firebase.dataconnect import com.google.common.truth.Truth.assertThat -import com.google.firebase.dataconnect.util.toAlphaNumericString +import com.google.firebase.dataconnect.util.AlphanumericStringUtil.toAlphaNumericString import org.junit.Test class UtilUnitTest { diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt index 1d0a396de80..b601bc11478 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt @@ -23,6 +23,7 @@ import com.google.firebase.auth.GetTokenResult import com.google.firebase.auth.internal.IdTokenListener import com.google.firebase.auth.internal.InternalAuthProvider import com.google.firebase.dataconnect.DataConnectException +import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken import com.google.firebase.dataconnect.testutil.DataConnectLogLevelRule import com.google.firebase.dataconnect.testutil.DelayedDeferred import com.google.firebase.dataconnect.testutil.ImmediateDeferred diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcClientUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcClientUnitTest.kt index 6b43c1ba8bc..4fe6f17d9ec 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcClientUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcClientUnitTest.kt @@ -19,6 +19,7 @@ import com.google.firebase.dataconnect.DataConnectError import com.google.firebase.dataconnect.DataConnectException import com.google.firebase.dataconnect.DataConnectUntypedData import com.google.firebase.dataconnect.core.DataConnectGrpcClient.OperationResult +import com.google.firebase.dataconnect.core.DataConnectGrpcClientGlobals.deserialize import com.google.firebase.dataconnect.testutil.DataConnectLogLevelRule import com.google.firebase.dataconnect.testutil.callerSdkType import com.google.firebase.dataconnect.testutil.connectorConfig @@ -30,9 +31,9 @@ import com.google.firebase.dataconnect.testutil.operationResult import com.google.firebase.dataconnect.testutil.projectId import com.google.firebase.dataconnect.testutil.requestId import com.google.firebase.dataconnect.testutil.shouldHaveLoggedExactlyOneMessageContaining -import com.google.firebase.dataconnect.util.buildStructProto -import com.google.firebase.dataconnect.util.encodeToStruct -import com.google.firebase.dataconnect.util.toMap +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToStruct +import com.google.firebase.dataconnect.util.ProtoUtil.toMap import com.google.protobuf.ListValue import com.google.protobuf.Value import google.firebase.dataconnect.proto.ExecuteMutationRequest diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/MutationRefImplUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/MutationRefImplUnitTest.kt index 3f609b62fae..0a89123a808 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/MutationRefImplUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/MutationRefImplUnitTest.kt @@ -21,14 +21,17 @@ import com.google.firebase.dataconnect.DataConnectUntypedData import com.google.firebase.dataconnect.DataConnectUntypedVariables import com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType import com.google.firebase.dataconnect.core.DataConnectGrpcClient.OperationResult +import com.google.firebase.dataconnect.core.Globals.copy +import com.google.firebase.dataconnect.core.Globals.withDataDeserializer +import com.google.firebase.dataconnect.core.Globals.withVariablesSerializer import com.google.firebase.dataconnect.testutil.callerSdkType import com.google.firebase.dataconnect.testutil.dataConnectError import com.google.firebase.dataconnect.testutil.filterNotEqual import com.google.firebase.dataconnect.testutil.mutationRefImpl +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToStruct +import com.google.firebase.dataconnect.util.ProtoUtil.toStructProto import com.google.firebase.dataconnect.util.SuspendingLazy -import com.google.firebase.dataconnect.util.buildStructProto -import com.google.firebase.dataconnect.util.encodeToStruct -import com.google.firebase.dataconnect.util.toStructProto import com.google.protobuf.Struct import io.kotest.assertions.asClue import io.kotest.assertions.assertSoftly diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/QueryRefImplUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/QueryRefImplUnitTest.kt index 5fe4d2b814c..e6695163947 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/QueryRefImplUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/QueryRefImplUnitTest.kt @@ -16,6 +16,7 @@ package com.google.firebase.dataconnect.core +import com.google.firebase.dataconnect.core.Globals.copy import com.google.firebase.dataconnect.querymgr.QueryManager import com.google.firebase.dataconnect.testutil.callerSdkType import com.google.firebase.dataconnect.testutil.filterNotEqual diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/serializers/TimestampSerializerUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/serializers/TimestampSerializerUnitTest.kt index 3cbe1b6d281..8939279bbb0 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/serializers/TimestampSerializerUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/serializers/TimestampSerializerUnitTest.kt @@ -19,9 +19,9 @@ package com.google.firebase.dataconnect.serializers import com.google.common.truth.Truth.assertThat import com.google.firebase.Timestamp import com.google.firebase.dataconnect.testutil.assertThrows -import com.google.firebase.dataconnect.util.buildStructProto -import com.google.firebase.dataconnect.util.decodeFromStruct -import com.google.firebase.dataconnect.util.encodeToStruct +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.decodeFromStruct +import com.google.firebase.dataconnect.util.ProtoUtil.encodeToStruct import kotlinx.serialization.Serializable import org.junit.Test diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/Arbs.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/Arbs.kt index 93e633269cb..5ea02ad56c0 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/Arbs.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/Arbs.kt @@ -21,7 +21,7 @@ import com.google.firebase.dataconnect.DataConnectError import com.google.firebase.dataconnect.core.DataConnectGrpcClient import com.google.firebase.dataconnect.core.MutationRefImpl import com.google.firebase.dataconnect.core.QueryRefImpl -import com.google.firebase.dataconnect.util.toStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.toStructProto import io.kotest.property.Arb import io.kotest.property.arbitrary.Codepoint import io.kotest.property.arbitrary.alphanumeric diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/DataConnectAnySerializer.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/DataConnectAnySerializer.kt index ae15e816846..49082914e8f 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/DataConnectAnySerializer.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/DataConnectAnySerializer.kt @@ -15,12 +15,12 @@ */ package com.google.firebase.dataconnect.testutil +import com.google.firebase.dataconnect.util.ProtoUtil.nullProtoValue +import com.google.firebase.dataconnect.util.ProtoUtil.toListOfAny +import com.google.firebase.dataconnect.util.ProtoUtil.toMap +import com.google.firebase.dataconnect.util.ProtoUtil.toValueProto import com.google.firebase.dataconnect.util.ProtoValueDecoder import com.google.firebase.dataconnect.util.ProtoValueEncoder -import com.google.firebase.dataconnect.util.nullProtoValue -import com.google.firebase.dataconnect.util.toListOfAny -import com.google.firebase.dataconnect.util.toMap -import com.google.firebase.dataconnect.util.toValueProto import com.google.protobuf.Value import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/MockLogger.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/MockLogger.kt index 0230a734989..a8e0999fc1e 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/MockLogger.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/MockLogger.kt @@ -18,6 +18,7 @@ package com.google.firebase.dataconnect.testutil import com.google.firebase.dataconnect.LogLevel import com.google.firebase.dataconnect.core.Logger +import com.google.firebase.dataconnect.core.LoggerGlobals.Logger import io.mockk.Matcher import io.mockk.MockKMatcherScope import io.mockk.every From a3a2638d39a07c0db40bc8608aa6c397f3354bb2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 24 Sep 2024 19:29:13 +0000 Subject: [PATCH 2/2] fix integration test builds --- .../google/firebase/dataconnect/testutil/DataConnectBackend.kt | 1 + .../dataconnect/connectors/demo/AnyScalarIntegrationTest.kt | 1 + .../com/google/firebase/dataconnect/AuthIntegrationTest.kt | 2 +- .../dataconnect/testutil/InProcessDataConnectGrpcServer.kt | 2 +- .../firebase/dataconnect/testutil/schemas/AllTypesSchema.kt | 1 + .../firebase/dataconnect/testutil/schemas/PersonSchema.kt | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectBackend.kt b/firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectBackend.kt index 85b2f991c04..a62e1c6008f 100644 --- a/firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectBackend.kt +++ b/firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectBackend.kt @@ -21,6 +21,7 @@ import com.google.firebase.FirebaseApp import com.google.firebase.dataconnect.ConnectorConfig import com.google.firebase.dataconnect.DataConnectSettings import com.google.firebase.dataconnect.FirebaseDataConnect +import com.google.firebase.dataconnect.copy import com.google.firebase.dataconnect.getInstance import com.google.firebase.dataconnect.testutil.DataConnectBackend.Autopush import com.google.firebase.dataconnect.testutil.DataConnectBackend.Companion.fromInstrumentationArguments diff --git a/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/AnyScalarIntegrationTest.kt b/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/AnyScalarIntegrationTest.kt index 35dfffa9d79..4c38d1b0db4 100644 --- a/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/AnyScalarIntegrationTest.kt +++ b/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/AnyScalarIntegrationTest.kt @@ -20,6 +20,7 @@ import com.google.firebase.dataconnect.AnyValue import com.google.firebase.dataconnect.DataConnectException import com.google.firebase.dataconnect.OperationRef import com.google.firebase.dataconnect.connectors.demo.testutil.DemoConnectorIntegrationTestBase +import com.google.firebase.dataconnect.fromAny import com.google.firebase.dataconnect.generated.GeneratedMutation import com.google.firebase.dataconnect.generated.GeneratedQuery import com.google.firebase.dataconnect.testutil.EdgeCases diff --git a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/AuthIntegrationTest.kt b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/AuthIntegrationTest.kt index f34bbeb7304..5ff1d87fcf5 100644 --- a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/AuthIntegrationTest.kt +++ b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/AuthIntegrationTest.kt @@ -26,7 +26,7 @@ import com.google.firebase.dataconnect.testutil.operationName import com.google.firebase.dataconnect.testutil.schemas.PersonSchema import com.google.firebase.dataconnect.testutil.schemas.PersonSchema.GetPersonAuthQuery import com.google.firebase.dataconnect.testutil.schemas.randomPersonId -import com.google.firebase.dataconnect.util.buildStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto import com.google.firebase.util.nextAlphanumericString import google.firebase.dataconnect.proto.executeMutationResponse import google.firebase.dataconnect.proto.executeQueryResponse diff --git a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/InProcessDataConnectGrpcServer.kt b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/InProcessDataConnectGrpcServer.kt index 54714404354..9598c09fd6d 100644 --- a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/InProcessDataConnectGrpcServer.kt +++ b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/InProcessDataConnectGrpcServer.kt @@ -18,7 +18,7 @@ package com.google.firebase.dataconnect.testutil import com.google.firebase.FirebaseApp import com.google.firebase.dataconnect.FirebaseDataConnect -import com.google.firebase.dataconnect.util.buildStructProto +import com.google.firebase.dataconnect.util.ProtoUtil.buildStructProto import google.firebase.dataconnect.proto.ConnectorServiceGrpc import google.firebase.dataconnect.proto.ExecuteMutationRequest import google.firebase.dataconnect.proto.ExecuteMutationResponse diff --git a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/schemas/AllTypesSchema.kt b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/schemas/AllTypesSchema.kt index 25c412e7b18..21d14331320 100644 --- a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/schemas/AllTypesSchema.kt +++ b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/schemas/AllTypesSchema.kt @@ -17,6 +17,7 @@ package com.google.firebase.dataconnect.testutil.schemas import com.google.firebase.dataconnect.FirebaseDataConnect +import com.google.firebase.dataconnect.copy import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase.Companion.testConnectorConfig import com.google.firebase.dataconnect.testutil.TestDataConnectFactory diff --git a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/schemas/PersonSchema.kt b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/schemas/PersonSchema.kt index 0f13e0c4252..8da1dcf6d94 100644 --- a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/schemas/PersonSchema.kt +++ b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/testutil/schemas/PersonSchema.kt @@ -17,6 +17,7 @@ package com.google.firebase.dataconnect.testutil.schemas import com.google.firebase.dataconnect.FirebaseDataConnect +import com.google.firebase.dataconnect.copy import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase.Companion.testConnectorConfig import com.google.firebase.dataconnect.testutil.TestDataConnectFactory