Skip to content

Commit a41dfcf

Browse files
authored
dataconnect: internal refactor to use higher-level types to work with proto struct paths (#7613)
1 parent 2968781 commit a41dfcf

File tree

12 files changed

+590
-223
lines changed

12 files changed

+590
-223
lines changed

firebase-dataconnect/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Unreleased
22

3+
- [changed] Internal refactor for reporting "paths" in response data.
4+
[#7613](https://github.com/firebase/firebase-android-sdk/pull/7613))
5+
36
# 17.1.2
47

58
- [changed] Internal refactor for managing Auth and App Check tokens

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/DataConnectPathSegment.kt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,75 @@ public sealed interface DataConnectPathSegment {
5454
override fun toString(): String = index.toString()
5555
}
5656
}
57+
58+
internal typealias DataConnectPath = List<DataConnectPathSegment>
59+
60+
internal fun <T : DataConnectPathSegment> List<T>.toPathString(): String = buildString {
61+
appendPathStringTo(this)
62+
}
63+
64+
internal fun <T : DataConnectPathSegment> List<T>.appendPathStringTo(sb: StringBuilder) {
65+
forEachIndexed { segmentIndex, segment ->
66+
when (segment) {
67+
is DataConnectPathSegment.Field -> {
68+
if (segmentIndex != 0) {
69+
sb.append('.')
70+
}
71+
sb.append(segment.field)
72+
}
73+
is DataConnectPathSegment.ListIndex -> {
74+
sb.append('[')
75+
sb.append(segment.index)
76+
sb.append(']')
77+
}
78+
}
79+
}
80+
}
81+
82+
internal fun MutableList<in DataConnectPathSegment.Field>.addField(
83+
field: String
84+
): DataConnectPathSegment.Field = DataConnectPathSegment.Field(field).also { add(it) }
85+
86+
internal fun MutableList<in DataConnectPathSegment.ListIndex>.addListIndex(
87+
index: Int
88+
): DataConnectPathSegment.ListIndex = DataConnectPathSegment.ListIndex(index).also { add(it) }
89+
90+
internal inline fun <T> MutableList<in DataConnectPathSegment.Field>.withAddedField(
91+
field: String,
92+
block: () -> T
93+
): T = withAddedPathSegment(DataConnectPathSegment.Field(field), block)
94+
95+
internal inline fun <T> MutableList<in DataConnectPathSegment.ListIndex>.withAddedListIndex(
96+
index: Int,
97+
block: () -> T
98+
): T = withAddedPathSegment(DataConnectPathSegment.ListIndex(index), block)
99+
100+
internal inline fun <T, S : DataConnectPathSegment> MutableList<in S>.withAddedPathSegment(
101+
pathSegment: S,
102+
block: () -> T
103+
): T {
104+
add(pathSegment)
105+
try {
106+
return block()
107+
} finally {
108+
val removedSegment = removeLastOrNull()
109+
check(removedSegment === pathSegment) {
110+
"internal error x6tzdsszmc: removed $removedSegment, but expected $pathSegment"
111+
}
112+
}
113+
}
114+
115+
internal fun List<DataConnectPathSegment>.withAddedField(
116+
field: String
117+
): List<DataConnectPathSegment> = withAddedPathSegment(DataConnectPathSegment.Field(field))
118+
119+
internal fun List<DataConnectPathSegment>.withAddedListIndex(
120+
index: Int
121+
): List<DataConnectPathSegment> = withAddedPathSegment(DataConnectPathSegment.ListIndex(index))
122+
123+
internal fun List<DataConnectPathSegment>.withAddedPathSegment(
124+
pathSegment: DataConnectPathSegment
125+
): List<DataConnectPathSegment> = buildList {
126+
addAll(this@withAddedPathSegment)
127+
add(pathSegment)
128+
}

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectOperationFailureResponseImpl.kt

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.google.firebase.dataconnect.core
1919
import com.google.firebase.dataconnect.DataConnectOperationFailureResponse
2020
import com.google.firebase.dataconnect.DataConnectOperationFailureResponse.ErrorInfo
2121
import com.google.firebase.dataconnect.DataConnectPathSegment
22+
import com.google.firebase.dataconnect.appendPathStringTo
2223
import java.util.Objects
2324

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

4344
override fun toString(): String = buildString {
44-
path.forEachIndexed { segmentIndex, segment ->
45-
when (segment) {
46-
is DataConnectPathSegment.Field -> {
47-
if (segmentIndex != 0) {
48-
append('.')
49-
}
50-
append(segment.field)
51-
}
52-
is DataConnectPathSegment.ListIndex -> {
53-
append('[').append(segment.index).append(']')
54-
}
55-
}
56-
}
57-
58-
if (path.isNotEmpty()) {
45+
path.appendPathStringTo(this)
46+
if (isNotEmpty()) {
5947
append(": ")
6048
}
61-
6249
append(message)
6350
}
6451
}

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoStructDecoder.kt

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
package com.google.firebase.dataconnect.util
2020

2121
import com.google.firebase.dataconnect.AnyValue
22+
import com.google.firebase.dataconnect.DataConnectPath
2223
import com.google.firebase.dataconnect.serializers.AnyValueSerializer
24+
import com.google.firebase.dataconnect.toPathString
2325
import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeBoolean
2426
import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeByte
2527
import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeChar
@@ -34,6 +36,8 @@ import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeShort
3436
import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeString
3537
import com.google.firebase.dataconnect.util.ProtoDecoderUtil.decodeStruct
3638
import com.google.firebase.dataconnect.util.ProtoUtil.toAny
39+
import com.google.firebase.dataconnect.withAddedField
40+
import com.google.firebase.dataconnect.withAddedListIndex
3741
import com.google.protobuf.ListValue
3842
import com.google.protobuf.NullValue
3943
import com.google.protobuf.Struct
@@ -58,59 +62,64 @@ import kotlinx.serialization.modules.SerializersModule
5862
* avoids this public API pollution.
5963
*/
6064
private object ProtoDecoderUtil {
61-
fun <T> decode(value: Value, path: String?, expectedKindCase: KindCase, block: (Value) -> T): T =
65+
fun <T> decode(
66+
value: Value,
67+
path: DataConnectPath,
68+
expectedKindCase: KindCase,
69+
block: (Value) -> T
70+
): T =
6271
if (value.kindCase != expectedKindCase) {
6372
throw SerializationException(
64-
(if (path === null) "" else "decoding \"$path\" failed: ") +
73+
(if (path.isEmpty()) "" else "decoding \"${path.toPathString()}\" failed: ") +
6574
"expected $expectedKindCase, but got ${value.kindCase} (${value.toAny()})"
6675
)
6776
} else {
6877
block(value)
6978
}
7079

71-
fun decodeBoolean(value: Value, path: String?): Boolean =
80+
fun decodeBoolean(value: Value, path: DataConnectPath): Boolean =
7281
decode(value, path, KindCase.BOOL_VALUE) { it.boolValue }
7382

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

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

80-
fun decodeDouble(value: Value, path: String?): Double =
89+
fun decodeDouble(value: Value, path: DataConnectPath): Double =
8190
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue }
8291

83-
fun decodeEnum(value: Value, path: String?): String =
92+
fun decodeEnum(value: Value, path: DataConnectPath): String =
8493
decode(value, path, KindCase.STRING_VALUE) { it.stringValue }
8594

86-
fun decodeFloat(value: Value, path: String?): Float =
95+
fun decodeFloat(value: Value, path: DataConnectPath): Float =
8796
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toFloat() }
8897

