Skip to content

Commit 36c848f

Browse files
authored
Make listItemsInParent public (#249)
1 parent 8657cf6 commit 36c848f

File tree

4 files changed

+218
-21
lines changed

4 files changed

+218
-21
lines changed

normalized-cache/api/normalized-cache.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ public final class com/apollographql/cache/normalized/api/CacheResolverKt {
383383
public static synthetic fun FieldPolicyCacheResolver$default (Lcom/apollographql/cache/normalized/api/CacheKey$Scope;ILjava/lang/Object;)Lcom/apollographql/cache/normalized/api/CacheResolver;
384384
public static final fun getFieldKey (Lcom/apollographql/cache/normalized/api/ResolverContext;)Ljava/lang/String;
385385
public static final fun getFieldPolicyCacheResolver ()Lcom/apollographql/cache/normalized/api/CacheResolver;
386+
public static final fun listItemsInParent (Lcom/apollographql/cache/normalized/api/ResolverContext;Ljava/lang/String;)Ljava/util/Map;
386387
}
387388

388389
public final class com/apollographql/cache/normalized/api/ConnectionEmbeddedFieldsProvider : com/apollographql/cache/normalized/api/EmbeddedFieldsProvider {

normalized-cache/api/normalized-cache.klib.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@ final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cach
642642
final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/receivedDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/receivedDate|[email protected](kotlin.String){}[0]
643643
final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/withDates(kotlin/String?, kotlin/String?): com.apollographql.cache.normalized.api/Record // com.apollographql.cache.normalized.api/withDates|[email protected](kotlin.String?;kotlin.String?){}[0]
644644
final fun (com.apollographql.cache.normalized.api/ResolverContext).com.apollographql.cache.normalized.api/getFieldKey(): kotlin/String // com.apollographql.cache.normalized.api/getFieldKey|getFieldKey@com.apollographql.cache.normalized.api.ResolverContext(){}[0]
645+
final fun (com.apollographql.cache.normalized.api/ResolverContext).com.apollographql.cache.normalized.api/listItemsInParent(kotlin/String): kotlin.collections/Map<kotlin/Any?, kotlin/Any?> // com.apollographql.cache.normalized.api/listItemsInParent|listItemsInParent@com.apollographql.cache.normalized.api.ResolverContext(kotlin.String){}[0]
645646
final fun (kotlin.collections/Collection<com.apollographql.cache.normalized.api/Record>?).com.apollographql.cache.normalized.api/dependentKeys(): kotlin.collections/Set<kotlin/String> // com.apollographql.cache.normalized.api/dependentKeys|[email protected]<com.apollographql.cache.normalized.api.Record>?(){}[0]
646647
final fun (kotlin.collections/Map<com.apollographql.cache.normalized.api/CacheKey, com.apollographql.cache.normalized.api/Record>).com.apollographql.cache.normalized/getReachableCacheKeys(): kotlin.collections/Set<com.apollographql.cache.normalized.api/CacheKey> // com.apollographql.cache.normalized/getReachableCacheKeys|[email protected]<com.apollographql.cache.normalized.api.CacheKey,com.apollographql.cache.normalized.api.Record>(){}[0]
647648
final fun <#A: com.apollographql.apollo.api/Executable.Data> (#A).com.apollographql.cache.normalized.api/withErrors(com.apollographql.apollo.api/Executable<#A>, kotlin.collections/List<com.apollographql.apollo.api/Error>?, com.apollographql.apollo.api/CustomScalarAdapters = ...): kotlin.collections/Map<kotlin/String, kotlin/Any?> // com.apollographql.cache.normalized.api/withErrors|withErrors@0:0(com.apollographql.apollo.api.Executable<0:0>;kotlin.collections.List<com.apollographql.apollo.api.Error>?;com.apollographql.apollo.api.CustomScalarAdapters){0§<com.apollographql.apollo.api.Executable.Data>}[0]

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,44 @@ fun ResolverContext.getFieldKey(): String {
132132
return fieldKeyGenerator.getFieldKey(FieldKeyContext(parentType, field, variables))
133133
}
134134

135+
/**
136+
* Returns the items in list fields in the parent matching the given name and key argument, keyed by the key argument value.
137+
*
138+
* For example, if `parent` contains:
139+
* ```
140+
* someList({"ids": ["aaa", "bbb", "ccc"]}): [ a, b, c ]
141+
* someList({"ids": ["ddd", "eee"], "someArg": 42}): [ d, e ]
142+
* otherField1: "value"
143+
* otherField2: 123
144+
* ```
145+
*
146+
* and `field.name` is `someList`, calling `listItemsInParent(context, "ids")` will return:
147+
* ```
148+
* "aaa" to a
149+
* "bbb" to b
150+
* "ccc" to c
151+
* "ddd" to d
152+
* "eee" to e
153+
* ```
154+
*
155+
* Note: this relies on the default format of the field keys as per [DefaultFieldKeyGenerator].
156+
*/
157+
fun ResolverContext.listItemsInParent(keyArg: String): Map<Any?, Any?> {
158+
val keyPrefix = "${this.field.name}("
159+
val filteredParent = this.parent.filterKeys { it.startsWith(keyPrefix) && it.contains("\"$keyArg\":") }
160+
val items: Map<Any?, Any?> = filteredParent.map { (k, v) ->
161+
val argumentsText = k.removePrefix(keyPrefix).removeSuffix(")")
162+
val argumentsMap = Buffer().writeUtf8(argumentsText).jsonReader().buffer().root as Map<*, *>
163+
val keyValues = argumentsMap[keyArg] as List<*>
164+
keyValues.mapIndexed { index, id ->
165+
id to (v as List<*>)[index]
166+
}.toMap()
167+
}.fold(emptyMap()) { acc, map ->
168+
acc + map
169+
}
170+
return items
171+
}
172+
135173
/**
136174
* A cache resolver that uses the parent to resolve fields.
137175
*/
@@ -275,8 +313,8 @@ fun FieldPolicyCacheResolver(
275313
// Only support single key argument which is a flat list
276314
if (keyArgsValues.size == 1) {
277315
val keyArgsValue = keyArgsValues.first() as? List<*>
278-
if (keyArgsValue != null && keyArgsValue.firstOrNull() !is List<*>) {
279-
val listItemsInParent: Map<Any?, Any?> = listItemsInParent(context, keyArgs.keys.first())
316+
if (keyArgsValue != null) {
317+
val listItemsInParent: Map<Any?, Any?> = context.listItemsInParent(keyArgs.keys.first())
280318
return keyArgsValue.mapIndexed { index, value ->
281319
if (listItemsInParent.containsKey(value)) {
282320
listItemsInParent[value]?.let {
@@ -323,23 +361,4 @@ fun FieldPolicyCacheResolver(
323361
}
324362
return builder.build()
325363
}
326-
327-
private fun listItemsInParent(
328-
context: ResolverContext,
329-
keyArg: String,
330-
): Map<Any?, Any?> {
331-
val keyPrefix = "${context.field.name}("
332-
val filteredParent = context.parent.filterKeys { it.startsWith(keyPrefix) && it.contains("\"$keyArg\":") }
333-
val items: Map<Any?, Any?> = filteredParent.map { (k, v) ->
334-
val argumentsText = k.removePrefix(keyPrefix).removeSuffix(")")
335-
val argumentsMap = Buffer().writeUtf8(argumentsText).jsonReader().buffer().root as Map<*, *>
336-
val keyValues = argumentsMap[keyArg] as List<*>
337-
keyValues.mapIndexed { index, id ->
338-
id to (v as List<*>)[index]
339-
}.toMap()
340-
}.fold(emptyMap()) { acc, map ->
341-
acc + map
342-
}
343-
return items
344-
}
345364
}

tests/cache-options/src/commonTest/kotlin/test/CacheOptionsTest.kt

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,182 @@ class CacheOptionsTest {
434434
}
435435
}
436436

437+
@Test
438+
fun listsWithNullMemory() = runTest(before = { setUp() }, after = { tearDown() }) {
439+
listsWithNull(memoryCacheManager)
440+
}
441+
442+
@Test
443+
fun listsWithNullSql() = runTest(before = { setUp() }, after = { tearDown() }) {
444+
listsWithNull(sqlCacheManager)
445+
}
446+
447+
@Test
448+
fun listsWithNullMemoryThenSql() = runTest(before = { setUp() }, after = { tearDown() }) {
449+
listsWithNull(memoryThenSqlCacheManager)
450+
}
451+
452+
private suspend fun listsWithNull(cacheManager: CacheManager) {
453+
mockServer.enqueueString(
454+
// language=JSON
455+
"""
456+
{
457+
"data": {
458+
"users": [
459+
{
460+
"__typename": "User",
461+
"id": "1",
462+
"firstName": "John",
463+
"lastName": "Smith",
464+
"email": "[email protected]"
465+
},
466+
{
467+
"__typename": "User",
468+
"id": "2",
469+
"firstName": "Jane",
470+
"lastName": "Doe",
471+
"email": "[email protected]"
472+
},
473+
null
474+
]
475+
}
476+
}
477+
"""
478+
)
479+
mockServer.enqueueString(
480+
// language=JSON
481+
"""
482+
{
483+
"data": {
484+
"users": [
485+
{
486+
"__typename": "User",
487+
"id": "4",
488+
"firstName": "Kevin",
489+
"lastName": "Jones",
490+
"email": "[email protected]"
491+
},
492+
{
493+
"__typename": "User",
494+
"id": "5",
495+
"firstName": "Alice",
496+
"lastName": "Johnson",
497+
"email": "[email protected]"
498+
},
499+
null
500+
]
501+
}
502+
}
503+
"""
504+
)
505+
506+
ApolloClient.Builder()
507+
.serverUrl(mockServer.url())
508+
.cacheManager(cacheManager)
509+
.build()
510+
.use { apolloClient ->
511+
val networkResult1 = apolloClient.query(UsersQuery(listOf("1", "2", "3")))
512+
.fetchPolicy(FetchPolicy.NetworkOnly)
513+
.execute()
514+
assertEquals(
515+
UsersQuery.Data(
516+
users = listOf(
517+
UsersQuery.User(
518+
__typename = "User",
519+
id = "1",
520+
firstName = "John",
521+
lastName = "Smith",
522+
email = "[email protected]",
523+
),
524+
UsersQuery.User(
525+
__typename = "User",
526+
id = "2",
527+
firstName = "Jane",
528+
lastName = "Doe",
529+
email = "[email protected]",
530+
),
531+
null,
532+
)
533+
),
534+
networkResult1.data
535+
)
536+
assertNull(networkResult1.errors)
537+
538+
val networkResult2 = apolloClient.query(UsersQuery(listOf("4", "5", "6")))
539+
.fetchPolicy(FetchPolicy.NetworkOnly)
540+
.execute()
541+
assertEquals(
542+
UsersQuery.Data(
543+
users = listOf(
544+
UsersQuery.User(
545+
__typename = "User",
546+
id = "4",
547+
firstName = "Kevin",
548+
lastName = "Jones",
549+
email = "[email protected]",
550+
),
551+
UsersQuery.User(
552+
__typename = "User",
553+
id = "5",
554+
firstName = "Alice",
555+
lastName = "Johnson",
556+
email = "[email protected]",
557+
),
558+
null,
559+
)
560+
),
561+
networkResult2.data
562+
)
563+
assertNull(networkResult2.errors)
564+
565+
566+
val cacheResult = apolloClient.query(UsersQuery(listOf("1", "2", "3", "4", "5", "6")))
567+
.fetchPolicy(FetchPolicy.CacheOnly)
568+
.serverErrorsAsCacheMisses(false)
569+
.throwOnCacheMiss(false)
570+
.execute()
571+
assertEquals(
572+
UsersQuery.Data(
573+
users = listOf(
574+
UsersQuery.User(
575+
__typename = "User",
576+
id = "1",
577+
firstName = "John",
578+
lastName = "Smith",
579+
email = "[email protected]",
580+
),
581+
UsersQuery.User(
582+
__typename = "User",
583+
id = "2",
584+
firstName = "Jane",
585+
lastName = "Doe",
586+
email = "[email protected]",
587+
),
588+
null,
589+
UsersQuery.User(
590+
__typename = "User",
591+
id = "4",
592+
firstName = "Kevin",
593+
lastName = "Jones",
594+
email = "[email protected]",
595+
),
596+
UsersQuery.User(
597+
__typename = "User",
598+
id = "5",
599+
firstName = "Alice",
600+
lastName = "Johnson",
601+
email = "[email protected]",
602+
),
603+
null,
604+
)
605+
),
606+
cacheResult.data,
607+
)
608+
assertNull(cacheResult.errors)
609+
}
610+
}
611+
612+
437613
@Test
438614
fun listsFromDifferentFieldsMemory() = runTest(before = { setUp() }, after = { tearDown() }) {
439615
listsFromDifferentFields(memoryCacheManager)

0 commit comments

Comments
 (0)