Last active
April 9, 2021 07:45
-
-
Save marcellogalhardo/26d1606af1842d373c848a657aeb91fb to your computer and use it in GitHub Desktop.
A Result class with Public API compatible to kotlin.Result for a easy migration
This file contains hidden or 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
import java.io.Serializable | |
import kotlin.contracts.ExperimentalContracts | |
import kotlin.contracts.InvocationKind | |
import kotlin.contracts.contract | |
/** | |
* A discriminated union that encapsulates a successful outcome with a value of type [T] | |
* or a failure with an arbitrary [Throwable] exception. | |
*/ | |
public sealed class Result<out T> : Serializable { | |
@PublishedApi | |
internal data class Failure<out T>(val exception: Throwable) : Result<T>() | |
@PublishedApi | |
internal data class Success<out T>(val value: T) : Result<T>() | |
/** | |
* Returns `true` if this instance represents a failed outcome. | |
* In this case [isSuccess] returns `false`. | |
*/ | |
public val isFailure: Boolean | |
get() = this is Failure | |
/** | |
* Returns `true` if this instance represents a successful outcome. | |
* In this case [isFailure] returns `false`. | |
*/ | |
public val isSuccess: Boolean | |
get() = this is Success | |
/** | |
* Returns the encapsulated value if this instance represents [success][Result.isSuccess] or `null` | |
* if it is [failure][Result.isFailure]. | |
* | |
* This function is a shorthand for `getOrElse { null }` (see [getOrElse]) or | |
* `fold(onSuccess = { it }, onFailure = { null })` (see [fold]). | |
*/ | |
public fun getOrNull(): T? = when (this) { | |
is Failure -> null | |
is Success -> value | |
} | |
/** | |
* Returns the encapsulated [Throwable] exception if this instance represents [failure][isFailure] or `null` | |
* if it is [success][isSuccess]. | |
* | |
* This function is a shorthand for `fold(onSuccess = { null }, onFailure = { it })` (see [fold]). | |
*/ | |
public fun exceptionOrNull(): Throwable? = when (this) { | |
is Failure -> exception | |
is Success -> null | |
} | |
/** | |
* Returns a string `Success(v)` if this instance represents [success][Result.isSuccess] | |
* where `v` is a string representation of the value or a string `Failure(x)` if | |
* it is [failure][isFailure] where `x` is a string representation of the exception. | |
*/ | |
public override fun toString(): String = when (this) { | |
is Failure -> "Failure($exception)" | |
is Success -> "Success($value)" | |
} | |
/** | |
* Companion object for [Result] class that contains its constructor functions | |
* [success] and [failure]. | |
*/ | |
public companion object { | |
/** | |
* Returns an instance that encapsulates the given [value] as successful value. | |
*/ | |
public fun <T> success(value: T): Result<T> = Success(value) | |
/** | |
* Returns an instance that encapsulates the given [Throwable] [exception] as failure. | |
*/ | |
public fun <T> failure(exception: Throwable): Result<T> = Failure(exception) | |
} | |
} | |
/** | |
* Calls the specified function [block] and returns its encapsulated result if invocation was successful, | |
* catching any [Throwable] exception that was thrown from the [block] function execution and encapsulating it as a failure. | |
*/ | |
@Suppress("FunctionName", "TooGenericExceptionCaught") | |
public inline fun <T> Result(block: () -> T): Result<T> { | |
return try { | |
Result.success(block()) | |
} catch (exception: Throwable) { | |
Result.failure(exception) | |
} | |
} | |
/** | |
* Returns the encapsulated value if this instance represents [success][Result.isSuccess] or throws the encapsulated [Throwable] exception | |
* if it is [failure][Result.isFailure]. | |
* | |
* This function is a shorthand for `getOrElse { throw it }` (see [getOrElse]). | |
*/ | |
public fun <T> Result<T>.getOrThrow(): T { | |
return when (this) { | |
is Result.Failure -> throw exception | |
is Result.Success -> value | |
} | |
} | |
/** | |
* Returns the encapsulated value if this instance represents [success][Result.isSuccess] or the | |
* result of [onFailure] function for the encapsulated [Throwable] exception if it is [failure][Result.isFailure]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [onFailure] function. | |
* | |
* This function is a shorthand for `fold(onSuccess = { it }, onFailure = onFailure)` (see [fold]). | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T : R> Result<T>.getOrElse(onFailure: (exception: Throwable) -> R): R { | |
contract { | |
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Result.Failure -> onFailure(exception) | |
is Result.Success -> value | |
} | |
} | |
/** | |
* Returns the encapsulated value if this instance represents [success][Result.isSuccess] or the | |
* [defaultValue] if it is [failure][Result.isFailure]. | |
* | |
* This function is a shorthand for `getOrElse { defaultValue }` (see [getOrElse]). | |
*/ | |
public fun <R, T : R> Result<T>.getOrDefault(defaultValue: R): R { | |
return when (this) { | |
is Result.Failure -> defaultValue | |
is Result.Success -> value | |
} | |
} | |
/** | |
* Returns the result of [onSuccess] for the encapsulated value if this instance represents [success][Result.isSuccess] | |
* or the result of [onFailure] function for the encapsulated [Throwable] exception if it is [failure][Result.isFailure]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [onSuccess] or by [onFailure] function. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T> Result<T>.fold( | |
onFailure: (exception: Throwable) -> R, | |
onSuccess: (value: T) -> R | |
): R { | |
contract { | |
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) | |
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Result.Failure -> onFailure(exception) | |
is Result.Success -> onSuccess(value) | |
} | |
} | |
/** | |
* Returns the encapsulated result of the given [transform] function applied to the encapsulated value | |
* if this instance represents [success][Result.isSuccess] or the | |
* original encapsulated [Throwable] exception if it is [failure][Result.isFailure]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function. | |
* See [mapCatching] for an alternative that encapsulates exceptions. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T> Result<T>.map(transform: (value: T) -> R): Result<R> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Result.Failure -> Result.failure(exception) | |
is Result.Success -> Result.success(transform(value)) | |
} | |
} | |
/** | |
* Returns the encapsulated result of the given [transform] function applied to the encapsulated value | |
* if this instance represents [success][Result.isSuccess] or the | |
* original encapsulated [Throwable] exception if it is [failure][Result.isFailure]. | |
* | |
* This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a failure. | |
* See [map] for an alternative that rethrows exceptions from `transform` function. | |
*/ | |
public inline fun <R, T> Result<T>.mapCatching(transform: (value: T) -> R): Result<R> { | |
return when (this) { | |
is Result.Failure -> Result.failure(exception) | |
is Result.Success -> Result { transform(value) } | |
} | |
} | |
/** | |
* Returns a [Result] of the given [transform] function applied to the encapsulated value | |
* if this instance represents [success][Result.isSuccess] or the | |
* original encapsulated [Throwable] exception if it is [failure][Result.isFailure]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function. | |
* See [flatMapCatching] for an alternative that encapsulates exceptions. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Result.Failure -> Result.failure(exception) | |
is Result.Success -> transform(value) | |
} | |
} | |
/** | |
* Returns a [Result] of the given [transform] function applied to the encapsulated value | |
* if this instance represents [success][Result.isSuccess] or the | |
* original encapsulated [Throwable] exception if it is [failure][Result.isFailure]. | |
* | |
* This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a failure. | |
* See [map] for an alternative that rethrows exceptions from `transform` function. | |
*/ | |
public inline fun <R, T> Result<T>.flatMapCatching(transform: (value: T) -> Result<R>): Result<R> { | |
return when (this) { | |
is Result.Failure -> Result.failure(exception) | |
is Result.Success -> Result { transform(value).getOrThrow() } | |
} | |
} | |
/** | |
* Returns the encapsulated result of the given [transform] function applied to the encapsulated [Throwable] exception | |
* if this instance represents [failure][Result.isFailure] or the | |
* original encapsulated value if it is [success][Result.isSuccess]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function. | |
* See [recoverCatching] for an alternative that encapsulates exceptions. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T : R> Result<T>.recover(transform: (exception: Throwable) -> R): Result<R> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Result.Failure -> Result.success(transform(exception)) | |
is Result.Success -> this | |
} | |
} | |
/** | |
* Returns the encapsulated result of the given [transform] function applied to the encapsulated [Throwable] exception | |
* if this instance represents [failure][Result.isFailure] or the | |
* original encapsulated value if it is [success][Result.isSuccess]. | |
* | |
* This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a failure. | |
* See [recover] for an alternative that rethrows exceptions. | |
*/ | |
public inline fun <R, T : R> Result<T>.recoverCatching(transform: (exception: Throwable) -> R): Result<R> { | |
return when (this) { | |
is Result.Failure -> Result { transform(exception) } | |
is Result.Success -> this | |
} | |
} | |
/** | |
* Performs the given [action] on the encapsulated [Throwable] exception if this instance represents [failure][Result.isFailure]. | |
* Returns the original `Result` unchanged. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <T> Result<T>.onFailure(action: (exception: Throwable) -> Unit): Result<T> { | |
contract { | |
callsInPlace(action, InvocationKind.AT_MOST_ONCE) | |
} | |
if (this is Result.Failure) action(exception) | |
return this | |
} | |
/** | |
* Performs the given [action] on the encapsulated value if this instance represents [success][Result.isSuccess]. | |
* Returns the original `Result` unchanged. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <T> Result<T>.onSuccess(action: (value: T) -> Unit): Result<T> { | |
contract { | |
callsInPlace(action, InvocationKind.AT_MOST_ONCE) | |
} | |
if (this is Result.Success) action(value) | |
return this | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment