commonMain.ua.railian.data.result.DataResult.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of data-result-core Show documentation
Show all versions of data-result-core Show documentation
DataResult is a discriminated union that encapsulates a successful outcome
with a value of type [T] or a failure with an error of type [E].
@file:OptIn(ExperimentalContracts::class)
package ua.railian.data.result
import ua.railian.data.result.DataResult.Factory
import ua.railian.data.result.DataResult.Failure
import ua.railian.data.result.DataResult.Success
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 error of type [E].
*/
public sealed interface DataResult {
public data class Success internal constructor(
public val value: T,
) : DataResult
public data class Failure internal constructor(
public val error: E,
) : DataResult
/**
* Companion object for [DataResult] class that contains its factory functions
* [success], [failure], [build], etc.
*/
public companion object Factory
}
//region Factory
/**
* Returns an instance that encapsulates the given [value] as successful value.
*/
public fun Factory.success(value: T): Success = Success(value)
/**
* Returns an instance that encapsulates the given [error] as failure.
*/
public fun Factory.failure(error: E): Failure = Failure(error)
/**
* Returns an instance that encapsulates the given [value] as successful value
* and define the type [E] of available failed error
*/
public fun Factory.typedSuccess(value: T): Success = Success(value)
/**
* Returns an instance that encapsulates the given [error] as failure
* and define the type [T] of available successful value
*/
public fun Factory.typedFailure(error: E): Failure = Failure(error)
/**
* Builds a new [DataResult] by populating a [Factory] using the given [builder],
*/
public inline fun Factory.build(
builder: Factory.() -> DataResult,
): DataResult = builder()
/**
* Returns the result of the encapsulated value if original instance represents [success][Result.isSuccess]
* or returns the result received with [handleException] function if it is [failure][Result.isFailure].
*
* Note, that this function rethrows any [Throwable] exception thrown by [handleException] function.
*/
public inline fun Result.toDataResult(
handleException: Factory.(Throwable) -> DataResult,
): DataResult {
return fold(
onSuccess = { Factory.success(it) },
onFailure = { Factory.handleException(it) },
)
}
//endregion
//region Checks
/**
* Returns `true` if this instance represents a successful outcome.
* In this case [isFailure] returns `false`.
*/
public fun DataResult.isSuccess(): Boolean {
contract {
returns(true) implies (this@isSuccess is Success)
returns(false) implies (this@isSuccess is Failure)
}
return this is Success
}
/**
* Returns `true` if this instance represents a failed outcome.
* In this case [isSuccess] returns `false`.
*/
public fun DataResult.isFailure(): Boolean {
contract {
returns(true) implies (this@isFailure is Failure)
returns(false) implies (this@isFailure is Success)
}
return this is Failure
}
//endregion
//region Extractions
/**
* Returns the encapsulated error 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 DataResult.errorOrNull(): E? = when (this) {
is Success -> null
is Failure -> error
}
/**
* Returns the encapsulated value if this instance represents [success][isSuccess]
* or `null` if it is [failure][isFailure].
*
* This function is a shorthand for `getOrElse { null }` (see [getOrElse])
* or `fold(onSuccess = { it }, onFailure = { null })` (see [fold]).
*/
public fun DataResult.getOrNull(): T? = when (this) {
is Success -> value
is Failure -> null
}
/**
* Returns the encapsulated value if this instance represents [success][isSuccess]
* or the [defaultValue] if it is [failure][isFailure].
*
* This function is a shorthand for `getOrElse { defaultValue }` (see [getOrElse]).
*/
public fun DataResult.getOrDefault(
defaultValue: R,
): R = when (this) {
is Success -> value
is Failure -> defaultValue
}
/**
* Returns the encapsulated value if this instance represents [success][isSuccess]
* or the result of [onFailure] function for the encapsulated error if it is [failure][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]).
*/
public inline fun DataResult.getOrElse(
onFailure: (error: E) -> R,
): R = when (this) {
is Success -> value
is Failure -> onFailure(error)
}
/**
* Returns the result of [onSuccess] for the encapsulated value if this instance represents [success][isSuccess]
* or the result of [onFailure] function for the encapsulated error if it is [failure][isFailure].
*
* Note, that this function rethrows any [Throwable] exception thrown by [onSuccess] or by [onFailure] function.
*/
public inline fun DataResult.fold(
onSuccess: (value: T) -> R,
onFailure: (error: E) -> R,
): R = when (this) {
is Success -> onSuccess(value)
is Failure -> onFailure(error)
}
//endregion
//region Transformations
/**
* Returns the encapsulated result of the given [transform] function
* applied to the encapsulated value if this instance represents [success][isSuccess]
* or the original encapsulated error if it is [failure][isFailure].
*
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
*/
public inline fun DataResult.map(
transform: (value: T) -> R,
): DataResult = flatMap { success(transform(it)) }
/**
* Returns the result of the given [transform] function
* applied to the encapsulated value if this instance represents [success][isSuccess]
* or the original result if it is [failure][isFailure].
*
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
*/
public inline fun DataResult.flatMap(
transform: Factory.(value: T) -> DataResult,
): DataResult {
contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) }
@Suppress("UNCHECKED_CAST")
return when {
isFailure() -> this as DataResult
else -> Factory.transform(value)
}
}
/**
* Returns the encapsulated result of the given [transform] function
* applied to the encapsulated error if this instance represents [failure][isFailure]
* or the original encapsulated value if it is [success][isSuccess].
*
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
*/
public inline fun DataResult.recover(
transform: (error: E) -> R,
): Success = flatRecover { success(transform(it)) } as Success
/**
* Returns the result of the given [transform] function
* applied to the encapsulated error if this instance represents [failure][isFailure]
* or the original result if it is [success][isSuccess].
*
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
*/
public inline fun DataResult.flatRecover(
transform: Factory.(error: E) -> DataResult,
): DataResult {
contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) }
@Suppress("UNCHECKED_CAST")
return when {
isSuccess() -> this as DataResult
else -> Factory.transform(error)
}
}
/**
* Returns the result of the given [transform] function applied to the original result.
*
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
*/
public inline fun DataResult.transform(
transform: Factory.(result: DataResult) -> DataResult,
): DataResult {
contract { callsInPlace(transform, InvocationKind.EXACTLY_ONCE) }
return Factory.transform(this)
}
/**
* Returns encapsulated value of inner result if both results were successful,
* or one of encapsulated errors as a failure from these results.
*/
public fun DataResult, E2>.flatten(
): DataResult where E1 : F, E2 : F = flatMap { it }
//endregion
//region Side effects
/**
* Performs the given [action] on the encapsulated [T] value
* if this instance represents [success][isSuccess].
* Returns the original `DataResult` unchanged.
*/
public inline fun DataResult.onSuccess(
action: Success.(value: T) -> Unit,
): DataResult {
contract { callsInPlace(action, InvocationKind.AT_MOST_ONCE) }
if (isSuccess()) action(value)
return this
}
/**
* Performs the given [action] on the encapsulated [E] error
* if this instance represents [failure][isFailure].
* Returns the original `DataResult` unchanged.
*/
public inline fun DataResult.onFailure(
action: Failure.(error: E) -> Unit,
): DataResult {
contract { callsInPlace(action, InvocationKind.AT_MOST_ONCE) }
if (isFailure()) action(error)
return this
}
//endregion