@@ -15,19 +15,25 @@ private typealias MutableJsonMap = MutableMap<String, Any?>
15
15
* Each call to [merge] will merge the given chunk into the [merged] Map, and will also update the [mergedFragmentIds] Set with the
16
16
* value of its `path` and `label` field.
17
17
*
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).
20
20
*
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
24
22
* [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].
25
25
*/
26
26
@ApolloInternal
27
+ @Suppress(" UNCHECKED_CAST" )
27
28
class DeferredJsonMerger {
28
29
private val _merged : MutableJsonMap = mutableMapOf ()
29
30
val merged: JsonMap = _merged
30
31
32
+ /* *
33
+ * Map of identifiers to their corresponding DeferredFragmentIdentifier, found in `pending`.
34
+ */
35
+ private val idsToDeferredFragmentIdentifiers = mutableMapOf<String , DeferredFragmentIdentifier >()
36
+
31
37
private val _mergedFragmentIds = mutableSetOf<DeferredFragmentIdentifier >()
32
38
val mergedFragmentIds: Set <DeferredFragmentIdentifier > = _mergedFragmentIds
33
39
@@ -47,11 +53,12 @@ class DeferredJsonMerger {
47
53
return merge(payloadMap)
48
54
}
49
55
50
- @Suppress(" UNCHECKED_CAST" )
51
56
fun merge (payload : JsonMap ): JsonMap {
52
57
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)
55
62
return merged
56
63
}
57
64
@@ -60,48 +67,68 @@ class DeferredJsonMerger {
60
67
isEmptyPayload = true
61
68
} else {
62
69
isEmptyPayload = false
63
- val mergedErrors = mutableListOf<JsonMap >()
64
- val mergedExtensions = mutableListOf<JsonMap >()
65
70
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 }
81
74
}
82
75
}
83
76
84
77
hasNext = payload[" hasNext" ] as Boolean? ? : false
85
78
79
+ handlePending(payload)
80
+ handleCompleted(payload)
81
+
82
+ (payload[" extensions" ] as ? JsonMap )?.let { getOrPutExtensions() + = it }
83
+
86
84
return merged
87
85
}
88
86
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 >
94
88
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
99
90
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
+ }
101
100
}
102
101
}
103
102
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
+
105
132
private fun deepMerge (destination : MutableJsonMap , map : JsonMap ) {
106
133
for ((key, value) in map) {
107
134
if (destination.containsKey(key) && destination[key] is MutableMap <* , * >) {
@@ -116,7 +143,6 @@ class DeferredJsonMerger {
116
143
}
117
144
}
118
145
119
- @Suppress(" UNCHECKED_CAST" )
120
146
private fun jsonToMap (json : BufferedSource ): JsonMap = BufferedSourceJsonReader (json).readAny() as JsonMap
121
147
122
148
@@ -130,7 +156,6 @@ class DeferredJsonMerger {
130
156
node = if (node is List <* >) {
131
157
node[key as Int ]
132
158
} else {
133
- @Suppress(" UNCHECKED_CAST" )
134
159
node as JsonMap
135
160
node[key]
136
161
}
0 commit comments