Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions firebase-dataconnect/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Unreleased
- [changed] Internal refactor for reporting "paths" in response data.
[#7613](https://github.com/firebase/firebase-android-sdk/pull/7613))

# 17.1.2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,75 @@ public sealed interface DataConnectPathSegment {
override fun toString(): String = index.toString()
}
}

internal typealias DataConnectPath = List<DataConnectPathSegment>

internal fun <T : DataConnectPathSegment> List<T>.toPathString(): String = buildString {
appendPathStringTo(this)
}

internal fun <T : DataConnectPathSegment> List<T>.appendPathStringTo(sb: StringBuilder) {
forEachIndexed { segmentIndex, segment ->
when (segment) {
is DataConnectPathSegment.Field -> {
if (segmentIndex != 0) {
sb.append('.')
}
sb.append(segment.field)
}
is DataConnectPathSegment.ListIndex -> {
sb.append('[')
sb.append(segment.index)
sb.append(']')
}
}
}
}

internal fun MutableList<in DataConnectPathSegment.Field>.addField(
field: String
): DataConnectPathSegment.Field = DataConnectPathSegment.Field(field).also { add(it) }

internal fun MutableList<in DataConnectPathSegment.ListIndex>.addListIndex(
index: Int
): DataConnectPathSegment.ListIndex = DataConnectPathSegment.ListIndex(index).also { add(it) }

internal inline fun <T> MutableList<in DataConnectPathSegment.Field>.withAddedField(
field: String,
block: () -> T
): T = withAddedPathSegment(DataConnectPathSegment.Field(field), block)

internal inline fun <T> MutableList<in DataConnectPathSegment.ListIndex>.withAddedListIndex(
index: Int,
block: () -> T
): T = withAddedPathSegment(DataConnectPathSegment.ListIndex(index), block)

internal inline fun <T, S : DataConnectPathSegment> MutableList<in S>.withAddedPathSegment(
pathSegment: S,
block: () -> T
): T {
add(pathSegment)
try {
return block()
} finally {
val removedSegment = removeLastOrNull()
check(removedSegment === pathSegment) {
"internal error x6tzdsszmc: removed $removedSegment, but expected $pathSegment"
}
}
}

internal fun List<DataConnectPathSegment>.withAddedField(
field: String
): List<DataConnectPathSegment> = withAddedPathSegment(DataConnectPathSegment.Field(field))

internal fun List<DataConnectPathSegment>.withAddedListIndex(
index: Int
): List<DataConnectPathSegment> = withAddedPathSegment(DataConnectPathSegment.ListIndex(index))