89-
fun decodeString(value: Value, path: String?): String =
98+
fun decodeString(value: Value, path: DataConnectPath): String =
9099
decode(value, path, KindCase.STRING_VALUE) { it.stringValue }
91100

92-
fun decodeStruct(value: Value, path: String?): Struct =
101+
fun decodeStruct(value: Value, path: DataConnectPath): Struct =
93102
decode(value, path, KindCase.STRUCT_VALUE) { it.structValue }
94103

95-
fun decodeList(value: Value, path: String?): ListValue =
104+
fun decodeList(value: Value, path: DataConnectPath): ListValue =
96105
decode(value, path, KindCase.LIST_VALUE) { it.listValue }
97106

98-
fun decodeNull(value: Value, path: String?): NullValue =
107+
fun decodeNull(value: Value, path: DataConnectPath): NullValue =
99108
decode(value, path, KindCase.NULL_VALUE) { it.nullValue }
100109

101-
fun decodeInt(value: Value, path: String?): Int =
110+
fun decodeInt(value: Value, path: DataConnectPath): Int =
102111
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt() }
103112

104-
fun decodeLong(value: Value, path: String?): Long =
113+
fun decodeLong(value: Value, path: DataConnectPath): Long =
105114
decode(value, path, KindCase.STRING_VALUE) { it.stringValue.toLong() }
106115

107-
fun decodeShort(value: Value, path: String?): Short =
116+
fun decodeShort(value: Value, path: DataConnectPath): Short =
108117
decode(value, path, KindCase.NUMBER_VALUE) { it.numberValue.toInt().toShort() }
109118
}
110119

