All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.ua.railian.data.result.DataResult.kt Maven / Gradle / Ivy

Go to download

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].

There is a newer version: 0.3.0-alpha06
Show newest version
@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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy