Deprecate Result.of in favour of runCatching factory function

Matches Kotlin's stdlib and becomes top-level
This commit is contained in:
Michael Bull 2019-08-24 00:07:27 +01:00
parent a8098db1c7
commit 586b260683
4 changed files with 62 additions and 25 deletions

View File

@ -15,6 +15,7 @@ import com.github.michaelbull.result.map
import com.github.michaelbull.result.mapAll import com.github.michaelbull.result.mapAll
import com.github.michaelbull.result.mapBoth import com.github.michaelbull.result.mapBoth
import com.github.michaelbull.result.mapError import com.github.michaelbull.result.mapError
import com.github.michaelbull.result.runCatching
import com.github.michaelbull.result.toResultOr import com.github.michaelbull.result.toResultOr
import java.sql.SQLTimeoutException import java.sql.SQLTimeoutException
@ -22,7 +23,7 @@ object CustomerService {
private val repository = InMemoryCustomerRepository() private val repository = InMemoryCustomerRepository()
fun getAll(): Result<Collection<Customer>, DomainMessage> { fun getAll(): Result<Collection<Customer>, DomainMessage> {
return Result.of(repository::findAll) return runCatching(repository::findAll)
.mapError(::exceptionToDomainMessage) .mapError(::exceptionToDomainMessage)
.mapAll(Customer.Companion::from) .mapAll(Customer.Companion::from)
} }
@ -40,12 +41,12 @@ object CustomerService {
} }
private fun updateCustomer(entity: CustomerEntity, old: Customer, new: Customer) = private fun updateCustomer(entity: CustomerEntity, old: Customer, new: Customer) =
Result.of { repository.update(entity) } runCatching { repository.update(entity) }
.map { differenceBetween(old, new) } .map { differenceBetween(old, new) }
.mapError(::exceptionToDomainMessage) .mapError(::exceptionToDomainMessage)
private fun createCustomer(entity: CustomerEntity) = private fun createCustomer(entity: CustomerEntity) =
Result.of { repository.insert(entity) } runCatching { repository.insert(entity) }
.map { CustomerCreated } .map { CustomerCreated }
.mapError(::exceptionToDomainMessage) .mapError(::exceptionToDomainMessage)

View File

@ -0,0 +1,53 @@
package com.github.michaelbull.result
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Calls the specified function [block] and returns its encapsulated result if
* invocation was successful, catching and encapsulating any thrown exception
* as a failure.
*/
inline fun <V> runCatching(block: () -> V): Result<V, Throwable> {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return try {
Ok(block())
} catch (e: Throwable) {
Err(e)
}
}
/**
* Calls the specified function [block] with `this` value as its receiver and
* returns its encapsulated result if invocation was successful, catching and
* encapsulating any thrown exception as a failure.
*/
inline infix fun <T, V> T.runCatching(block: T.() -> V): Result<V, Throwable> {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return try {
Ok(block())
} catch (e: Throwable) {
Err(e)
}
}
/**
* Converts a nullable of type [V] to a [Result]. Returns [Ok] if the value is
* non-null, otherwise the supplied [error].
*/
inline infix fun <V, E> V?.toResultOr(error: () -> E): Result<V, E> {
contract {
callsInPlace(error, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
null -> Err(error())
else -> Ok(this)
}
}

View File

@ -1,8 +1,5 @@
package com.github.michaelbull.result package com.github.michaelbull.result
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/** /**
* [Result] is a type that represents either success ([Ok]) or failure ([Err]). * [Result] is a type that represents either success ([Ok]) or failure ([Err]).
* *
@ -17,6 +14,7 @@ sealed class Result<out V, out E> {
* Invokes a [function] and wraps it in a [Result], returning an [Err] * Invokes a [function] and wraps it in a [Result], returning an [Err]
* if an [Exception] was thrown, otherwise [Ok]. * if an [Exception] was thrown, otherwise [Ok].
*/ */
@Deprecated("Use top-level runCatching instead", ReplaceWith("runCatching(function)"))
inline fun <V> of(function: () -> V): Result<V, Exception> { inline fun <V> of(function: () -> V): Result<V, Exception> {
return try { return try {
Ok(function.invoke()) Ok(function.invoke())
@ -36,18 +34,3 @@ data class Ok<out V>(val value: V) : Result<V, Nothing>()
* Represents a failed [Result], containing an [error]. * Represents a failed [Result], containing an [error].
*/ */
data class Err<out E>(val error: E) : Result<Nothing, E>() data class Err<out E>(val error: E) : Result<Nothing, E>()
/**
* Converts a nullable of type [V] to a [Result]. Returns [Ok] if the value is
* non-null, otherwise the supplied [error].
*/
inline infix fun <V, E> V?.toResultOr(error: () -> E): Result<V, E> {
contract {
callsInPlace(error, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
null -> Err(error())
else -> Ok(this)
}
}

View File

@ -4,12 +4,12 @@ import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertSame import kotlin.test.assertSame
class ResultTest { class FactoryTest {
class Of { class RunCatching {
@Test @Test
fun returnsOkIfInvocationSuccessful() { fun returnsOkIfInvocationSuccessful() {
val callback = { "example" } val callback = { "example" }
val result = Result.of(callback) val result = runCatching(callback)
assertEquals( assertEquals(
expected = "example", expected = "example",
@ -21,7 +21,7 @@ class ResultTest {
fun returnsErrIfInvocationFails() { fun returnsErrIfInvocationFails() {
val exception = IllegalArgumentException("throw me") val exception = IllegalArgumentException("throw me")
val callback = { throw exception } val callback = { throw exception }
val result = Result.of(callback) val result = runCatching(callback)
assertSame( assertSame(
expected = exception, expected = exception,