internal fun List<DataConnectPathSegment>.withAddedPathSegment(
pathSegment: DataConnectPathSegment
): List<DataConnectPathSegment> = buildList {
addAll(this@withAddedPathSegment)
add(pathSegment)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.firebase.dataconnect.core
import com.google.firebase.dataconnect.DataConnectOperationFailureResponse
import com.google.firebase.dataconnect.DataConnectOperationFailureResponse.ErrorInfo
import com.google.firebase.dataconnect.DataConnectPathSegment
import com.google.firebase.dataconnect.appendPathStringTo
import java.util.Objects

internal class DataConnectOperationFailureResponseImpl<Data>(
Expand All @@ -41,24 +42,10 @@ internal class DataConnectOperationFailureResponseImpl<Data>(
override fun hashCode(): Int = Objects.hash("ErrorInfoImpl", message, path)

override fun toString(): String = buildString {
path.forEachIndexed { segmentIndex, segment ->
when (segment) {
is DataConnectPathSegment.Field -> {
if (segmentIndex != 0) {
append('.')
}
append(segment.field)
}
is DataConnectPathSegment.ListIndex -> {
append('[').append(segment.index).append(']')
}
}
}

if (path.isNotEmpty()) {
path.appendPathStringTo(this)
if (isNotEmpty()) {
append(": ")
}

append(message)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
package com.google.firebase.dataconnect.util

import com.google.firebase.dataconnect.AnyValue
import com.google.firebase.dataconnect.DataConnectPath
import com.google.firebase.dataconnect.serializers.AnyValueSerializer
import com.google.firebase.dataconnect.toPathString
import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeBoolean
import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeByte
import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeChar
Expand All @@ -34,6 +36,8 @@ 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.firebase.dataconnect.withAddedField
import com.google.firebase.dataconnect.withAddedListIndex
import com.google.protobuf.ListValue
import com.google.protobuf.NullValue
import com.google.protobuf.Struct
Expand All @@ -58,59 +62,64 @@ import kotlinx.serialization.modules.SerializersModule
* avoids this public API pollution.
*/
private object ProtoDecoderUtil {
fun <T> decode(value: Value, path: String?, expectedKindCase: KindCase, block: (Value) -> T): T =
fun <T> decode(
value: Value,
path: DataConnectPath,
expectedKindCase: KindCase,
block: (Value) -> T
): T =
if (value.kindCase != expectedKindCase) {
throw SerializationException(
(if (path === null) "" else "decoding \"$path\" failed: ") +
(if (path.isEmpty()) "" else "decoding \"${path.toPathString()}\" failed: ") +
"expected $expectedKindCase, but got ${value.kindCase} (${value.toAny()})"
)
} else {
block(value)
}

fun decodeBoolean(value: Value, path: String?): Boolean =
fun decodeBoolean(value: Value, path: DataConnectPath): Boolean =
decode(value, path, KindCase.BOOL_VALUE) { it.boolValue }

fun decodeByte(value: Value, path: String?): Byte =
fun decodeByte(value: Value, path: DataConnectPath): Byte =
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toByte() }

fun decodeChar(value: Value, path: String?): Char =
fun decodeChar(value: Value, path: DataConnectPath): Char =
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toChar() }

fun decodeDouble(value: Value, path: String?): Double =
fun decodeDouble(value: Value, path: DataConnectPath): Double =
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue }

fun decodeEnum(value: Value, path: String?): String =
fun decodeEnum(value: Value, path: DataConnectPath): String =
decode(value, path, KindCase.STRING_VALUE) { it.stringValue }

fun decodeFloat(value: Value, path: String?): Float =
fun decodeFloat(value: Value, path: DataConnectPath): Float =
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toFloat() }

fun decodeString(value: Value, path: String?): String =
fun decodeString(value: Value, path: DataConnectPath): String =
decode(value, path, KindCase.STRING_VALUE) { it.stringValue }

fun decodeStruct(value: Value, path: String?): Struct =
fun decodeStruct(value: Value, path: DataConnectPath): Struct =
decode(value, path, KindCase.STRUCT_VALUE) { it.structValue }

fun decodeList(value: Value, path: String?): ListValue =
fun decodeList(value: Value, path: DataConnectPath): ListValue =
decode(value, path, KindCase.LIST_VALUE) { it.listValue }

fun decodeNull(value: Value, path: String?): NullValue =
fun decodeNull(value: Value, path: DataConnectPath): NullValue =
decode(value, path, KindCase.NULL_VALUE) { it.nullValue }

fun decodeInt(value: Value, path: String?): Int =
fun decodeInt(value: Value, path: DataConnectPath): Int =
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt() }

fun decodeLong(value: Value, path: String?): Long =
fun decodeLong(value: Value, path: DataConnectPath): Long =
decode(value, path, KindCase.STRING_VALUE) { it.stringValue.toLong() }

fun decodeShort(value: Value, path: String?): Short =
fun decodeShort(value: Value, path: DataConnectPath): Short =
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toShort() }
}

