Skip to content

Instantly share code, notes, and snippets.

@marcellogalhardo
Last active April 9, 2021 07:45
Show Gist options
  • Save marcellogalhardo/26d1606af1842d373c848a657aeb91fb to your computer and use it in GitHub Desktop.
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
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