Skip to content

Commit 5160711

Browse files
committed
Update DeferredJsonMerger to take pending and completed into account.
1 parent 9def5f3 commit 5160711

File tree

2 files changed

+1093
-383
lines changed

2 files changed

+1093
-383
lines changed

libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/internal/DeferredJsonMerger.kt

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,25 @@ private typealias MutableJsonMap = MutableMap<String, Any?>
1515
* Each call to [merge] will merge the given chunk into the [merged] Map, and will also update the [mergedFragmentIds] Set with the
1616
* value of its `path` and `label` field.
1717
*
18-
* The fields in `data` are merged into the node found in [merged] at `path` (for the first call to [merge], the payload is
19-
* copied to [merged] as-is).
18+
* The fields in `data` are merged into the node found in [merged] at the path known by looking at the `id` field (for the first call to
19+
* [merge], the payload is copied to [merged] as-is).
2020
*
21-
* `errors` in incremental items (if present) are merged together in an array and then set to the `errors` field of the [merged] Map,
22-
* at each call to [merge].
23-
* `extensions` in incremental items (if present) are merged together in an array and then set to the `extensions/incremental` field of the
21+
* `errors` in incremental and completed items (if present) are merged together in an array and then set to the `errors` field of the
2422
* [merged] Map, at each call to [merge].
23+
* `extensions` in incremental items (if present) are merged together in an array and then set to the `extensions` field of the [merged]
24+
* Map, at each call to [merge].
2525
*/
2626
@ApolloInternal
27+
@Suppress("UNCHECKED_CAST")
2728
class DeferredJsonMerger {
2829
private val _merged: MutableJsonMap = mutableMapOf()
2930
val merged: JsonMap = _merged
3031

32+
/**
33+
* Map of identifiers to their corresponding DeferredFragmentIdentifier, found in `pending`.
34+
*/
35+
private val idsToDeferredFragmentIdentifiers = mutableMapOf<String, DeferredFragmentIdentifier>()
36+
3137
private val _mergedFragmentIds = mutableSetOf<DeferredFragmentIdentifier>()
3238
val mergedFragmentIds: Set<DeferredFragmentIdentifier> = _mergedFragmentIds
3339

@@ -47,11 +53,12 @@ class DeferredJsonMerger {
4753
return merge(payloadMap)
4854
}
4955

50-
@Suppress("UNCHECKED_CAST")
5156
fun merge(payload: JsonMap): JsonMap {
5257
if (merged.isEmpty()) {
53-
// Initial payload, no merging needed
54-
_merged += payload
58+
// Initial payload, no merging needed (strip some fields that should not appear in the final result)
59+
_merged += payload - "hasNext" - "pending"
60+
handlePending(payload)
61+
handleCompleted(payload)
5562
return merged
5663
}
5764

@@ -60,48 +67,68 @@ class DeferredJsonMerger {
6067
isEmptyPayload = true
6168
} else {
6269
isEmptyPayload = false
63-
val mergedErrors = mutableListOf<JsonMap>()
64-
val mergedExtensions = mutableListOf<JsonMap>()
6570
for (incrementalItem in incrementalList) {
66-
mergeData(incrementalItem)
67-
// Merge errors and extensions (if any) of the incremental list
68-
(incrementalItem["errors"] as? List<JsonMap>)?.let { mergedErrors += it }
69-
(incrementalItem["extensions"] as? JsonMap)?.let { mergedExtensions += it }
70-
}
71-
// Keep only this payload's errors and extensions, if any
72-
if (mergedErrors.isNotEmpty()) {
73-
_merged["errors"] = mergedErrors
74-
} else {
75-
_merged.remove("errors")
76-
}
77-
if (mergedExtensions.isNotEmpty()) {
78-
_merged["extensions"] = mapOf("incremental" to mergedExtensions)
79-
} else {
80-
_merged.remove("extensions")
71+
mergeIncrementalData(incrementalItem)
72+
// Merge errors (if any) of the incremental item
73+
(incrementalItem["errors"] as? List<JsonMap>)?.let { getOrPutMergedErrors() += it }
8174
}
8275
}
8376

8477
hasNext = payload["hasNext"] as Boolean? ?: false
8578

79+
handlePending(payload)
80+
handleCompleted(payload)
81+
82+
(payload["extensions"] as? JsonMap)?.let { getOrPutExtensions() += it }
83+
8684
return merged
8785
}
8886

89-
@Suppress("UNCHECKED_CAST")
90-
private fun mergeData(incrementalItem: JsonMap) {
91-
val data = incrementalItem["data"] as JsonMap?
92-
val path = incrementalItem["path"] as List<Any>
93-
val mergedData = merged["data"] as JsonMap
87+
private fun getOrPutMergedErrors() = _merged.getOrPut("errors") { mutableListOf<JsonMap>() } as MutableList<JsonMap>
9488

95-
// payloadData can be null if there are errors
96-
if (data != null) {
97-
val nodeToMergeInto = nodeAtPath(mergedData, path) as MutableJsonMap
98-
deepMerge(nodeToMergeInto, data)
89+
private fun getOrPutExtensions() = _merged.getOrPut("extensions") { mutableMapOf<String, Any?>() } as MutableJsonMap
9990

100-
_mergedFragmentIds += DeferredFragmentIdentifier(path = path, label = incrementalItem["label"] as String?)
91+
private fun handlePending(payload: JsonMap) {
92+
val pending = payload["pending"] as? List<JsonMap>
93+
if (pending != null) {
94+
for (pendingItem in pending) {
95+
val id = pendingItem["id"] as String
96+
val path = pendingItem["path"] as List<Any>
97+
val label = pendingItem["label"] as String?
98+
idsToDeferredFragmentIdentifiers[id] = DeferredFragmentIdentifier(path = path, label = label)
99+
}
101100
}
102101
}
103102

104-
@Suppress("UNCHECKED_CAST")
103+
private fun handleCompleted(payload: JsonMap) {
104+
val completed = payload["completed"] as? List<JsonMap>
105+
if (completed != null) {
106+
for (completedItem in completed) {
107+
val errors = completedItem["errors"] as? List<JsonMap>
108+
if (errors != null) {
109+
// Merge errors (if any) of the completed item
110+
getOrPutMergedErrors() += errors
111+
} else {
112+
// No errors: we have merged all the fields of the fragment so it can be parsed
113+
val id = completedItem["id"] as String
114+
val deferredFragmentIdentifier = idsToDeferredFragmentIdentifiers.remove(id)
115+
?: error("Id '$id' not found in pending results")
116+
_mergedFragmentIds += deferredFragmentIdentifier
117+
}
118+
}
119+
}
120+
}
121+
122+
private fun mergeIncrementalData(incrementalItem: JsonMap) {
123+
val id = incrementalItem["id"] as String? ?: error("No id found in incremental item")
124+
val data = incrementalItem["data"] as JsonMap? ?: error("No data found in incremental item")
125+
val subPath = incrementalItem["subPath"] as List<Any>? ?: emptyList()
126+
val path = (idsToDeferredFragmentIdentifiers[id]?.path ?: error("Id '$id' not found in pending results")) + subPath
127+
val mergedData = merged["data"] as JsonMap
128+
val nodeToMergeInto = nodeAtPath(mergedData, path) as MutableJsonMap
129+
deepMerge(nodeToMergeInto, data)
130+
}
131+
105132
private fun deepMerge(destination: MutableJsonMap, map: JsonMap) {
106133
for ((key, value) in map) {
107134
if (destination.containsKey(key) && destination[key] is MutableMap<*, *>) {
@@ -116,7 +143,6 @@ class DeferredJsonMerger {
116143
}
117144
}
118145

119-
@Suppress("UNCHECKED_CAST")
120146
private fun jsonToMap(json: BufferedSource): JsonMap = BufferedSourceJsonReader(json).readAny() as JsonMap
121147

122148

@@ -130,7 +156,6 @@ class DeferredJsonMerger {
130156
node = if (node is List<*>) {
131157
node[key as Int]
132158
} else {
133-
@Suppress("UNCHECKED_CAST")
134159
node as JsonMap
135160
node[key]
136161
}

0 commit comments

Comments
 (0)