Skip to content

Commit 9cf23bc

Browse files
committed
Add allowPartialResults and allowCachedErrors
1 parent f706e96 commit 9cf23bc

5 files changed

Lines changed: 82 additions & 62 deletions

File tree

normalized-cache/api/normalized-cache.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,12 @@ public final class com/apollographql/cache/normalized/NormalizedCache {
179179
public static final fun -logCacheMisses (Lcom/apollographql/apollo/ApolloClient$Builder;Lkotlin/jvm/functions/Function1;)Lcom/apollographql/apollo/ApolloClient$Builder;
180180
public static synthetic fun -logCacheMisses$default (Lcom/apollographql/apollo/ApolloClient$Builder;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder;
181181
public static final fun addCacheHeader (Lcom/apollographql/apollo/api/MutableExecutionOptions;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
182+
public static final fun allowCachedErrors (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object;
183+
public static final fun allowPartialResults (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object;
182184
public static final fun cacheHeaders (Lcom/apollographql/apollo/api/ApolloResponse$Builder;Lcom/apollographql/cache/normalized/api/CacheHeaders;)Lcom/apollographql/apollo/api/ApolloResponse$Builder;
183185
public static final fun cacheHeaders (Lcom/apollographql/apollo/api/MutableExecutionOptions;Lcom/apollographql/cache/normalized/api/CacheHeaders;)Ljava/lang/Object;
184186
public static final fun cacheManager (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/cache/normalized/CacheManager;Z)Lcom/apollographql/apollo/ApolloClient$Builder;
185187
public static synthetic fun cacheManager$default (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/cache/normalized/CacheManager;ZILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder;
186-
public static final fun cachePolicyResponseMapper (Lcom/apollographql/apollo/api/MutableExecutionOptions;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
187188
public static final fun clock (Lcom/apollographql/apollo/api/MutableExecutionOptions;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
188189
public static final fun configureApolloClientBuilder2 (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/cache/normalized/api/NormalizedCacheFactory;)Lcom/apollographql/apollo/ApolloClient$Builder;
189190
public static final fun configureApolloClientBuilder2 (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/cache/normalized/api/NormalizedCacheFactory;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;)Lcom/apollographql/apollo/ApolloClient$Builder;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,9 @@ final fun <#A: com.apollographql.apollo.api/Operation.Data> (com.apollographql.a
632632
final fun <#A: com.apollographql.apollo.api/Query.Data> (com.apollographql.apollo/ApolloCall<#A>).com.apollographql.cache.normalized/watch(#A?): kotlinx.coroutines.flow/Flow<com.apollographql.apollo.api/ApolloResponse<#A>> // com.apollographql.cache.normalized/watch|watch@com.apollographql.apollo.ApolloCall<0:0>(0:0?){0§<com.apollographql.apollo.api.Query.Data>}[0]
633633
final fun <#A: com.apollographql.apollo.api/Query.Data> (com.apollographql.apollo/ApolloCall<#A>).com.apollographql.cache.normalized/watch(): kotlinx.coroutines.flow/Flow<com.apollographql.apollo.api/ApolloResponse<#A>> // com.apollographql.cache.normalized/watch|watch@com.apollographql.apollo.ApolloCall<0:0>(){0§<com.apollographql.apollo.api.Query.Data>}[0]
634634
final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/addCacheHeader(kotlin/String, kotlin/String): #A // com.apollographql.cache.normalized/addCacheHeader|addCacheHeader@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.String;kotlin.String){0§<kotlin.Any?>}[0]
635+
final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/allowCachedErrors(kotlin/Boolean): #A // com.apollographql.cache.normalized/allowCachedErrors|allowCachedErrors@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§<kotlin.Any?>}[0]
636+
final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/allowPartialResults(kotlin/Boolean): #A // com.apollographql.cache.normalized/allowPartialResults|allowPartialResults@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§<kotlin.Any?>}[0]
635637
final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/cacheHeaders(com.apollographql.cache.normalized.api/CacheHeaders): #A // com.apollographql.cache.normalized/cacheHeaders|cacheHeaders@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(com.apollographql.cache.normalized.api.CacheHeaders){0§<kotlin.Any?>}[0]
636-
final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/cachePolicyResponseMapper(kotlin/Function2<com.apollographql.apollo.api/ApolloResponse<*>, com.apollographql.cache.normalized/FetchPolicy, com.apollographql.apollo.api/ApolloResponse<*>>): #A // com.apollographql.cache.normalized/cachePolicyResponseMapper|cachePolicyResponseMapper@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Function2<com.apollographql.apollo.api.ApolloResponse<*>,com.apollographql.cache.normalized.FetchPolicy,com.apollographql.apollo.api.ApolloResponse<*>>){0§<kotlin.Any?>}[0]
637638
final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/clock(kotlin/Function0<kotlin/Long>): #A // com.apollographql.cache.normalized/clock|clock@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Function0<kotlin.Long>){0§<kotlin.Any?>}[0]
638639
final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/doNotStore(kotlin/Boolean): #A // com.apollographql.cache.normalized/doNotStore|doNotStore@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§<kotlin.Any?>}[0]
639640
final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/errorsReplaceCachedValues(kotlin/Boolean): #A // com.apollographql.cache.normalized/errorsReplaceCachedValues|errorsReplaceCachedValues@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§<kotlin.Any?>}[0]

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -754,26 +754,28 @@ fun <T> MutableExecutionOptions<T>.clock(clock: () -> Long): T {
754754
return this as T
755755
}
756756

757-
internal class CachePolicyResponseMapperContext(val value: (ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*>) :
758-
ExecutionContext.Element {
757+
internal class AllowPartialResults(val value: Boolean) : ExecutionContext.Element {
759758
override val key: ExecutionContext.Key<*>
760759
get() = Key
761760

762-
companion object Key : ExecutionContext.Key<CachePolicyResponseMapperContext>
761+
companion object Key : ExecutionContext.Key<AllowPartialResults>
763762
}
764763

765-
private val defaultCachePolicyResponseMapper: (ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*> = { response, _ ->
766-
response.errorsAsException()
767-
}
764+
internal val ExecutionOptions.allowPartialResults: Boolean
765+
get() = executionContext[AllowPartialResults]?.value ?: false
768766

769-
internal val ExecutionOptions.cachePolicyResponseMapper: ((ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*>)
770-
get() = executionContext[CachePolicyResponseMapperContext]?.value ?: defaultCachePolicyResponseMapper
767+
fun <T> MutableExecutionOptions<T>.allowPartialResults(allowPartialResults: Boolean): T =
768+
addExecutionContext(AllowPartialResults(allowPartialResults))
771769

772-
/**
773-
* Sets a response mapper that will be used inside the built-in fetch policies interceptors to transform the responses from the cache.
774-
*/
775-
fun <T> MutableExecutionOptions<T>.cachePolicyResponseMapper(mapper: (ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*>): T {
776-
addExecutionContext(CachePolicyResponseMapperContext(mapper))
777-
@Suppress("UNCHECKED_CAST")
778-
return this as T
770+
internal class AllowCachedErrors(val value: Boolean) : ExecutionContext.Element {
771+
override val key: ExecutionContext.Key<*>
772+
get() = Key
773+
774+
companion object Key : ExecutionContext.Key<AllowCachedErrors>
779775
}
776+
777+
internal val ExecutionOptions.allowCachedErrors: Boolean
778+
get() = executionContext[AllowCachedErrors]?.value ?: false
779+
780+
fun <T> MutableExecutionOptions<T>.allowCachedErrors(allowCachedErrors: Boolean): T =
781+
addExecutionContext(AllowCachedErrors(allowCachedErrors))

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/FetchPolicyInterceptors.kt

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ val CacheOnlyInterceptor = object : ApolloInterceptor {
3434
.fetchFromCache(true)
3535
.build()
3636
).map {
37-
@Suppress("UNCHECKED_CAST")
38-
request.cachePolicyResponseMapper(it, FetchPolicy.CacheOnly) as ApolloResponse<D>
37+
it.errorsAsException(allowPartialResults = request.allowPartialResults, allowCachedErrors = request.allowCachedErrors)
3938
}
4039
}
4140
}
@@ -60,10 +59,7 @@ val CacheFirstInterceptor = object : ApolloInterceptor {
6059
.newBuilder()
6160
.fetchFromCache(true)
6261
.build()
63-
).single().let {
64-
@Suppress("UNCHECKED_CAST")
65-
request.cachePolicyResponseMapper(it, FetchPolicy.CacheFirst) as ApolloResponse<D>
66-
}
62+
).single().errorsAsException(allowPartialResults = request.allowPartialResults, allowCachedErrors = request.allowCachedErrors)
6763
emit(cacheResponse.newBuilder().isLast(cacheResponse.exception == null).build())
6864
if (cacheResponse.exception == null) {
6965
return@flow
@@ -109,10 +105,7 @@ val NetworkFirstInterceptor = object : ApolloInterceptor {
109105
.newBuilder()
110106
.fetchFromCache(true)
111107
.build()
112-
).single().let {
113-
@Suppress("UNCHECKED_CAST")
114-
request.cachePolicyResponseMapper(it, FetchPolicy.NetworkFirst) as ApolloResponse<D>
115-
}
108+
).single().errorsAsException(allowPartialResults = request.allowPartialResults, allowCachedErrors = request.allowCachedErrors)
116109
emit(cacheResponse)
117110
}
118111
}
@@ -129,10 +122,7 @@ val CacheAndNetworkInterceptor = object : ApolloInterceptor {
129122
.newBuilder()
130123
.fetchFromCache(true)
131124
.build()
132-
).single().let {
133-
@Suppress("UNCHECKED_CAST")
134-
request.cachePolicyResponseMapper(it, FetchPolicy.CacheAndNetwork) as ApolloResponse<D>
135-
}
125+
).single().errorsAsException(allowPartialResults = request.allowPartialResults, allowCachedErrors = request.allowCachedErrors)
136126

137127
emit(cacheResponse.newBuilder().isLast(false).build())
138128

@@ -162,6 +152,57 @@ fun <D : Operation.Data> ApolloResponse<D>.errorsAsException(): ApolloResponse<D
162152
}
163153
}
164154

155+
private fun <D : Operation.Data> ApolloResponse<D>.errorsAsException(
156+
allowPartialResults: Boolean,
157+
allowCachedErrors: Boolean,
158+
): ApolloResponse<D> {
159+
return if (allowPartialResults && allowCachedErrors) {
160+
this
161+
} else {
162+
val cacheMissException = if (allowPartialResults) {
163+
null
164+
} else {
165+
errors.orEmpty().mapNotNull { it.cacheMissException }.reduceOrNull { acc, e ->
166+
acc.addSuppressed(e)
167+
acc
168+
}
169+
}
170+
val cachedErrorException = if (allowCachedErrors) {
171+
null
172+
} else {
173+
errors.orEmpty().mapNotNull { if (it.cacheMissException != null) null else ApolloGraphQLException(it) }.reduceOrNull { acc, e ->
174+
acc.addSuppressed(e)
175+
acc
176+
}
177+
}
178+
when {
179+
cacheMissException != null -> {
180+
newBuilder()
181+
.exception(cacheMissException.apply {
182+
if (cachedErrorException != null) {
183+
addSuppressed(cachedErrorException)
184+
}
185+
})
186+
.data(null)
187+
.errors(null)
188+
.build()
189+
}
190+
191+
cachedErrorException != null -> {
192+
newBuilder()
193+
.exception(cachedErrorException)
194+
.data(null)
195+
.errors(null)
196+
.build()
197+
}
198+
199+
else -> {
200+
this
201+
}
202+
}
203+
}
204+
}
205+
165206
internal object FetchPolicyRouterInterceptor : ApolloInterceptor {
166207
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
167208
if (request.operation !is Query) {

tests/cache-policy-response-mapper/src/commonTest/kotlin/test/CachePolicyResponseMapperTest.kt

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package test
22

33
import com.apollographql.apollo.ApolloClient
4-
import com.apollographql.apollo.api.ApolloResponse
54
import com.apollographql.apollo.api.Error
65
import com.apollographql.cache.normalized.CacheManager
76
import com.apollographql.cache.normalized.FetchPolicy
7+
import com.apollographql.cache.normalized.allowCachedErrors
8+
import com.apollographql.cache.normalized.allowPartialResults
89
import com.apollographql.cache.normalized.cacheManager
9-
import com.apollographql.cache.normalized.cacheMissException
10-
import com.apollographql.cache.normalized.cachePolicyResponseMapper
1110
import com.apollographql.cache.normalized.fetchPolicy
1211
import com.apollographql.cache.normalized.memory.MemoryCacheFactory
1312
import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory
@@ -79,7 +78,6 @@ class StoreErrorsTest {
7978
ApolloClient.Builder()
8079
.serverUrl(mockServer.url())
8180
.cacheManager(cacheManager)
82-
.cachePolicyResponseMapper(cachePolicyResponseMapper)
8381
.build()
8482
.use { apolloClient ->
8583
val networkResult = apolloClient.query(MeWithNickNameQuery())
@@ -105,6 +103,7 @@ class StoreErrorsTest {
105103
)
106104

107105
val cacheResult = apolloClient.query(MeWithNickNameQuery())
106+
.allowCachedErrors(true)
108107
.execute()
109108
assertEquals(
110109
networkResult.data,
@@ -168,7 +167,6 @@ class StoreErrorsTest {
168167
ApolloClient.Builder()
169168
.serverUrl(mockServer.url())
170169
.cacheManager(cacheManager)
171-
.cachePolicyResponseMapper(cachePolicyResponseMapper)
172170
.build()
173171
.use { apolloClient ->
174172
val networkResult = apolloClient.query(UsersQuery(listOf("1", "2", "3")))
@@ -205,6 +203,7 @@ class StoreErrorsTest {
205203

206204
val cacheResult = apolloClient.query(UsersQuery(listOf("1", "2", "3")))
207205
.fetchPolicy(FetchPolicy.CacheOnly)
206+
.allowCachedErrors(true)
208207
.execute()
209208
assertEquals(
210209
networkResult.data,
@@ -258,7 +257,6 @@ class StoreErrorsTest {
258257
ApolloClient.Builder()
259258
.serverUrl(mockServer.url())
260259
.cacheManager(cacheManager)
261-
.cachePolicyResponseMapper(cachePolicyResponseMapper)
262260
.build()
263261
.use { apolloClient ->
264262
val networkResult = apolloClient.query(MeWithNickNameQuery())
@@ -285,6 +283,8 @@ class StoreErrorsTest {
285283

286284
val cacheResult = apolloClient.query(MeWithNickNameAndProjectQuery())
287285
.fetchPolicy(FetchPolicy.CacheOnly)
286+
.allowPartialResults(true)
287+
.allowCachedErrors(true)
288288
.execute()
289289
assertEquals(
290290
MeWithNickNameAndProjectQuery.Data(
@@ -309,28 +309,3 @@ class StoreErrorsTest {
309309
}
310310

311311
}
312-
313-
private val cachePolicyResponseMapper: (ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*> = { response, fetchPolicy ->
314-
when (fetchPolicy) {
315-
FetchPolicy.CacheFirst -> {
316-
val cacheMissException = response.errors.orEmpty().mapNotNull { it.cacheMissException }.reduceOrNull { acc, e ->
317-
acc.addSuppressed(e)
318-
acc
319-
}
320-
if (cacheMissException != null) {
321-
response.newBuilder()
322-
.exception(cacheMissException)
323-
.data(null)
324-
.errors(null)
325-
.build()
326-
} else {
327-
response
328-
}
329-
}
330-
331-
FetchPolicy.CacheOnly -> response
332-
FetchPolicy.NetworkFirst -> response
333-
FetchPolicy.NetworkOnly -> response
334-
FetchPolicy.CacheAndNetwork -> response
335-
}
336-
}

0 commit comments

Comments
 (0)