@@ -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" )
2728class 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