Created
May 9, 2018 09:24
-
-
Save just-4-fun/449c032df66a978e8c308258d8429888 to your computer and use it in GitHub Desktop.
Result<T> class implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* The result of some method call provides the caller with a requested [value] if the call is successful or an [exception] if failed. | |
* Replaces the usual approach to throw an exception or return the null in case of an execution failure. | |
* Note: [Result.exception] of the successful [Result] is always `null`. | |
*/ | |
@Suppress("UNCHECKED_CAST") | |
class Result<T> { | |
private var result: Any? = null | |
private var success = true | |
/** Creates a successful [Result] with [value]. Note: to create a [Result] with the [value] of [Throwable] the [Result.Success] fun can be used. */ | |
constructor (value: T) { | |
result = value | |
} | |
/** Creates a failed [Result] with [exception]. */ | |
constructor (exception: Throwable) { | |
result = exception | |
success = false | |
} | |
/** Indicates the successful result. */ | |
val isSuccess: Boolean get() = success | |
/** Indicates the failed result. */ | |
val isFailure: Boolean get() = !success | |
/** A value of a successful call or null otherwise. | |
* Note: if the type [T] is nullable a successful value can still be null. */ | |
val value: T? get() = if (success) result as T else null | |
/** Returns [value] in case of a success, otherwise throws [exception].*/ | |
val valueOrThrow: T get() = if (success) result as T else throw result as Throwable | |
/** Returns [value] in case of a success, otherwise throws [ClassCastException]. Can be used only if [isSuccess] is true. */ | |
val unsafeValue: T get() = result as T | |
/** exception of a failed call or null otherwise. */ | |
val exception: Throwable? get() = if (success) null else result as Throwable | |
/** Returns [exception] in case of a failure, otherwise throws [ClassCastException]. Can be used only if [isFailure] is true. */ | |
val unsafeException: Throwable get() = result as Throwable | |
/** Returns [value] in case of a success, otherwise [altValue].*/ | |
fun valueOr(altValue: T): T = if (success) result as T else altValue | |
/** Returns [value]. Serves destructuring purpose. */ | |
operator fun component1(): T? = if (success) result as T else null | |
/** Returns [exception]. Serves destructuring purpose. */ | |
operator fun component2(): Throwable? = if (success) null else result as Throwable | |
override fun toString(): String = "Result($result)" | |
override fun hashCode(): Int = result?.hashCode() ?: 0 | |
override fun equals(other: Any?) = this === other || (other is Result<*> && other.result == result) | |
/** Companion object provides [Result] utilities.*/ | |
companion object { | |
private var unit: Result<Unit>? = null | |
/** Constant shorthand for Result(Unit) object. */ | |
val ofUnit: Result<Unit> get() = unit ?: Result(Unit).apply { unit = this } | |
/** Handles errors occurred during a [Result] { ... } function execution. For debugging purposes.*/ | |
var errorHandler: ((Throwable) -> Unit)? = null | |
/** Creates a successful [Result] with [value]. Same as calling the constructor with non-[Throwable]. */ | |
fun <T: Any> Success(value: T): Result<T> = Result(value) | |
/** Creates a failed [Result] with [exception]. Same as calling the constructor with [Throwable]. */ | |
fun <T> Failure(exception: Throwable): Result<T> = Result(exception) | |
} | |
} | |
/* Extensions */ | |
/** Returns [value] in case of a success, otherwise a result of the [code] call.*/ | |
inline fun <T> Result<T>.valueOr(code: (Throwable) -> T): T = if (isSuccess) unsafeValue else code(unsafeException) | |
/** Executes [code] in case of a success with [value] as the argument. Returns this. */ | |
inline fun <T> Result<T>.onSuccess(code: (T) -> Unit): Result<T> { | |
if (isSuccess) code(unsafeValue) | |
return this | |
} | |
/** If this is a success, returns the successful [Result] of the [code] execution. Returns this otherwise. */ | |
inline fun <T, R> Result<T>.mapSuccess(code: (T) -> R): Result<R> { | |
return if (isSuccess) try { | |
Result(code(unsafeValue)) | |
} catch (x: Throwable) { | |
Result<R>(x) | |
} | |
else this as Result<R> | |
} | |
/** In case of success, returns result of [code] execution. Returns this otherwise. */ | |
inline fun <T, R> Result<T>.flatMapSuccess(code: (T) -> Result<R>): Result<R> { | |
return if (isSuccess) code(unsafeValue) else this as Result<R> | |
} | |
/** Executes [code] in case of a failure with [exception] as the argument. Returns this. */ | |
inline fun <T> Result<T>.onFailure(code: (Throwable) -> Unit): Result<T> { | |
if (isFailure) code(unsafeException) | |
return this | |
} | |
/** Executes [code] in case of a failure if [Exception] is [F]. Returns this. */ | |
inline fun <T, reified F: Throwable> Result<T>.onFailureOf(code: (F) -> Unit): Result<T> { | |
if (isFailure) unsafeException.let { if (it is F) code(it) } | |
return this | |
} | |
/** Executes [code] in case of a failure if [Exception] is not [F]. Returns this. */ | |
inline fun <T, reified F: Throwable> Result<T>.onFailureOfNot(code: (Throwable) -> Unit): Result<T> { | |
if (isFailure) unsafeException.let { if (it !is F) code(it as Throwable) } | |
return this | |
} | |
/** Wraps [exception] into the [Throwable] returned by [code] as its cause. */ | |
inline fun <T> Result<T>.wrapFailure(code: (Throwable) -> Throwable): Result<T> { | |
return if (isSuccess) this else (unsafeException).let { x -> | |
Result<T>(code(x).also { if (it.cause == null) it.initCause(x) }) | |
} | |
} | |
/** If this is a failure, returns the successful [Result] of the [code] execution. Returns this otherwise. */ | |
inline fun <T> Result<T>.mapFailure(code: (Throwable) -> T): Result<T> { | |
return if (isSuccess) this else try { | |
Result(code(unsafeException)) | |
} catch (x: Throwable) { | |
Result<T>(x) | |
} | |
} | |
/** If this is a failure, returns the successful [Result] of the [code] execution. Returns this otherwise. */ | |
inline fun <T, reified F: Throwable> Result<T>.mapFailureOf(code: (F) -> T): Result<T> { | |
return if (isSuccess) this else unsafeException.let { | |
if (it is F) Result(code(it)) else this | |
} | |
} | |
/** If this is a failure, returns the successful [Result] of the [code] execution. Returns this otherwise. */ | |
inline fun <T, reified F: Throwable> Result<T>.mapFailureOfNot(code: (Throwable) -> T): Result<T> { | |
return if (isSuccess) this else unsafeException.let { | |
if (it !is F) Result(code(it as Throwable)) else this | |
} | |
} | |
/** In case of failure, returns result of [code] execution. Returns this otherwise. */ | |
inline fun <T> Result<T>.flatMapFailure(code: (Throwable) -> Result<T>): Result<T> { | |
if (isFailure) return code(unsafeException) | |
return this | |
} | |
/** Executes [code] in a try/catch block and returns a failed [Result] if an [Result.exception] was thrown, otherwise returns successful [Result] with [Result.value] assigned. An error can be handled with [errorHandler]. */ | |
inline fun <T> Result(code: () -> T): Result<T> = try { | |
Result(code()) | |
} catch (x: Throwable) { | |
errorHandler?.invoke(x) | |
Result(x) | |
} | |
/** Flattens nested [Result]. */ | |
@Suppress("UNCHECKED_CAST") | |
fun <T> Result<Result<T>>.flatten(): Result<T> = value ?: this as Result<T> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Name
.kt
, it shall highlight it