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

com.pusher.util.Result.kt Maven / Gradle / Ivy

package com.pusher.util

import com.pusher.platform.network.flatMap
import com.pusher.platform.network.map
import com.pusher.platform.network.toFuture
import com.pusher.util.Result.Companion.failure
import com.pusher.util.Result.Companion.success
import elements.Errors
import elements.Error
import java.util.concurrent.Future

fun  A.asSuccess(): Result = success(this)
fun  B.asFailure(): Result = failure(this)
fun  A?.orElse(block: () -> B): Result = when (this) {
    null -> failure(block())
    else -> success(this)
}

/**
 * Slight modification of the Either monadic pattern with semantics of failure and success for
 * left and right respectively.
 *
 * A [Result] instance can be instantiated:
 *  - Using one of the factory methods: `Result.success(value)` or `Result.failure(error)`
 *  - Extension functions: `value.asSuccess()` or `error.asFailure()`
 *  - From a nullable: `candidate.orElse { error }`
 */
sealed class Result {

    @Suppress("MemberVisibilityCanBePrivate") // public APIs
    companion object {
        @JvmStatic
        fun  success(value: A): Result = Success(value)
        @JvmStatic
        fun  failure(error: B): Result = Failure(error)
        @JvmStatic
        fun  failuresOf(vararg results: Result<*, B>): List =
            failuresOf(results.asList())
        @JvmStatic
        fun  failuresOf(results: Iterable>): List =
            results.mapNotNull { it as? Result.Failure }.map { it.error }
        @JvmStatic
        fun  successesOf(vararg results: Result): List =
            successesOf(results.asList())
        @JvmStatic
        fun  successesOf(results: Iterable>): List =
            results.mapNotNull { it as? Result.Success }.map { it.value }
    }

    data class Success internal constructor(val value: A) : Result()
    data class Failure internal constructor(val error: B) : Result()

    inline fun  fold(onFailure: (B) -> C, onSuccess: (A) -> C): C = when (this) {
        is Failure -> onFailure(error)
        is Success -> onSuccess(value)
    }

    /**
     * Creates a new result using [block] to convert when it is success.
     */
    inline fun  map(block: (A) -> C): Result = fold(
        onFailure = { failure(it) },
        onSuccess = { success(block(it)) }
    )

    /**
     * Creates a new result using [block] to convert when it is success.
     */
    inline fun  mapFailure(block: (B) -> C): Result = fold(
            onFailure = { failure(block(it)) },
            onSuccess = { it.asSuccess() }
    )

    /**
     * Creates a new result when it is a failure or uses [block] to create a new result.
     */
    inline fun  flatMap(block: (A) -> Result): Result = fold(
        onFailure = { failure(it) },
        onSuccess = { block(it) }
    )

    /**
     * If the result is a failure it will create a new success result using the provided [block].
     */
    fun recover(block: (B) -> A): A = fold(
        onFailure = block,
        onSuccess = { it }
    )

    /**
     * Similar to [recover] but allows to create a new result instance with [block] instead of
     * always recovering with a success.
     */
    fun flatRecover(block: (B) -> Result): Result = fold(
        onFailure = block,
        onSuccess = { it.asSuccess() }
    )

    /**
     * Unwrap a success or raise the failure as an exception
     */
    fun successOrThrow(): A = fold(
        onSuccess = { it },
        onFailure = { throw it }
    )
}

/**
 * Collapses a nested result into a simple one
 */
fun  Result, B>.flatten(): Result = fold(
    onFailure = { it.asFailure() },
    onSuccess = { it }
)

/**
 * Collapses a nested result into a simple one
 */
fun  Result.flatten(): B = fold(
    onFailure = { it },
    onSuccess = { it }
)

/**
 * [Result.map] when result is inside a [Future].
 */
fun  Future>.mapResult(block: (A) -> C): Future> =
    map { it.map(block) }

/**
 * [Result.recover] when result is inside a [Future].
 */
fun  Future>.recoverResult(block: (B) -> Result): Future> =
    map {
        it.fold(
            onFailure = { block(it) },
            onSuccess = { it.asSuccess() }
        )
    }

/**
 * Same as [recoverResult] but recovering with a future.
 */
fun  Future>.recoverFutureResult(block: (B) -> Future>): Future> =
    flatMap {
        it.fold(
            onFailure = { block(it) },
            onSuccess = { it.asSuccess().toFuture() }
        )
    }

/**
 * [Result.flatMap] when result is inside a [Future].
 */
fun  Future>.flatMapResult(block: (A) -> Result): Future> =
    map { it.flatMap(block) }

/**
 * Same as [flatMapResult] but returning a future when mapping.
 */
fun  Future>.flatMapFutureResult(block: (A) -> Future>) : Future> =
    flatMap {
        it.fold(
            onFailure = { it.asFailure().toFuture() },
            onSuccess = { block(it) }
        )
    }

/**
 * Transforms a list of results in to a result of a List.
 * Errors are composed using elements.Errors.compose()
 */
fun  Iterable>.collect(): Result, Error> =
    if (Result.failuresOf(this).isEmpty()) {
        Result.successesOf(this).asSuccess()
    } else {
        Errors.compose(Result.failuresOf(this)).asFailure()
    }