Last active
June 16, 2021 11:25
-
-
Save jonreeve/68f0076c14109930d53c90a72e76c42e to your computer and use it in GitHub Desktop.
Sealed failure types - with some wrapping + potentially reusable handling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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