Skip to content

Commit

Permalink
Support custom error types (#583)
Browse files Browse the repository at this point in the history
* Rebase

Signed-off-by: mramotar <[email protected]>

* Cover custom error

Signed-off-by: mramotar <[email protected]>

---------

Signed-off-by: mramotar <[email protected]>
  • Loading branch information
matt-ramotar authored Jan 7, 2024
1 parent 8cc8edd commit 912a390
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ sealed class FetcherResult<out Network : Any> {
sealed class Error : FetcherResult<Nothing>() {
data class Exception(val error: Throwable) : Error()
data class Message(val message: String) : Error()
data class Custom<E : Any>(val error: E) : Error()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ sealed class StoreReadResponse<out Output> {
StoreReadResponse<Output>()

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

Expand All @@ -62,6 +62,11 @@ sealed class StoreReadResponse<out Output> {
val message: String,
override val origin: StoreReadResponseOrigin
) : Error()

data class Custom<E : Any>(
val error: E,
override val origin: StoreReadResponseOrigin
) : Error()
}

/**
Expand Down Expand Up @@ -105,6 +110,26 @@ sealed class StoreReadResponse<out Output> {
else -> null
}

private fun errorOrNull(): Throwable? {
if (this is Error.Exception) {
return error
}

return null
}

/**
* @returns Error if there is one, else null.
*/
@Suppress("UNCHECKED_CAST")
fun <E : Any> errorOrNull(): E? {
if (this is Error.Custom<*>) {
return (this as? Error.Custom<E>)?.error
}

return errorOrNull() as? E
}

@Suppress("UNCHECKED_CAST")
internal fun <T> swapType(): StoreReadResponse<T> = when (this) {
is Error -> this
Expand Down Expand Up @@ -141,4 +166,11 @@ sealed class StoreReadResponseOrigin {
fun StoreReadResponse.Error.doThrow(): Nothing = when (this) {
is StoreReadResponse.Error.Exception -> throw error
is StoreReadResponse.Error.Message -> throw RuntimeException(message)
is StoreReadResponse.Error.Custom<*> -> {
if (error is Throwable) {
throw error
} else {
throw RuntimeException("Non-throwable custom error: $error")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ internal class FetcherController<Key : Any, Network : Any, Output : Any, Local :
it.error,
origin = StoreReadResponseOrigin.Fetcher()
)
is FetcherResult.Error.Custom<*> -> StoreReadResponse.Error.Custom(
it.error,
StoreReadResponseOrigin.Fetcher()
)
}
}.onEmpty {
val origin =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,32 @@ class FetcherResponseTests {
)
}

@Test
fun givenAFetcherThatEmitsCustomErrorWhenStreamingThenCustomErrorShouldBeEmitted() = testScope.runTest {
data class TestCustomError(val errorMessage: String)
val customError = TestCustomError("Test custom error")

val store = StoreBuilder.from(
fetcher = Fetcher.ofResultFlow { _: Int ->
flowOf(
FetcherResult.Error.Custom(customError)
)
}
).buildWithTestScope()

assertEmitsExactly(
store.stream(StoreReadRequest.fresh(1)),
listOf(
StoreReadResponse.Loading(origin = StoreReadResponseOrigin.Fetcher()),
StoreReadResponse.Error.Custom(
error = customError,
origin = StoreReadResponseOrigin.Fetcher()
)
)
)
}


private fun <Key : Any, Output : Any> StoreBuilder<Key, Output>.buildWithTestScope() =
scope(testScope).build()
}

0 comments on commit 912a390

Please sign in to comment.