Skip to content

Instantly share code, notes, and snippets.

@jonreeve
Last active June 16, 2021 11:25
Show Gist options
  • Save jonreeve/68f0076c14109930d53c90a72e76c42e to your computer and use it in GitHub Desktop.
Save jonreeve/68f0076c14109930d53c90a72e76c42e to your computer and use it in GitHub Desktop.
Sealed failure types - with some wrapping + potentially reusable handling
package com.myapp.domain.CallResult
sealed class CallResult<out S, out F> {
data class Success<out S> internal constructor(val value: S) : CallResult<S, Nothing>()
data class Failure<out F> internal constructor(val failure: F) : CallResult<Nothing, F>()
inline fun <V> fold(onSuccess: (S) -> V, onFailure: (F) -> V): V =
when (this) {
is Success -> onSuccess(value)
is Failure -> onFailure(failure)
}.exhaustive
companion object {
fun <S> success(value: S) = Success(value)
fun success() = Success(Unit)
fun <F> failure(value: F) = Failure(value)
}
}
package com.myapp.domain.api.common.failure
import com.myapp.domain.CallResult
import com.myapp.domain.api.common.failure.ApiFailure.Common
import com.myapp.domain.api.common.failure.ApiFailure.Specific
import com.myapp.domain.api.common.failure.ApiResult.Failure
import com.myapp.domain.api.common.failure.ApiResult.Success
import com.myapp.domain.api.common.failure.CommonApiFailure.GraphQLUnrecognised
import com.myapp.domain.api.common.failure.CommonApiFailure.Network
import com.myapp.domain.api.common.failure.CommonApiFailure.Server
import com.myapp.domain.api.common.failure.EatPorridgeFailure.TooCold
import com.myapp.domain.api.common.failure.EatPorridgeFailure.TooHot
import com.myapp.domain.api.common.failure.SacrificeFailure.NotAFullMoon
import com.myapp.domain.api.common.failure.SacrificeFailure.UserNotHandsome
sealed interface CommonApiFailure {
object Network : CommonApiFailure
data class Server(val code: Int, val message: String) : CommonApiFailure
data class GraphQLUnrecognised(val errors: List<String>) : CommonApiFailure
}
fun handleCommonFailure(common: CommonApiFailure): Nothing = when (common) {
is GraphQLUnrecognised -> TODO()
is Network -> TODO()
is Server -> TODO()
}
fun handleCommonFailure(common: Common): Nothing = handleCommonFailure (common.common)
fun handleCommonFailure(result: Failure.Common): Nothing = handleCommonFailure(result.failure)
// --------------------------------------------------------
// 1. Reusing common types in each API call failure enumeration
sealed interface EatPorridgeFailure {
object TooHot : EatPorridgeFailure
data class TooCold(val somethingPertinent: Int) : EatPorridgeFailure
// Have to declare this in every set of specific failures :(
data class Common(val common: CommonApiFailure) : EatPorridgeFailure
}
sealed interface SleepInBedFailure {
object TooHard : SleepInBedFailure
object TooSoft : SleepInBedFailure
// Have to declare this in every set of specific failures :(
data class Common(val common: CommonApiFailure) : SleepInBedFailure
}
fun test() {
val result: CallResult<Int, EatPorridgeFailure> = CallResult.failure(TooHot)
result.fold(
onSuccess = {
// tasty porridge
},
onFailure = { failure ->
when (failure) {
is TooHot -> TODO()
is TooCold -> TODO()
// Not so sure having it mixed in here directly would actually be a good thing...
is EatPorridgeFailure.Common -> handleCommonFailure(failure.common)
}
}
)
}
// --------------------------------------------------------
// 2. Wrapping both common + specific
sealed interface ApiFailure<out T> {
data class Specific<T>(val specific: T) : ApiFailure<T>
data class Common(val common: CommonApiFailure) : ApiFailure<Nothing>
}
sealed interface SacrificeFailure {
object UserNotHandsome : SacrificeFailure
object NotAFullMoon : SacrificeFailure
}
fun test2() {
val result: CallResult<Int, ApiFailure<SacrificeFailure>> = CallResult.failure(Specific(UserNotHandsome))
// The nesting of CallResult handling then the failure type handling is still a bit boilerplatey
result.fold(
onSuccess = {
// time to chant and dance round the fire
},
onFailure = { failure ->
when (failure) {
is Common -> handleCommonFailure(failure)
// Could also extract a function handleSpecificFailure
is Specific -> when (failure.specific) {
UserNotHandsome -> TODO()
NotAFullMoon -> TODO()
}
}
}
)}
// --------------------------------------------------------
// 3. Different result type, so wrapping success, and failure: common + specific
sealed interface ApiResult<out S, out F> {
data class Success<S>(val content: S) : ApiResult<S, Nothing>
sealed class Failure<F> : ApiResult<Nothing, F> {
data class Specific<F>(val failure: F) : Failure<F>()
data class Common(val failure: CommonApiFailure) : Failure<Nothing>()
}
}
fun test3() {
val result: ApiResult<Int, SacrificeFailure> = Failure.Specific(UserNotHandsome)
when (result) {
is Success -> TODO()
is Failure.Common -> handleCommonFailure(result)
is Failure.Specific -> when (result.failure) {
UserNotHandsome -> TODO()
NotAFullMoon -> TODO()
}
}
// Or if you like:
when (result) {
is Success -> TODO()
is Failure -> TODO() // maybe delegate all the failure handling, common and specific, to another function
}
// ...or with some function like that fold thing we have on CallResult, but with using
// 2 or 3 lamdas however we want to split it just like we do with the `when` here
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment