Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save just-4-fun/449c032df66a978e8c308258d8429888 to your computer and use it in GitHub Desktop.
Save just-4-fun/449c032df66a978e8c308258d8429888 to your computer and use it in GitHub Desktop.
Result<T> class implementation
/**
* 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>
@elect86
Copy link

elect86 commented Aug 10, 2018

Name .kt, it shall highlight it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment