Skip to content

Commit cc437c7

Browse files
committed
Address PR review comments
Signed-off-by: Matt Ramotar <matt.ramotar@uber.com>
1 parent 6cc71fb commit cc437c7

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

store/src/commonMain/kotlin/org/mobilenativefoundation/store/store5/impl/FetcherController.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.mobilenativefoundation.store.store5.impl
1717

18+
import kotlinx.coroutines.CancellationException
1819
import kotlinx.coroutines.CoroutineScope
1920
import kotlinx.coroutines.NonCancellable
2021
import kotlinx.coroutines.async
@@ -86,6 +87,8 @@ internal class FetcherController<Key : Any, Network : Any, Output : Any, Local :
8687
network,
8788
origin = StoreReadResponseOrigin.Fetcher(it.origin),
8889
) as StoreReadResponse<Network>
90+
} catch (exception: CancellationException) {
91+
throw exception
8992
} catch (exception: Throwable) {
9093
StoreReadResponse.Error.Exception(
9194
exception,

store/src/commonMain/kotlin/org/mobilenativefoundation/store/store5/impl/RealStore.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ internal class RealStore<Key : Any, Network : Any, Output : Any, Local : Any>(
223223
}
224224

225225
val requestKeyToFetcherName: MutableMap<Key, String?> = mutableMapOf()
226+
// Track if network errored AND this is a fresh request where fallback behavior matters
227+
var networkErrorWithNoFallback = false
226228
// we use a merge implementation that gives the source of the flow so that we can decide
227229
// based on that.
228230
return networkFlow.merge(diskFlow).transform {
@@ -233,6 +235,11 @@ internal class RealStore<Key : Any, Network : Any, Output : Any, Local : Any>(
233235
val responseOrigin = it.value.origin as StoreReadResponseOrigin.Fetcher
234236
requestKeyToFetcherName[request.key] = responseOrigin.name
235237

238+
// Track if network errored and fallback to disk is disabled for fresh requests
239+
if (it.value is StoreReadResponse.Error && skipDiskCache && !request.fallBackToSourceOfTruth) {
240+
networkErrorWithNoFallback = true
241+
}
242+
236243
if (it.value is StoreReadResponse.Data ||
237244
it.value is StoreReadResponse.NoNewData ||
238245
it.value is StoreReadResponse.Error
@@ -254,6 +261,11 @@ internal class RealStore<Key : Any, Network : Any, Output : Any, Local : Any>(
254261
// right, that is data from disk
255262
when (val diskData = it.value) {
256263
is StoreReadResponse.Data -> {
264+
// Skip disk data if this was a fresh request that errored with fallback disabled
265+
if (networkErrorWithNoFallback) {
266+
return@transform
267+
}
268+
257269
val responseOriginWithFetcherName =
258270
diskData.origin.let { origin ->
259271
if (origin is StoreReadResponseOrigin.Fetcher) {

store/src/commonTest/kotlin/org/mobilenativefoundation/store/store5/FlowStoreTests.kt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,4 +1207,82 @@ class FlowStoreTests {
12071207
cancelAndIgnoreRemainingEvents()
12081208
}
12091209
}
1210+
1211+
@Test
1212+
fun stream_givenConverterThrowsWithFallbackDisabled_thenDiskDataNotEmitted() =
1213+
testScope.runTest {
1214+
// Given: Pre-populate disk with data, then request fresh with fallBackToSourceOfTruth=false
1215+
val exception = IllegalStateException("Converter failed")
1216+
val persister = InMemoryPersister<Int, String>()
1217+
persister.write(1, "cached value")
1218+
1219+
val pipeline =
1220+
StoreBuilder.from(
1221+
fetcher = Fetcher.of { _: Int -> "network" },
1222+
sourceOfTruth = persister.asSourceOfTruth(),
1223+
converter =
1224+
object : Converter<String, String, String> {
1225+
override fun fromNetworkToLocal(network: String): String {
1226+
throw exception
1227+
}
1228+
1229+
override fun fromOutputToLocal(output: String): String = output
1230+
},
1231+
).buildWithTestScope()
1232+
1233+
// When: Request with fallBackToSourceOfTruth=false
1234+
pipeline.stream(StoreReadRequest.fresh(1, fallBackToSourceOfTruth = false)).test {
1235+
assertEquals(
1236+
Loading(origin = StoreReadResponseOrigin.Fetcher()),
1237+
awaitItem(),
1238+
)
1239+
1240+
// Then: Only error is emitted, no disk data (since fallback is disabled)
1241+
val errorResponse = awaitItem()
1242+
assertIs<StoreReadResponse.Error.Exception>(errorResponse)
1243+
assertEquals(exception.message, errorResponse.error.message)
1244+
cancelAndIgnoreRemainingEvents()
1245+
}
1246+
}
1247+
1248+
@Test
1249+
fun stream_givenConverterThrowsWithFallbackEnabled_thenDiskDataEmitted() =
1250+
testScope.runTest {
1251+
// Given: Pre-populate disk with data, then request with fallBackToSourceOfTruth=true
1252+
val exception = IllegalStateException("Converter failed")
1253+
val persister = InMemoryPersister<Int, String>()
1254+
persister.write(1, "cached value")
1255+
1256+
val pipeline =
1257+
StoreBuilder.from(
1258+
fetcher = Fetcher.of { _: Int -> "network" },
1259+
sourceOfTruth = persister.asSourceOfTruth(),
1260+
converter =
1261+
object : Converter<String, String, String> {
1262+
override fun fromNetworkToLocal(network: String): String {
1263+
throw exception
1264+
}
1265+
1266+
override fun fromOutputToLocal(output: String): String = output
1267+
},
1268+
).buildWithTestScope()
1269+
1270+
// When: Request with fallBackToSourceOfTruth=true
1271+
pipeline.stream(StoreReadRequest.fresh(1, fallBackToSourceOfTruth = true)).test {
1272+
assertEquals(
1273+
Loading(origin = StoreReadResponseOrigin.Fetcher()),
1274+
awaitItem(),
1275+
)
1276+
1277+
// Then: Error is emitted
1278+
val errorResponse = awaitItem()
1279+
assertIs<StoreReadResponse.Error.Exception>(errorResponse)
1280+
1281+
// And: Disk data is also emitted (since fallback is enabled)
1282+
val diskData = awaitItem()
1283+
assertIs<StoreReadResponse.Data<String>>(diskData)
1284+
assertEquals("cached value", diskData.value)
1285+
cancelAndIgnoreRemainingEvents()
1286+
}
1287+
}
12101288
}

0 commit comments

Comments
 (0)