111120
internal class ProtoValueDecoder(
112121
internal val valueProto: Value,
113-
private val path: String?,
122+
private val path: DataConnectPath,
114123
override val serializersModule: SerializersModule
115124
) : Decoder {
116125

@@ -162,7 +171,7 @@ internal class ProtoValueDecoder(
162171

163172
private class ProtoStructValueDecoder(
164173
private val struct: Struct,
165-
private val path: String?,
174+
private val path: DataConnectPath,
166175
override val serializersModule: SerializersModule
167176
) : CompositeDecoder {
168177

@@ -228,7 +237,7 @@ private class ProtoStructValueDecoder(
228237
private fun <T> decodeValueElement(
229238
descriptor: SerialDescriptor,
230239
index: Int,
231-
block: (Value, String?) -> T
240+
block: (Value, DataConnectPath) -> T
232241
): T {
233242
val elementName = descriptor.getElementName(index)
234243
val elementPath = elementPathForName(elementName)
@@ -290,13 +299,13 @@ private class ProtoStructValueDecoder(
290299
}
291300
}
292301

293-
private fun elementPathForName(elementName: String) =
294-
if (path === null) elementName else "${path}.${elementName}"
302+
private fun elementPathForName(elementName: String): DataConnectPath =
303+
path.withAddedField(elementName)
295304
}
296305

297306
private class ProtoListValueDecoder(
298307
private val list: ListValue,
299-
private val path: String?,
308+
private val path: DataConnectPath,
300309
override val serializersModule: SerializersModule
301310
) : CompositeDecoder {
302311

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

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

345354
override fun <T> decodeSerializableElement(
@@ -373,14 +382,15 @@ private class ProtoListValueDecoder(
373382
decodeSerializableElement(descriptor, index, deserializer, previousValue = null)
374383
}
375384

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

378-
override fun toString() = "ProtoListValueDecoder{path=$path, size=${list.valuesList.size}"
387+
override fun toString() =
388+
"ProtoListValueDecoder{path=${path.toPathString()}, size=${list.valuesList.size}}"
379389
}
380390

381391
private class ProtoMapValueDecoder(
382392
private val struct: Struct,
383-
private val path: String?,
393+
private val path: DataConnectPath,
384394
override val serializersModule: SerializersModule
385395
) : CompositeDecoder {
386396

@@ -435,7 +445,7 @@ private class ProtoMapValueDecoder(
435445
decodeValueElement(index, ProtoDecoderUtil::decodeString)
436446
}
437447

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

494-
private fun elementPathForIndex(index: Int): String {
504+
private fun elementPathForIndex(index: Int): DataConnectPath {
495505
val structEntry = structEntryByElementIndex(index)
496506
val key = structEntry.key
497507
return if (index % 2 == 0) {
498-
if (path === null) "[$key]" else "${path}[$key]"
508+
path.withAddedField(key)
499509
} else {
500-
if (path === null) "[$key].value" else "${path}[$key].value"
510+
path.withAddedField(key).withAddedField("value")
501511
}
502512
}
503513

504-
override fun toString() = "ProtoMapValueDecoder{path=$path, size=${struct.fieldsCount}"
514+
override fun toString() =
515+
"ProtoMapValueDecoder{path=${path.toPathString()}, size=${struct.fieldsCount}"
505516
}
506517

507518
private class ProtoObjectValueDecoder(
508-
val path: String?,
519+
val path: DataConnectPath,
509520
override val serializersModule: SerializersModule
510521
) : CompositeDecoder {
511522

@@ -553,12 +564,12 @@ private class ProtoObjectValueDecoder(
553564

554565
override fun endStructure(descriptor: SerialDescriptor) {}
555566

556-
override fun toString() = "ProtoObjectValueDecoder{path=$path}"
567+
override fun toString() = "ProtoObjectValueDecoder{path=${path.toPathString()}}"
557568
}
558569

559570
private class MapKeyDecoder(
560571
val key: String,
561-
val path: String,
572+
val path: DataConnectPath,
562573
override val serializersModule: SerializersModule
563574
) : Decoder {
564575

@@ -595,5 +606,5 @@ private class MapKeyDecoder(
595606
"The only valid method call on MapKeyDecoder is decodeString()"
596607
)
597608

598-
override fun toString() = "MapKeyDecoder{path=$path}"
609+
override fun toString() = "MapKeyDecoder{path=${path.toPathString()}}"
599610
}

0 commit comments

Comments
 (0)