Skip to content

Commit 912a390

Browse files
authored
Support custom error types (#583)
* Rebase Signed-off-by: mramotar <mramotar@dropbox.com> * Cover custom error Signed-off-by: mramotar <mramotar@dropbox.com> --------- Signed-off-by: mramotar <mramotar@dropbox.com>
1 parent 8cc8edd commit 912a390

File tree

4 files changed

+65
-2
lines changed

4 files changed

+65
-2
lines changed

store/src/commonMain/kotlin/org/mobilenativefoundation/store/store5/FetcherResult.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ sealed class FetcherResult<out Network : Any> {
55
sealed class Error : FetcherResult<Nothing>() {
66
data class Exception(val error: Throwable) : Error()
77
data class Message(val message: String) : Error()
8+
data class Custom<E : Any>(val error: E) : Error()
89
}
910
}

store/src/commonMain/kotlin/org/mobilenativefoundation/store/store5/StoreReadResponse.kt

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ sealed class StoreReadResponse<out Output> {
4444
StoreReadResponse<Output>()
4545

4646
/**
47-
* No new data event dispatched by Store to signal the [Fetcher] returned no data (i.e the
48-
* returned [kotlinx.coroutines.Flow], when collected, was empty).
47+
* No new data event dispatched by Store to signal the [Fetcher] returned no data (i.e., the
48+
* returned [kotlinx.coroutines.flow.Flow], when collected, was empty).
4949
*/
5050
data class NoNewData(override val origin: StoreReadResponseOrigin) : StoreReadResponse<Nothing>()
5151

@@ -62,6 +62,11 @@ sealed class StoreReadResponse<out Output> {
6262
val message: String,
6363
override val origin: StoreReadResponseOrigin
6464
) : Error()
65+
66+
data class Custom<E : Any>(
67+
val error: E,
68+
override val origin: StoreReadResponseOrigin
69+
) : Error()
6570
}
6671

6772
/**
@@ -105,6 +110,26 @@ sealed class StoreReadResponse<out Output> {
105110
else -> null
106111
}
107112

113+
private fun errorOrNull(): Throwable? {
114+
if (this is Error.Exception) {
115+
return error
116+
}
117+
118+
return null
119+
}
120+
121+
/**
122+
* @returns Error if there is one, else null.
123+
*/
124+
@Suppress("UNCHECKED_CAST")
125+
fun <E : Any> errorOrNull(): E? {
126+
if (this is Error.Custom<*>) {
127+
return (this as? Error.Custom<E>)?.error
128+
}
129+
130+
return errorOrNull() as? E
131+
}
132+
108133
@Suppress("UNCHECKED_CAST")
109134
internal fun <T> swapType(): StoreReadResponse<T> = when (this) {
110135
is Error -> this
@@ -141,4 +166,11 @@ sealed class StoreReadResponseOrigin {
141166
fun StoreReadResponse.Error.doThrow(): Nothing = when (this) {
142167
is StoreReadResponse.Error.Exception -> throw error
143168
is StoreReadResponse.Error.Message -> throw RuntimeException(message)
169+
is StoreReadResponse.Error.Custom<*> -> {
170+
if (error is Throwable) {
171+
throw error
172+
} else {
173+
throw RuntimeException("Non-throwable custom error: $error")
174+
}
175+
}
144176
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ internal class FetcherController<Key : Any, Network : Any, Output : Any, Local :
9292
it.error,
9393
origin = StoreReadResponseOrigin.Fetcher()
9494
)
95+
is FetcherResult.Error.Custom<*> -> StoreReadResponse.Error.Custom(
96+
it.error,
97+
StoreReadResponseOrigin.Fetcher()
98+
)
9599
}
96100
}.onEmpty {
97101
val origin =

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,32 @@ class FetcherResponseTests {
211211
)
212212
}
213213

214+
@Test
215+
fun givenAFetcherThatEmitsCustomErrorWhenStreamingThenCustomErrorShouldBeEmitted() = testScope.runTest {
216+
data class TestCustomError(val errorMessage: String)
217+
val customError = TestCustomError("Test custom error")
218+
219+
val store = StoreBuilder.from(
220+
fetcher = Fetcher.ofResultFlow { _: Int ->
221+
flowOf(
222+
FetcherResult.Error.Custom(customError)
223+
)
224+
}
225+
).buildWithTestScope()
226+
227+
assertEmitsExactly(
228+
store.stream(StoreReadRequest.fresh(1)),
229+
listOf(
230+
StoreReadResponse.Loading(origin = StoreReadResponseOrigin.Fetcher()),
231+
StoreReadResponse.Error.Custom(
232+
error = customError,
233+
origin = StoreReadResponseOrigin.Fetcher()
234+
)
235+
)
236+
)
237+
}
238+
239+
214240
private fun <Key : Any, Output : Any> StoreBuilder<Key, Output>.buildWithTestScope() =
215241
scope(testScope).build()
216242
}

0 commit comments

Comments
 (0)