Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions firebase-dataconnect/api.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
// Signature format: 2.0
package com.google.firebase.dataconnect {

@kotlinx.serialization.Serializable(with=AnyValueSerializer::class) public final class AnyValue {
ctor public AnyValue(@NonNull java.util.Map<java.lang.String,?> value);
ctor public AnyValue(@NonNull java.util.List<?> value);
ctor public AnyValue(@NonNull String value);
ctor public AnyValue(boolean value);
ctor public AnyValue(double value);
method public <T> T decode(@NonNull kotlinx.serialization.DeserializationStrategy<? extends T> deserializer);
method public inline <reified T> 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 <T> com.google.firebase.dataconnect.AnyValue encode(@Nullable T value, @NonNull kotlinx.serialization.SerializationStrategy<? super T> serializer);
method public inline <reified T> 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 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);
Expand Down Expand Up @@ -213,6 +233,14 @@ package com.google.firebase.dataconnect.generated {

package com.google.firebase.dataconnect.serializers {

public final class AnyValueSerializer implements kotlinx.serialization.KSerializer<com.google.firebase.dataconnect.AnyValue> {
method @NonNull public com.google.firebase.dataconnect.AnyValue deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder);
method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull com.google.firebase.dataconnect.AnyValue value);
property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
field @NonNull public static final com.google.firebase.dataconnect.serializers.AnyValueSerializer INSTANCE;
}

public final class DateSerializer implements kotlinx.serialization.KSerializer<java.util.Date> {
method @NonNull public java.util.Date deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder);
method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* 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

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.protobuf.Struct
import com.google.protobuf.Value
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.serializer

/**
* Represents a variable or field of the Data Connect custom scalar type `Any`.
*
* ### Valid Values for `AnyValue`
*
* `AnyValue` can encapsulate [String], [Boolean], [Double], a [List] of one of these types, or a
* [Map] whose values are one of these types. The values can be arbitrarily nested (e.g. a list that
* contains a map that contains other maps, and so on. The lists and maps can contain heterogeneous
* values; for example, a single [List] can contain a [String] value, some [Boolean] values, and
* some [List] values. The values of a [List] or a [Map] may be `null`. The only exception is that a
* variable or field declared as `[Any]` in GraphQL may _not_ have `null` values in the top-level
* list; however, nested lists or maps _may_ contain null values.
*
* ### Storing `Int` in an `AnyValue`
*
* To store an [Int] value, simply convert it to a [Double] and store the [Double] value.
*
* ### Storing `Long` in an `AnyValue`
*
* To store a [Long] value, converting it to a [Double] can be lossy if the value is sufficiently
* large (or small) to not be exactly represented by [Double]. The _largest_ [Long] value that can
* be stored in a [Double] with its exact value is `2^53 – 1` (`9007199254740991`). The _smallest_
* [Long] value that can be stored in a [Double] with its exact value is `-(2^53 – 1)`
* (`-9007199254740991`). This limitation is exactly the same in JavaScript, which does not have a
* native "int" or "long" type, but rather stores all numeric values in a 64-bit floating point
* value. See
* [MAX_SAFE_INTEGER](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER])
* and
* [MIN_SAFE_INTEGER](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER])
* for more details.
*
* ### Integration with `kotlinx.serialization`
*
* To serialize a value of this type when using Data Connect, use [AnyValueSerializer].
*
* ### Example
*
* For example, suppose this schema and operation is defined in the GraphQL source:
*
* ```
* type Foo @table { value: Any }
* mutation FooInsert($value: Any) {
* key: foo_insert(data: { value: $value })
* }
* ```
*
* then a serializable "Variables" type could be defined as follows:
*
* ```
* @Serializable
* data class FooInsertVariables(
* @Serializable(with=AnyValueSerializer::class) val value: AnyValue?
* )
* ```
*/
@Serializable(with = AnyValueSerializer::class)
public class AnyValue internal constructor(internal val protoValue: Value) {

init {
require(protoValue.kindCase != Value.KindCase.NULL_VALUE) {
"NULL_VALUE is not allowed; just use null"
}
}

internal constructor(struct: Struct) : this(struct.toValueProto())

/**
* Creates an instance that encapsulates the given [Map].
*
* An exception is thrown if any of the values of the map, or its sub-values, are invalid for
* being stored in [AnyValue]; see the [AnyValue] class documentation for a detailed description
* of value values.
*
* This class makes a _copy_ of the given map; therefore, any modifications to the map after this
* object is created will have no effect on this [AnyValue] object.
*/
public constructor(value: Map<String, Any?>) : this(value.toValueProto())

/**
* Creates an instance that encapsulates the given [List].
*
* An exception is thrown if any of the values of the list, or its sub-values, are invalid for
* being stored in [AnyValue]; see the [AnyValue] class documentation for a detailed description
* of value values.
*
* This class makes a _copy_ of the given list; therefore, any modifications to the list after
* this object is created will have no effect on this [AnyValue] object.
*/
public constructor(value: List<Any?>) : this(value.toValueProto())

/** Creates an instance that encapsulates the given [String]. */
public constructor(value: String) : this(value.toValueProto())

/** Creates an instance that encapsulates the given [Boolean]. */
public constructor(value: Boolean) : this(value.toValueProto())

/** Creates an instance that encapsulates the given [Double]. */
public constructor(value: Double) : this(value.toValueProto())

/**
* The native Kotlin type of the value encapsulated in this object.
*
* Although this type is `Any` it will be one of `String, `Boolean`, `Double`, `List<Any?>` or
* `Map<String, Any?>`. See the [AnyValue] class documentation for a detailed description of the
* types of values that are supported.
*/
public val value: Any
// NOTE: The not-null assertion operator (!!) below will never throw because the `init` block
// 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.
*
* @return the object of type `T` created by decoding the encapsulated value using the given
* deserializer.
*/
public fun <T> decode(deserializer: DeserializationStrategy<T>): T =
decodeFromValue(deserializer, protoValue)

/**
* 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 <reified T> decode(): T = decode(serializer<T>())

/**
* Compares this object with another object for equality.
*
* @param other The object to compare to this for equality.
* @return true if, and only if, the other object is an instance of [AnyValue] whose encapsulated
* value compares equal using the `==` operator to the given object.
*/
override fun equals(other: Any?): Boolean = other is AnyValue && other.value == value

/**
* Calculates and returns the hash code for this object.
*
* The hash code is _not_ guaranteed to be stable across application restarts.
*
* @return the hash code for this object, calculated from the encapsulated value.
*/
override fun hashCode(): Int = value.hashCode()

/**
* Returns a string representation of this object, useful for debugging.
*
* The string representation is _not_ guaranteed to be stable and may change without notice at any
* time. Therefore, the only recommended usage of the returned string is debugging and/or logging.
* Namely, parsing the returned string or storing the returned string in non-volatile storage
* should generally be avoided in order to be robust in case that the string representation
* changes.
*
* @return a string representation of this object's encapsulated value.
*/
override fun toString(): String = protoValue.toCompactString(keySortSelector = { it })

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.
*
* @return a new `AnyValue` object whose encapsulated value is the encoding of the given value
* when decoded with the given serializer.
*/
public fun <T> encode(value: T, serializer: SerializationStrategy<T>): AnyValue =
AnyValue(encodeToValue(serializer, value))

/**
* 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 <reified T> encode(value: T): AnyValue = encode(value, serializer<T>())

/**
* 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)

/**
* 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<String, Any?>)
else ->
throw IllegalArgumentException(
"unsupported type: ${value::class.qualifiedName}" +
" (supported types: null, String, Boolean, Double, List<Any?>, Map<String, Any?>)"
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ import kotlinx.serialization.SerializationStrategy
* or later; otherwise, a compilation error like `Class 'FooConnector' is not abstract and does not
* implement abstract member public abstract fun equals(other: Any?): Boolean defined in
* com.google.firebase.dataconnect.generated.GeneratedConnector` will occur.
* - [#NNNN](https://github.com/firebase/firebase-android-sdk/pull/NNNN]) Added [AnyValue] class to
* support the custom `Any` GraphQL scalar type. Support for `Any` scalars in the Android SDK code
* generation was added in the dataconnect toolkit v1.3.8 (released Sept 20, 2024), which will be
* included in the next release of firebase-tools (the release following v13.18.0).
*
* #### 16.0.0-alpha05 (June 24, 2024)
* - [#6003](https://github.com/firebase/firebase-android-sdk/pull/6003]) Fixed [close] to
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.serializers

import com.google.firebase.dataconnect.AnyValue
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

/**
* An implementation of [KSerializer] for serializing and deserializing [AnyValue] objects.
*
* Note that this is _not_ a generic serializer, but is only useful in the Data Connect SDK.
*/
public object AnyValueSerializer : KSerializer<AnyValue> {

override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("com.google.firebase.dataconnect.AnyValue") {}

override fun serialize(encoder: Encoder, value: AnyValue): Unit = unsupported()

override fun deserialize(decoder: Decoder): AnyValue = unsupported()

private fun unsupported(): Nothing =
throw UnsupportedOperationException(
"The AnyValueSerializer class cannot actually be used;" +
" it is merely a sentinel that gets special treatment during Data Connect serialization"
)
}
Loading
Loading