internal class ProtoValueDecoder(
internal val valueProto: Value,
private val path: String?,
private val path: DataConnectPath,
override val serializersModule: SerializersModule
) : Decoder {

Expand Down Expand Up @@ -162,7 +171,7 @@ internal class ProtoValueDecoder(

private class ProtoStructValueDecoder(
private val struct: Struct,
private val path: String?,
private val path: DataConnectPath,
override val serializersModule: SerializersModule
) : CompositeDecoder {

Expand Down Expand Up @@ -228,7 +237,7 @@ private class ProtoStructValueDecoder(
private fun <T> decodeValueElement(
descriptor: SerialDescriptor,
index: Int,
block: (Value, String?) -> T
block: (Value, DataConnectPath) -> T
): T {
val elementName = descriptor.getElementName(index)
val elementPath = elementPathForName(elementName)
Expand Down Expand Up @@ -290,13 +299,13 @@ private class ProtoStructValueDecoder(
}
}

private fun elementPathForName(elementName: String) =
if (path === null) elementName else "${path}.${elementName}"
private fun elementPathForName(elementName: String): DataConnectPath =
path.withAddedField(elementName)
}

private class ProtoListValueDecoder(
private val list: ListValue,
private val path: String?,
private val path: DataConnectPath,
override val serializersModule: SerializersModule
) : CompositeDecoder {

Expand Down Expand Up @@ -339,7 +348,7 @@ private class ProtoListValueDecoder(
override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) =
decodeValueElement(index, ProtoDecoderUtil::decodeString)

private inline fun <T> decodeValueElement(index: Int, block: (Value, String?) -> T): T =
private inline fun <T> decodeValueElement(index: Int, block: (Value, DataConnectPath) -> T): T =
block(list.valuesList[index], elementPathForIndex(index))

override fun <T> decodeSerializableElement(
Expand Down Expand Up @@ -373,14 +382,15 @@ private class ProtoListValueDecoder(
decodeSerializableElement(descriptor, index, deserializer, previousValue = null)
}

private fun elementPathForIndex(index: Int) = if (path === null) "[$index]" else "${path}[$index]"
private fun elementPathForIndex(index: Int): DataConnectPath = path.withAddedListIndex(index)

override fun toString() = "ProtoListValueDecoder{path=$path, size=${list.valuesList.size}"
override fun toString() =
"ProtoListValueDecoder{path=${path.toPathString()}, size=${list.valuesList.size}}"
}

private class ProtoMapValueDecoder(
private val struct: Struct,
private val path: String?,
private val path: DataConnectPath,
override val serializersModule: SerializersModule
) : CompositeDecoder {

Expand Down Expand Up @@ -435,7 +445,7 @@ private class ProtoMapValueDecoder(
decodeValueElement(index, ProtoDecoderUtil::decodeString)
}

private inline fun <T> decodeValueElement(index: Int, block: (Value, String?) -> T): T {
private inline fun <T> decodeValueElement(index: Int, block: (Value, DataConnectPath) -> T): T {
require(index % 2 != 0) { "invalid value index: $index" }
val value = structEntryByElementIndex(index).value
val elementPath = elementPathForIndex(index)
Expand Down Expand Up @@ -491,21 +501,22 @@ private class ProtoMapValueDecoder(
return deserializer.deserialize(elementDecoder)
}

private fun elementPathForIndex(index: Int): String {
private fun elementPathForIndex(index: Int): DataConnectPath {
val structEntry = structEntryByElementIndex(index)
val key = structEntry.key
return if (index % 2 == 0) {
if (path === null) "[$key]" else "${path}[$key]"
path.withAddedField(key)
} else {
if (path === null) "[$key].value" else "${path}[$key].value"
path.withAddedField(key).withAddedField("value")
}
}

override fun toString() = "ProtoMapValueDecoder{path=$path, size=${struct.fieldsCount}"
override fun toString() =
"ProtoMapValueDecoder{path=${path.toPathString()}, size=${struct.fieldsCount}"
}

private class ProtoObjectValueDecoder(
val path: String?,
val path: DataConnectPath,
override val serializersModule: SerializersModule
) : CompositeDecoder {

Expand Down Expand Up @@ -553,12 +564,12 @@ private class ProtoObjectValueDecoder(

override fun endStructure(descriptor: SerialDescriptor) {}

override fun toString() = "ProtoObjectValueDecoder{path=$path}"
override fun toString() = "ProtoObjectValueDecoder{path=${path.toPathString()}}"
}

private class MapKeyDecoder(
val key: String,
val path: String,
val path: DataConnectPath,
override val serializersModule: SerializersModule
) : Decoder {

Expand Down Expand Up @@ -595,5 +606,5 @@ private class MapKeyDecoder(
"The only valid method call on MapKeyDecoder is decodeString()"
)

override fun toString() = "MapKeyDecoder{path=$path}"
override fun toString() = "MapKeyDecoder{path=${path.toPathString()}"
}
Loading
Loading