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()
}