Skip to content

Commit 4359ff9

Browse files
committed
Each option needs its own context element, because they can be set on the client + the request
1 parent c2a1286 commit 4359ff9

4 files changed

Lines changed: 195 additions & 70 deletions

File tree

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,28 @@ internal data class CacheOptionsImpl(
1616
var allowCachedErrors: Boolean = false,
1717
) : CacheOptions {
1818
override fun noCache(noCache: Boolean) {
19-
if (noCache && onlyIfCached) error("Apollo: noCache and onlyIfCached are mutually exclusive")
19+
if (noCache) {
20+
// noCache and onlyIfCached are mutually exclusive
21+
onlyIfCached = false
22+
}
2023
this.noCache = noCache
2124
}
2225

2326
override fun onlyIfCached(onlyIfCached: Boolean) {
24-
if (onlyIfCached && noCache) error("Apollo: noCache and onlyIfCached are mutually exclusive")
25-
if (onlyIfCached && reload) error("Apollo: onlyIfCached and reload are mutually exclusive")
27+
if (onlyIfCached) {
28+
// noCache and onlyIfCached are mutually exclusive
29+
noCache = false
30+
// reload and onlyIfCached are mutually exclusive
31+
reload = false
32+
}
2633
this.onlyIfCached = onlyIfCached
2734
}
2835

2936
override fun reload(reload: Boolean) {
30-
if (reload && onlyIfCached) error("Apollo: onlyIfCached and reload are mutually exclusive")
37+
if (reload) {
38+
// onlyIfCached and reload are mutually exclusive
39+
onlyIfCached = false
40+
}
3141
this.reload = reload
3242
}
3343

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

Lines changed: 170 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,11 @@ fun <D : Query.Data> ApolloCall<D>.watch(): Flow<ApolloResponse<D>> {
257257

258258

259259
copy().fetchPolicyInterceptor(refetchPolicyInterceptor)
260-
.fetchCacheOptions(refetchCacheOptions)
260+
.noCache(refetchNoCache)
261+
.onlyIfCached(refetchOnlyIfCached)
262+
.reload(refetchReload)
263+
.allowCachedPartialResults(refetchAllowCachedPartialResults)
264+
.allowCachedErrors(refetchAllowCachedErrors)
261265
.watchInternal(response?.data)
262266
.collect {
263267
if (it.exception === WatcherSentinel) {
@@ -300,28 +304,48 @@ val ApolloClient.apolloStore: ApolloStore
300304
* Sets the initial [FetchPolicy]
301305
* This only has effects for queries. Mutations and subscriptions always use the network only.
302306
*/
303-
@Deprecated("Use noCache(), onlyIfCached() or reload() instead")
304-
@Suppress("DEPRECATION")
307+
@Deprecated("Use noCache(), onlyIfCached() or reload() instead. For NetworkFirst, use fetchPolicyInterceptor(NetworkFirstInterceptor) instead")
308+
@Suppress("DEPRECATION", "UNCHECKED_CAST")
305309
fun <T> MutableExecutionOptions<T>.fetchPolicy(fetchPolicy: FetchPolicy): T {
306-
return if (fetchPolicy == FetchPolicy.NetworkFirst) {
307-
// NetworkFirst is deprecated but should still work
308-
fetchPolicyInterceptor(NetworkFirstInterceptor)
309-
} else {
310-
addExecutionContext(FetchCacheOptionsContext(cacheOptionsFor(fetchPolicy)))
310+
// Reset first
311+
onlyIfCached(false)
312+
noCache(false)
313+
reload(false)
314+
return when (fetchPolicy) {
315+
FetchPolicy.NetworkFirst -> {
316+
// NetworkFirst is deprecated but should still work
317+
fetchPolicyInterceptor(NetworkFirstInterceptor)
318+
}
319+
320+
FetchPolicy.CacheOnly -> onlyIfCached(true)
321+
FetchPolicy.NetworkOnly -> noCache(true)
322+
FetchPolicy.CacheFirst -> this as T
323+
FetchPolicy.CacheAndNetwork -> reload(true)
311324
}
312325
}
313326

314327
/**
315328
* Sets the [FetchPolicy] used when watching queries and a cache change has been published
316329
*/
317-
@Deprecated("Use refetchCacheOptions() instead")
318-
@Suppress("DEPRECATION")
330+
@Deprecated("Use refetchCacheOptions() instead. For NetworkFirst, use refetchPolicyInterceptor(NetworkFirstInterceptor) instead")
331+
@Suppress("DEPRECATION", "UNCHECKED_CAST")
319332
fun <T> MutableExecutionOptions<T>.refetchPolicy(@Suppress("DEPRECATION") fetchPolicy: FetchPolicy): T {
320-
return if (fetchPolicy == FetchPolicy.NetworkFirst) {
321-
// NetworkFirst is deprecated but should still work
322-
refetchPolicyInterceptor(NetworkFirstInterceptor)
323-
} else {
324-
addExecutionContext(RefetchCacheOptionsContext(cacheOptionsFor(fetchPolicy)))
333+
// Reset first
334+
refetchCacheOptions {
335+
onlyIfCached(true)
336+
noCache(false)
337+
reload(false)
338+
}
339+
return when (fetchPolicy) {
340+
FetchPolicy.NetworkFirst -> {
341+
// NetworkFirst is deprecated but should still work
342+
refetchPolicyInterceptor(NetworkFirstInterceptor)
343+
}
344+
345+
FetchPolicy.CacheOnly -> refetchCacheOptions { onlyIfCached(true) }
346+
FetchPolicy.NetworkOnly -> refetchCacheOptions { noCache(true) }
347+
FetchPolicy.CacheFirst -> this as T
348+
FetchPolicy.CacheAndNetwork -> refetchCacheOptions { reload(true) }
325349
}
326350
}
327351

@@ -340,15 +364,6 @@ fun <T> MutableExecutionOptions<T>.refetchPolicyInterceptor(interceptor: ApolloI
340364
RefetchPolicyContext(interceptor)
341365
)
342366

343-
@Suppress("DEPRECATION")
344-
private fun cacheOptionsFor(fetchPolicy: FetchPolicy) = when (fetchPolicy) {
345-
FetchPolicy.CacheOnly -> CacheOptionsImpl(onlyIfCached = true)
346-
FetchPolicy.NetworkOnly -> CacheOptionsImpl(noCache = true)
347-
FetchPolicy.CacheFirst -> CacheOptionsImpl()
348-
FetchPolicy.NetworkFirst -> CacheOptionsImpl()
349-
FetchPolicy.CacheAndNetwork -> CacheOptionsImpl(reload = true)
350-
}
351-
352367
/**
353368
* @param doNotStore Whether to store the response in cache.
354369
*
@@ -727,9 +742,9 @@ internal class ErrorsReplaceCachedValuesContext(val value: Boolean) : ExecutionC
727742
companion object Key : ExecutionContext.Key<ErrorsReplaceCachedValuesContext>
728743
}
729744

730-
fun <D : Operation.Data> ApolloRequest.Builder<D>.fetchFromCache(fetchFromCache: Boolean) = apply {
745+
fun <D : Operation.Data> ApolloRequest.Builder<D>.fetchFromCache(fetchFromCache: Boolean) =
731746
addExecutionContext(FetchFromCacheContext(fetchFromCache))
732-
}
747+
733748

734749
val <D : Operation.Data> ApolloRequest<D>.fetchFromCache
735750
get() = executionContext[FetchFromCacheContext]?.value ?: false
@@ -761,54 +776,155 @@ internal val ExecutionOptions.clock: (() -> Long)
761776
*
762777
* @param clock returns the current time in milliseconds since the epoch.
763778
*/
764-
fun <T> MutableExecutionOptions<T>.clock(clock: () -> Long): T {
765-
addExecutionContext(ClockContext(clock))
766-
@Suppress("UNCHECKED_CAST")
767-
return this as T
779+
fun <T> MutableExecutionOptions<T>.clock(clock: () -> Long): T = addExecutionContext(ClockContext(clock))
780+
781+
782+
internal class FetchAllowCachedPartialResultsContext(val value: Boolean) : ExecutionContext.Element {
783+
override val key: ExecutionContext.Key<*>
784+
get() = Key
785+
786+
companion object Key : ExecutionContext.Key<FetchAllowCachedPartialResultsContext>
768787
}
769788

770-
internal class FetchCacheOptionsContext(val value: CacheOptionsImpl) : ExecutionContext.Element {
789+
internal val ExecutionOptions.allowCachedPartialResults: Boolean
790+
get() = executionContext[FetchAllowCachedPartialResultsContext]?.value ?: false
791+
792+
fun <T> MutableExecutionOptions<T>.allowCachedPartialResults(allowCachedPartialResults: Boolean): T =
793+
addExecutionContext(FetchAllowCachedPartialResultsContext(allowCachedPartialResults))
794+
795+
796+
internal class FetchAllowCachedErrorsContext(val value: Boolean) : ExecutionContext.Element {
771797
override val key: ExecutionContext.Key<*>
772798
get() = Key
773799

774-
companion object Key : ExecutionContext.Key<FetchCacheOptionsContext>
800+
companion object Key : ExecutionContext.Key<FetchAllowCachedErrorsContext>
775801
}
776802

777-
internal val ExecutionOptions.fetchCacheOptions: CacheOptionsImpl
778-
get() = executionContext[FetchCacheOptionsContext]?.value ?: CacheOptionsImpl()
803+
internal val ExecutionOptions.allowCachedErrors: Boolean
804+
get() = executionContext[FetchAllowCachedErrorsContext]?.value ?: false
779805

780-
internal fun <T> MutableExecutionOptions<T>.fetchCacheOptions(fetchCacheOptions: CacheOptionsImpl): T {
781-
addExecutionContext(FetchCacheOptionsContext(fetchCacheOptions))
782-
@Suppress("UNCHECKED_CAST")
783-
return this as T
806+
fun <T> MutableExecutionOptions<T>.allowCachedErrors(allowCachedErrors: Boolean): T =
807+
addExecutionContext(FetchAllowCachedErrorsContext(allowCachedErrors))
808+
809+
810+
internal class FetchNoCacheContext(val value: Boolean) : ExecutionContext.Element {
811+
override val key: ExecutionContext.Key<*>
812+
get() = Key
813+
814+
companion object Key : ExecutionContext.Key<FetchNoCacheContext>
784815
}
785816

817+
internal val ExecutionOptions.noCache: Boolean
818+
get() = executionContext[FetchNoCacheContext]?.value ?: false
786819

787-
fun <T> MutableExecutionOptions<T>.allowCachedPartialResults(allowCachedPartialResults: Boolean): T =
788-
addExecutionContext(FetchCacheOptionsContext(fetchCacheOptions.also { it.allowCachedPartialResults(allowCachedPartialResults) }))
820+
fun <T> MutableExecutionOptions<T>.noCache(noCache: Boolean): T {
821+
if (noCache) {
822+
// noCache and onlyIfCached are mutually exclusive
823+
addExecutionContext(FetchOnlyIfCachedContext(false))
824+
}
825+
return addExecutionContext(FetchNoCacheContext(noCache))
826+
}
789827

790-
fun <T> MutableExecutionOptions<T>.allowCachedErrors(allowCachedErrors: Boolean): T =
791-
addExecutionContext(FetchCacheOptionsContext(fetchCacheOptions.also { it.allowCachedErrors(allowCachedErrors) }))
792828

793-
fun <T> MutableExecutionOptions<T>.noCache(noCache: Boolean): T =
794-
addExecutionContext(FetchCacheOptionsContext(fetchCacheOptions.also { it.noCache(noCache) }))
829+
internal class FetchOnlyIfCachedContext(val value: Boolean) : ExecutionContext.Element {
830+
override val key: ExecutionContext.Key<*>
831+
get() = Key
832+
833+
companion object Key : ExecutionContext.Key<FetchOnlyIfCachedContext>
834+
}
795835

796-
fun <T> MutableExecutionOptions<T>.onlyIfCached(onlyIfCached: Boolean): T =
797-
addExecutionContext(FetchCacheOptionsContext(fetchCacheOptions.also { it.onlyIfCached(onlyIfCached) }))
836+
internal val ExecutionOptions.onlyIfCached: Boolean
837+
get() = executionContext[FetchOnlyIfCachedContext]?.value ?: false
798838

799-
fun <T> MutableExecutionOptions<T>.reload(reload: Boolean): T =
800-
addExecutionContext(FetchCacheOptionsContext(fetchCacheOptions.also { it.reload(reload) }))
839+
fun <T> MutableExecutionOptions<T>.onlyIfCached(onlyIfCached: Boolean): T {
840+
if (onlyIfCached) {
841+
// noCache and onlyIfCached are mutually exclusive
842+
addExecutionContext(FetchNoCacheContext(false))
843+
// reload and onlyIfCached are mutually exclusive
844+
addExecutionContext(FetchReloadContext(false))
845+
}
846+
return addExecutionContext(FetchOnlyIfCachedContext(onlyIfCached))
847+
}
801848

802849

803-
internal class RefetchCacheOptionsContext(val value: CacheOptionsImpl) : ExecutionContext.Element {
850+
internal class FetchReloadContext(val value: Boolean) : ExecutionContext.Element {
804851
override val key: ExecutionContext.Key<*>
805852
get() = Key
806853

807-
companion object Key : ExecutionContext.Key<RefetchCacheOptionsContext>
854+
companion object Key : ExecutionContext.Key<FetchReloadContext>
808855
}
809856

810-
internal val ExecutionOptions.refetchCacheOptions: CacheOptionsImpl
811-
get() = executionContext[RefetchCacheOptionsContext]?.value ?: CacheOptionsImpl(onlyIfCached = true)
857+
internal val ExecutionOptions.reload: Boolean
858+
get() = executionContext[FetchReloadContext]?.value ?: false
812859

813-
fun <T> MutableExecutionOptions<T>.refetchCacheOptions(refetchCacheOptions: CacheOptions.() -> Unit): T =
814-
addExecutionContext(RefetchCacheOptionsContext(this.refetchCacheOptions.also { refetchCacheOptions(it) }))
860+
fun <T> MutableExecutionOptions<T>.reload(reload: Boolean): T {
861+
if (reload) {
862+
// onlyIfCached and reload are mutually exclusive
863+
addExecutionContext(FetchOnlyIfCachedContext(false))
864+
}
865+
return addExecutionContext(FetchReloadContext(reload))
866+
}
867+
868+
869+
internal class RefetchAllowCachedPartialResultsContext(val value: Boolean) : ExecutionContext.Element {
870+
override val key: ExecutionContext.Key<*>
871+
get() = Key
872+
873+
companion object Key : ExecutionContext.Key<RefetchAllowCachedPartialResultsContext>
874+
}
875+
876+
internal val ExecutionOptions.refetchAllowCachedPartialResults: Boolean
877+
get() = executionContext[RefetchAllowCachedPartialResultsContext]?.value ?: false
878+
879+
880+
internal class RefetchAllowCachedErrorsContext(val value: Boolean) : ExecutionContext.Element {
881+
override val key: ExecutionContext.Key<*>
882+
get() = Key
883+
884+
companion object Key : ExecutionContext.Key<RefetchAllowCachedErrorsContext>
885+
}
886+
887+
internal val ExecutionOptions.refetchAllowCachedErrors: Boolean
888+
get() = executionContext[RefetchAllowCachedErrorsContext]?.value ?: false
889+
890+
891+
internal class RefetchNoCacheContext(val value: Boolean) : ExecutionContext.Element {
892+
override val key: ExecutionContext.Key<*>
893+
get() = Key
894+
895+
companion object Key : ExecutionContext.Key<RefetchNoCacheContext>
896+
}
897+
898+
internal val ExecutionOptions.refetchNoCache: Boolean
899+
get() = executionContext[RefetchNoCacheContext]?.value ?: false
900+
901+
902+
internal class RefetchOnlyIfCachedContext(val value: Boolean) : ExecutionContext.Element {
903+
override val key: ExecutionContext.Key<*>
904+
get() = Key
905+
906+
companion object Key : ExecutionContext.Key<RefetchOnlyIfCachedContext>
907+
}
908+
909+
internal val ExecutionOptions.refetchOnlyIfCached: Boolean
910+
get() = executionContext[RefetchOnlyIfCachedContext]?.value ?: true
911+
912+
913+
internal class RefetchReloadContext(val value: Boolean) : ExecutionContext.Element {
914+
override val key: ExecutionContext.Key<*>
915+
get() = Key
916+
917+
companion object Key : ExecutionContext.Key<RefetchReloadContext>
918+
}
919+
920+
internal val ExecutionOptions.refetchReload: Boolean
921+
get() = executionContext[RefetchReloadContext]?.value ?: false
922+
923+
fun <T> MutableExecutionOptions<T>.refetchCacheOptions(refetchCacheOptions: CacheOptions.() -> Unit): T {
924+
val refetchCacheOptions = CacheOptionsImpl().also { refetchCacheOptions(it) }
925+
addExecutionContext(RefetchNoCacheContext(refetchCacheOptions.noCache))
926+
addExecutionContext(RefetchOnlyIfCachedContext(refetchCacheOptions.onlyIfCached))
927+
addExecutionContext(RefetchReloadContext(refetchCacheOptions.reload))
928+
addExecutionContext(RefetchAllowCachedPartialResultsContext(refetchCacheOptions.allowCachedPartialResults))
929+
return addExecutionContext(RefetchAllowCachedErrorsContext(refetchCacheOptions.allowCachedErrors))
930+
}

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,24 @@ val DefaultFetchPolicyInterceptor = object : ApolloInterceptor {
3737
request: ApolloRequest<D>,
3838
chain: ApolloInterceptorChain,
3939
): Flow<ApolloResponse<D>> {
40-
val cacheOptions = request.fetchCacheOptions
4140
return flow {
42-
if (!cacheOptions.noCache) {
41+
if (!request.noCache) {
4342
val cacheResponse = chain.proceed(
4443
request = request
4544
.newBuilder()
4645
.fetchFromCache(true)
4746
.build()
4847
).single()
49-
.errorsAsException(allowCachedPartialResults = cacheOptions.allowCachedPartialResults, allowCachedErrors = cacheOptions.allowCachedErrors)
50-
emit(cacheResponse.newBuilder().isLast(!cacheOptions.reload && (cacheOptions.onlyIfCached || cacheResponse.exception == null))
48+
.errorsAsException(allowCachedPartialResults = request.allowCachedPartialResults, allowCachedErrors = request.allowCachedErrors)
49+
emit(cacheResponse.newBuilder().isLast(!request.reload && (request.onlyIfCached || cacheResponse.exception == null))
5150
.build()
5251
)
53-
if (cacheResponse.exception == null && !cacheOptions.reload) {
52+
if (cacheResponse.exception == null && !request.reload) {
5453
return@flow
5554
}
5655
}
5756

58-
if (!cacheOptions.onlyIfCached) {
57+
if (!request.onlyIfCached) {
5958
val networkResponses = chain.proceed(request = request)
6059
emitAll(networkResponses)
6160
}
@@ -75,7 +74,7 @@ val CacheOnlyInterceptor = object : ApolloInterceptor {
7574
.fetchFromCache(true)
7675
.build()
7776
).map {
78-
it.errorsAsException(allowCachedPartialResults = request.fetchCacheOptions.allowCachedPartialResults, allowCachedErrors = request.fetchCacheOptions.allowCachedErrors)
77+
it.errorsAsException(allowCachedPartialResults = request.allowCachedPartialResults, allowCachedErrors = request.allowCachedErrors)
7978
}
8079
}
8180
}
@@ -103,7 +102,7 @@ val CacheFirstInterceptor = object : ApolloInterceptor {
103102
.fetchFromCache(true)
104103
.build()
105104
).single()
106-
.errorsAsException(allowCachedPartialResults = request.fetchCacheOptions.allowCachedPartialResults, allowCachedErrors = request.fetchCacheOptions.allowCachedErrors)
105+
.errorsAsException(allowCachedPartialResults = request.allowCachedPartialResults, allowCachedErrors = request.allowCachedErrors)
107106
emit(cacheResponse.newBuilder().isLast(cacheResponse.exception == null).build())
108107
if (cacheResponse.exception == null) {
109108
return@flow
@@ -150,7 +149,7 @@ val NetworkFirstInterceptor = object : ApolloInterceptor {
150149
.fetchFromCache(true)
151150
.build()
152151
).single()
153-
.errorsAsException(allowCachedPartialResults = request.fetchCacheOptions.allowCachedPartialResults, allowCachedErrors = request.fetchCacheOptions.allowCachedErrors)
152+
.errorsAsException(allowCachedPartialResults = request.allowCachedPartialResults, allowCachedErrors = request.allowCachedErrors)
154153
emit(cacheResponse)
155154
}
156155
}
@@ -169,7 +168,7 @@ val CacheAndNetworkInterceptor = object : ApolloInterceptor {
169168
.fetchFromCache(true)
170169
.build()
171170
).single()
172-
.errorsAsException(allowCachedPartialResults = request.fetchCacheOptions.allowCachedPartialResults, allowCachedErrors = request.fetchCacheOptions.allowCachedErrors)
171+
.errorsAsException(allowCachedPartialResults = request.allowCachedPartialResults, allowCachedErrors = request.allowCachedErrors)
173172

174173
emit(cacheResponse.newBuilder().isLast(false).build())
175174

0 commit comments

Comments
 (0)