Updated SuccessOrFailure class corresponding to KEEP-127 and docs.
This commit is contained in:
committed by
Denis Zharkov
parent
33f4a3fcbf
commit
3aa76c58fc
@@ -36,7 +36,7 @@ internal actual constructor(
|
||||
while (true) { // lock-free loop
|
||||
val cur = this.result // atomic read
|
||||
when {
|
||||
cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result._value)) return
|
||||
cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result.value)) return
|
||||
cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)) {
|
||||
delegate.resumeWith(result)
|
||||
return
|
||||
@@ -55,7 +55,7 @@ internal actual constructor(
|
||||
}
|
||||
return when {
|
||||
result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream
|
||||
result is Failure -> throw result.exception
|
||||
result is SuccessOrFailure.Failure -> throw result.exception
|
||||
else -> result // either COROUTINE_SUSPENDED or data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
// todo: Figure out how to avoid supressing error, move suppressions where they are needed.
|
||||
// todo: Figure out how to avoid suppressing errors, move suppressions where they are needed.
|
||||
@file:Suppress(
|
||||
"UNCHECKED_CAST",
|
||||
"RedundantVisibilityModifier",
|
||||
@@ -16,98 +16,259 @@
|
||||
package kotlin
|
||||
|
||||
import kotlin.internal.InlineOnly
|
||||
import kotlin.internal.contracts.*
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
public inline class SuccessOrFailure<out T> @PublishedApi internal constructor(internal val _value: Any?) {
|
||||
/**
|
||||
* A discriminated union that encapsulates successful outcome with a value of type [T]
|
||||
* or a failure with an arbitrary [Throwable] exception.
|
||||
*/
|
||||
public inline class SuccessOrFailure<out T> @PublishedApi internal constructor(
|
||||
@PublishedApi internal val value: Any?
|
||||
) : Serializable {
|
||||
// discovery
|
||||
|
||||
public val isSuccess: Boolean get() = _value !is Failure
|
||||
public val isFailure: Boolean get() = _value is Failure
|
||||
/**
|
||||
* Returns `true` if this instance represents successful outcome.
|
||||
* In this case [isFailure] returns `false`.
|
||||
*/
|
||||
public val isSuccess: Boolean get() = value !is Failure
|
||||
|
||||
/**
|
||||
* Returns `true` if this instance represents failed outcome.
|
||||
* In this case [isSuccess] returns `false`.
|
||||
*/
|
||||
public val isFailure: Boolean get() = value is Failure
|
||||
|
||||
// value retrieval
|
||||
|
||||
public fun getOrThrow(): T = when (_value) {
|
||||
is Failure -> throw _value.exception
|
||||
else -> _value as T
|
||||
}
|
||||
/**
|
||||
* Returns the encapsulated value if this instance represents [success][isSuccess] or throws the encapsulated exception
|
||||
* if it is [failure][isFailure].
|
||||
*/
|
||||
public fun getOrThrow(): T =
|
||||
when (value) {
|
||||
is Failure -> throw value.exception
|
||||
else -> value as T
|
||||
}
|
||||
|
||||
public fun getOrNull(): T? = when (_value) {
|
||||
is Failure -> null
|
||||
else -> _value as T
|
||||
}
|
||||
/**
|
||||
* Returns the encapsulated value if this instance represents [success][isSuccess] or `null`
|
||||
* if it is [failure][isFailure].
|
||||
*/
|
||||
public fun getOrNull(): T? =
|
||||
when (value) {
|
||||
is Failure -> null
|
||||
else -> value as T
|
||||
}
|
||||
|
||||
// exception retrieval
|
||||
|
||||
public fun exceptionOrNull(): Throwable? = when (_value) {
|
||||
is Failure -> _value.exception
|
||||
else -> null
|
||||
/**
|
||||
* Returns the encapsulated exception if this instance represents [failure][isFailure] or `null`
|
||||
* if it is [success][isSuccess].
|
||||
*/
|
||||
public fun exceptionOrNull(): Throwable? =
|
||||
when (value) {
|
||||
is Failure -> value.exception
|
||||
else -> null
|
||||
}
|
||||
|
||||
// identity
|
||||
|
||||
/**
|
||||
* Returns `true` if the [other] object is `SuccessOrFailure` that encapsulates an equal value or exception.
|
||||
*/
|
||||
public override fun equals(other: Any?): Boolean {
|
||||
// todo: this is workaround for is/as bugs, rewrite in direct way when fixed
|
||||
val other = other as? SuccessOrFailure<*> ?: return false
|
||||
return value == other.value
|
||||
}
|
||||
|
||||
// internal API for inline functions
|
||||
/**
|
||||
* Returns hashcode of either the encapsulated value or of the exception.
|
||||
*/
|
||||
public override fun hashCode(): Int = value?.hashCode() ?: 0
|
||||
|
||||
@PublishedApi internal val exception: Throwable get() = (_value as Failure).exception
|
||||
@PublishedApi internal val value: T get() = _value as T
|
||||
/**
|
||||
* Returns a string representation of the encapsulated value or `Failure(xxx)` string where
|
||||
* `xxx` is a string representation of the exception.
|
||||
*/
|
||||
public override fun toString(): String = value.toString()
|
||||
|
||||
// companion with constructors
|
||||
|
||||
/**
|
||||
* Companion object for [SuccessOrFailure] class that contains its constructor functions
|
||||
* [success] and [failure].
|
||||
*/
|
||||
public companion object {
|
||||
/**
|
||||
* Returns an instance that encapsulates the given [value] as successful value.
|
||||
*/
|
||||
@InlineOnly public inline fun <T> success(value: T): SuccessOrFailure<T> =
|
||||
SuccessOrFailure(value)
|
||||
|
||||
/**
|
||||
* Returns an instance that encapsulates the given [exception] as failure.
|
||||
*/
|
||||
@InlineOnly public inline fun <T> failure(exception: Throwable): SuccessOrFailure<T> =
|
||||
SuccessOrFailure<T>(Failure(exception))
|
||||
SuccessOrFailure(Failure(exception))
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal class Failure @PublishedApi internal constructor(
|
||||
@JvmField
|
||||
val exception: Throwable
|
||||
) : Serializable {
|
||||
override fun equals(other: Any?): Boolean = other is Failure && exception == other.exception
|
||||
override fun hashCode(): Int = exception.hashCode()
|
||||
override fun toString(): String = "Failure($exception)"
|
||||
}
|
||||
}
|
||||
|
||||
// top-Level internal failure-marker class
|
||||
// todo: maybe move it to another kotlin.internal package?
|
||||
@SinceKotlin("1.3")
|
||||
@PublishedApi
|
||||
internal class Failure @PublishedApi internal constructor(
|
||||
@JvmField
|
||||
val exception: Throwable
|
||||
) : Serializable
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@InlineOnly public inline fun <T> runOrCatch(block: () -> T): SuccessOrFailure<T> =
|
||||
try {
|
||||
/**
|
||||
* Calls the specified function [block] and returns its encapsulated result if invocation was successful,
|
||||
* catching and encapsulating any thrown exception as a failure.
|
||||
*/
|
||||
@InlineOnly public inline fun <R> runCatching(block: () -> R): SuccessOrFailure<R> {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
return try {
|
||||
SuccessOrFailure.success(block())
|
||||
} catch (e: Throwable) {
|
||||
SuccessOrFailure.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the specified function [block] with `this` value as its receiver and returns its encapsulated result
|
||||
* if invocation was successful, catching and encapsulating any thrown exception as a failure.
|
||||
*/
|
||||
@InlineOnly public inline fun <T, R> T.runCatching(block: T.() -> R): SuccessOrFailure<R> {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
return try {
|
||||
SuccessOrFailure.success(block())
|
||||
} catch (e: Throwable) {
|
||||
SuccessOrFailure.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
// -- extensions ---
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@InlineOnly public inline fun <R, T : R> SuccessOrFailure<T>.getOrElse(default: () -> R): R = when {
|
||||
isFailure -> default()
|
||||
else -> value
|
||||
}
|
||||
/**
|
||||
* Returns the encapsulated value if this instance represents [success][SuccessOrFailure.isSuccess] or the
|
||||
* result of [defaultValue] function if it is [failure][SuccessOrFailure.isFailure].
|
||||
*
|
||||
* Note, that an exception thrown by [defaultValue] function is rethrown by this function.
|
||||
*/
|
||||
@InlineOnly public inline fun <R, T : R> SuccessOrFailure<T>.getOrElse(defaultValue: () -> R): R =
|
||||
when(value) {
|
||||
is SuccessOrFailure.Failure -> defaultValue()
|
||||
else -> value as T
|
||||
}
|
||||
|
||||
// transformation
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@InlineOnly public inline fun <U, T> SuccessOrFailure<T>.map(block: (T) -> U): SuccessOrFailure<U> =
|
||||
if (isFailure) this as SuccessOrFailure<U>
|
||||
else runOrCatch { block(value) }
|
||||
/**
|
||||
* Returns the encapsulated result of the given [transform] function applied to encapsulated value
|
||||
* if this instance represents [success][SuccessOrFailure.isSuccess] or the
|
||||
* original encapsulated exception if it is [failure][SuccessOrFailure.isFailure].
|
||||
*
|
||||
* Note, that an exception thrown by [transform] function is rethrown by this function.
|
||||
* See [mapCatching] for an alternative that encapsulates exceptions.
|
||||
*/
|
||||
@InlineOnly public inline fun <R, T> SuccessOrFailure<T>.map(transform: (T) -> R): SuccessOrFailure<R> {
|
||||
contract {
|
||||
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return when(value) {
|
||||
is SuccessOrFailure.Failure -> SuccessOrFailure(value) // cannot cast here -- casts don't work (todo)
|
||||
else -> SuccessOrFailure.success(transform(value as T))
|
||||
}
|
||||
}
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@InlineOnly public inline fun <U, T: U> SuccessOrFailure<T>.handle(block: (Throwable) -> U): SuccessOrFailure<U> =
|
||||
if (isFailure) runOrCatch { block(exception) }
|
||||
else this
|
||||
/**
|
||||
* Returns the encapsulated result of the given [transform] function applied to encapsulated value
|
||||
* if this instance represents [success][SuccessOrFailure.isSuccess] or the
|
||||
* original encapsulated exception if it is [failure][SuccessOrFailure.isFailure].
|
||||
*
|
||||
* Any exception thrown by [transform] function is caught, encapsulated as a failure and returned by this function.
|
||||
* See [map] for an alternative that rethrows exceptions.
|
||||
*/
|
||||
@InlineOnly public inline fun <R, T> SuccessOrFailure<T>.mapCatching(transform: (T) -> R): SuccessOrFailure<R> {
|
||||
contract {
|
||||
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return when(value) {
|
||||
is SuccessOrFailure.Failure -> SuccessOrFailure(value) // cannot cast here -- casts don't work (todo)
|
||||
else -> runCatching { transform(value as T) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated result of the given [transform] function applied to encapsulated exception
|
||||
* if this instance represents [failure][SuccessOrFailure.isFailure] or the
|
||||
* original encapsulated value if it is [success][SuccessOrFailure.isSuccess].
|
||||
*
|
||||
* Note, that an exception thrown by [transform] function is rethrown by this function.
|
||||
* See [recoverCatching] for an alternative that encapsulates exceptions.
|
||||
*/
|
||||
@InlineOnly public inline fun <R, T: R> SuccessOrFailure<T>.recover(transform: (Throwable) -> R): SuccessOrFailure<R> {
|
||||
contract {
|
||||
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return when(value) {
|
||||
is SuccessOrFailure.Failure -> SuccessOrFailure.success(transform(value.exception))
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated result of the given [transform] function applied to encapsulated exception
|
||||
* if this instance represents [failure][SuccessOrFailure.isFailure] or the
|
||||
* original encapsulated value if it is [success][SuccessOrFailure.isSuccess].
|
||||
*
|
||||
* Any exception thrown by [transform] function is caught, encapsulated as a failure and returned by this function.
|
||||
* See [recover] for an alternative that rethrows exceptions.
|
||||
*/
|
||||
@InlineOnly public inline fun <R, T: R> SuccessOrFailure<T>.recoverCatching(transform: (Throwable) -> R): SuccessOrFailure<R> {
|
||||
contract {
|
||||
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
val value = value // workaround for inline classes BE bug
|
||||
return when(value) {
|
||||
is SuccessOrFailure.Failure -> runCatching { transform(value.exception) }
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
// "peek" onto value/exception and pipe
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onFailure(block: (Throwable) -> Unit): SuccessOrFailure<T> {
|
||||
if (isFailure) block(exception)
|
||||
/**
|
||||
* Performs the given [action] on encapsulated value if this instance represents [success][SuccessOrFailure.isSuccess].
|
||||
* Returns the original `SuccessOrFailure` unchanged.
|
||||
*/
|
||||
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onFailure(action: (Throwable) -> Unit): SuccessOrFailure<T> {
|
||||
contract {
|
||||
callsInPlace(action, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
if (value is SuccessOrFailure.Failure) action(value.exception)
|
||||
return this
|
||||
}
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onSuccess(block: (T) -> Unit): SuccessOrFailure<T> {
|
||||
if (isSuccess) block(value)
|
||||
/**
|
||||
* Performs the given [action] on encapsulated exception if this instance represents [failure][SuccessOrFailure.isFailure].
|
||||
* Returns the original `SuccessOrFailure` unchanged.
|
||||
*/
|
||||
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onSuccess(action: (T) -> Unit): SuccessOrFailure<T> {
|
||||
contract {
|
||||
callsInPlace(action, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
if (value !is SuccessOrFailure.Failure) action(value as T)
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user