From 8e972466aad8dfb4fabc1fa9869065966352b95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Fri, 25 Oct 2024 11:14:14 +0200 Subject: [PATCH] address PR feedback --- CHANGELOG.md | 6 +- .../kotlin/at/asitplus/KmmResult.kt | 164 ++++++++++++++++-- .../commonMain/kotlin/at/asitplus/NonFatal.kt | 12 +- .../src/commonTest/kotlin/KmmResultTest.kt | 50 +++++- 4 files changed, 199 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80c67d3..b6341a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,6 @@ ## 1.9.0 - add WasmJS target - add WasmWasi target (not or KmmResult-test, as Kotest does not support WASI yet) -- add `wrappingPlain`, which works just like `wrapping` but on a `Result` rather than a `KmmResult` to avoid instantiation overhead -- rename `catchingUnwrapped` to `catchingPlain` to avoid confusions with `wrapping` but keep the old name as deprecated alternative -- add `nonFatalOrThrow` +- Function Renames (old ones are still present, but deprecated) +- rename `wrapping` -> `catchingAs` but keep the old names as deprecated alternative + - add `catchingUnwrappedAs`, which works just like `catchingAs` but on a `Result` rather than a `KmmResult` to avoid instantiation overhead diff --git a/kmmresult/src/commonMain/kotlin/at/asitplus/KmmResult.kt b/kmmresult/src/commonMain/kotlin/at/asitplus/KmmResult.kt index bcb04fe..9bf4c42 100644 --- a/kmmresult/src/commonMain/kotlin/at/asitplus/KmmResult.kt +++ b/kmmresult/src/commonMain/kotlin/at/asitplus/KmmResult.kt @@ -12,6 +12,7 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.experimental.ExperimentalObjCName import kotlin.experimental.ExperimentalObjCRefinement +import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic import kotlin.native.HiddenFromObjC import kotlin.native.ObjCName @@ -305,26 +306,92 @@ inline fun T.catching(block: T.() -> R): KmmResult { } } +@Deprecated("Function name was misleading", ReplaceWith("catchingAs(type, block)")) +@Suppress("TooGenericExceptionCaught") +inline fun wrapping(asA: (String?, Throwable) -> E, block: () -> R): KmmResult = + catchingAs(asA, block) + /** * Runs the specified function [block], returning a [KmmResult]. * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. * - * Usage: `wrapping(asA = ::ThrowableType) { block }`. + * Usage: `catchingAs(type = ::ThrowableType) { block }`. */ @Suppress("TooGenericExceptionCaught") -inline fun wrapping(asA: (String?, Throwable) -> E, block: () -> R): KmmResult = - wrappingPlain(asA, block).wrap() +inline fun catchingAs(type: (String?, Throwable) -> E, block: () -> R): KmmResult = + catchingUnwrappedAs(type, block).wrap() + +/** + * Runs the specified function [block], returning a [Result]. + * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. + * + * Usage: `catchingAs(typeWithoutMessage = ::ThrowableType) { block }`. + */ +@Suppress("TooGenericExceptionCaught") +@JvmName("#catchingUnwrappedAs") +inline fun catchingUnwrappedAs( + crossinline typeWithoutMessage: (Throwable) -> E, + block: () -> R +): Result = catchingUnwrappedAs(type = { str, t -> + when (t) { + is E -> t + else -> typeWithoutMessage(t) + } +}, block) + +/** + * Runs the specified function [block], returning a [KmmResult]. + * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. + * + * Usage: `catchingAs(typeWithoutMessage = ::ThrowableType) { block }`. + */ +@JvmName("#catchingAs") +inline fun catchingAs( + crossinline typeWithoutMessage: (Throwable) -> E, + block: () -> R +): KmmResult = + catchingUnwrappedAs(typeWithoutMessage, block).wrap() /** * Runs the specified function [block], returning a [Result]. * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. * - * Usage: `wrapping(asA = ::ThrowableType) { block }`. + * Usage: `catchingAs(typeWithMessage = ::ThrowableType) { block }`. */ @Suppress("TooGenericExceptionCaught") -inline fun wrappingPlain(asA: (String?, Throwable) -> E, block: () -> R): Result { +inline fun catchingUnwrappedAs( + crossinline typeWithMessage: (String?) -> E, + block: () -> R +): Result = + catchingUnwrappedAs(type = { str, t -> + when (t) { + is E -> t + else -> typeWithMessage(str) + } + }, block) + +/** + * Runs the specified function [block], returning a [KmmResult]. + * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. + * + * Usage: `catchingAs(typeWithMessage = ::ThrowableType) { block }`. + */ +inline fun catchingAs( + crossinline typeWithMessage: (String?) -> E, + block: () -> R +): KmmResult = + catchingUnwrappedAs(typeWithMessage, block).wrap() + +/** + * Runs the specified function [block], returning a [Result]. + * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. + * + * Usage: `catchingUnwrappedAs(type = ::ThrowableType) { block }`. + */ +@Suppress("TooGenericExceptionCaught") +inline fun catchingUnwrappedAs(type: (String?, Throwable) -> E, block: () -> R): Result { contract { - callsInPlace(asA, InvocationKind.AT_MOST_ONCE) + callsInPlace(type, InvocationKind.AT_MOST_ONCE) // not EXACTLY_ONCE, because inside a try block! callsInPlace(block, InvocationKind.AT_MOST_ONCE) } @@ -334,35 +401,44 @@ inline fun wrappingPlain(asA: (String?, Throwable) -> Result.failure( when (e.nonFatalOrThrow()) { is E -> e - else -> asA(e.message, e) + else -> type(e.message, e) } ) } } +@Deprecated("Function name was misleading", ReplaceWith("catchingAs(type, block)")) +@Suppress("TooGenericExceptionCaught") +inline fun T.wrapping( + asA: (String?, Throwable) -> E, + block: T.() -> R +): KmmResult = catchingAs(asA, block) /** * Runs the specified function [block] with `this` as its receiver, returning a [KmmResult]. * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. * - * Usage: `wrapping(asA = ::ThrowableType) { block }`. + * Usage: `catchingAs(type = ::ThrowableType) { block }`. */ @Suppress("TooGenericExceptionCaught") -inline fun T.wrapping( - asA: (String?, Throwable) -> E, +inline fun T.catchingAs( + type: (String?, Throwable) -> E, block: T.() -> R -): KmmResult = this.wrappingPlain(asA, block).wrap() +): KmmResult = this.catchingUnwrappedAs(type, block).wrap() /** - * Runs the specified function [block] with `this` as its receiver, returning a [KmmResult]. + * Runs the specified function [block] with `this` as its receiver, returning a [Result]. * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. * - * Usage: `wrapping(asA = ::ThrowableType) { block }`. + * Usage: `catchingUnwrappedAd(type = ::ThrowableType) { block }`. */ @Suppress("TooGenericExceptionCaught") -inline fun T.wrappingPlain(asA: (String?, Throwable) -> E, block: T.() -> R): Result { +inline fun T.catchingUnwrappedAs( + type: (String?, Throwable) -> E, + block: T.() -> R +): Result { contract { - callsInPlace(asA, InvocationKind.AT_MOST_ONCE) + callsInPlace(type, InvocationKind.AT_MOST_ONCE) // not EXACTLY_ONCE, because inside a try block! callsInPlace(block, InvocationKind.AT_MOST_ONCE) } @@ -372,8 +448,64 @@ inline fun T.wrappingPlain(asA: (String?, Throwabl Result.failure( when (e.nonFatalOrThrow()) { is E -> e - else -> asA(e.message, e) + else -> type(e.message, e) } ) } } + +/** + * Runs the specified function [block] with `this` as its receiver, returning a [Result]. + * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. + * + * Usage: `catchingUnwrappedAd(typeWithMessage = ::ThrowableType) { block }`. + */ +@JvmName("#catchingUnwrappedAsWithString") +inline fun T.catchingUnwrappedAs( + typeWithMessage: (String?) -> E, + block: T.() -> R +): Result = this.catchingUnwrappedAs(type = { str, t -> + when (t) { + is E -> t + else -> typeWithMessage(str) + } +}, block) + +/** + * Runs the specified function [block] with `this` as its receiver, returning a [Result]. + * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. + * + * Usage: `catchingUnwrappedAd(typeWithoutMessage = ::ThrowableType) { block }`. + */ +inline fun T.catchingUnwrappedAs( + typeWithoutMessage: (Throwable) -> E, + block: T.() -> R +): Result = this.catchingUnwrappedAs(type = { str, t -> + when (t) { + is E -> t + else -> typeWithoutMessage(t) + } +}, block) + +/** + * Runs the specified function [block] with `this` as its receiver, returning a [KmmResult]. + * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. + * + * Usage: `catchingUnwrappedAd(typeWithMessage = ::ThrowableType) { block }`. + */ +@JvmName("#catchingAsWithString") +inline fun T.catchingAs( + typeWithMessage: (String?) -> E, + block: T.() -> R +): KmmResult = this.catchingUnwrappedAs(typeWithMessage, block).wrap() + +/** + * Runs the specified function [block] with `this` as its receiver, returning a [KmmResult]. + * Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type. + * + * Usage: `catchingUnwrappedAd(typeWithoutMessage = ::ThrowableType) { block }`. + */ +inline fun T.catchingAs( + typeWithoutMessage: (Throwable) -> E, + block: T.() -> R +): KmmResult = this.catchingUnwrappedAs(typeWithoutMessage, block).wrap() diff --git a/kmmresult/src/commonMain/kotlin/at/asitplus/NonFatal.kt b/kmmresult/src/commonMain/kotlin/at/asitplus/NonFatal.kt index 13c2f63..767bee6 100644 --- a/kmmresult/src/commonMain/kotlin/at/asitplus/NonFatal.kt +++ b/kmmresult/src/commonMain/kotlin/at/asitplus/NonFatal.kt @@ -24,11 +24,7 @@ inline fun Result.nonFatalOrThrow(): Result = this.onFailure { it.nonF * logic to avoid a dependency on Arrow for a single function. */ @Suppress("NOTHING_TO_INLINE") -inline fun catchingPlain(block: () -> T): Result = runCatching(block).nonFatalOrThrow() - -@Suppress("NOTHING_TO_INLINE") -@Deprecated("This function was badly named", ReplaceWith("catchingPlain(block)")) -inline fun catchingUnwrapped(block: () -> T): Result = catchingPlain(block) +inline fun catchingUnwrapped(block: () -> T): Result = runCatching(block).nonFatalOrThrow() /** * Non-fatal-only-catching version of stdlib's [runCatching] (calling the specified function [block] with `this` value @@ -38,8 +34,4 @@ inline fun catchingUnwrapped(block: () -> T): Result = catchingPlain(bloc * logic to avoid a dependency on Arrow for a single function. */ @Suppress("NOTHING_TO_INLINE") -inline fun T.catchingPlain(block: T.() -> R): Result = runCatching(block).nonFatalOrThrow() - -@Suppress("NOTHING_TO_INLINE") -@Deprecated("This function was badly named", ReplaceWith("catchingPlain { }")) -inline fun T.catchingUnwrapped(block: T.() -> R): Result = catchingPlain(block) +inline fun T.catchingUnwrapped(block: T.() -> R): Result = runCatching(block).nonFatalOrThrow() diff --git a/kmmresult/src/commonTest/kotlin/KmmResultTest.kt b/kmmresult/src/commonTest/kotlin/KmmResultTest.kt index 7989b09..4681df8 100644 --- a/kmmresult/src/commonTest/kotlin/KmmResultTest.kt +++ b/kmmresult/src/commonTest/kotlin/KmmResultTest.kt @@ -139,7 +139,7 @@ class KmmResultTest { @Test fun testWrapping() { class CustomException(message: String? = null, cause: Throwable? = null) : Throwable(message, cause) - wrapping(asA = ::CustomException) { + catchingAs(type = ::CustomException) { throw RuntimeException("foo") }.let { val ex = it.exceptionOrNull() @@ -148,7 +148,7 @@ class KmmResultTest { assertIs(ex.cause) assertEquals(ex.message, "foo") } - wrapping(asA = ::CustomException) { + catchingAs(type = ::CustomException) { throw CustomException("bar") }.let { val ex = it.exceptionOrNull() @@ -233,13 +233,55 @@ class KmmResultTest { } assertFailsWith(CancellationException::class) { - catchingPlain { throw CancellationException() } + catchingUnwrapped { throw CancellationException() } } assertFailsWith(CancellationException::class) { - "Receiver".catchingPlain { throw CancellationException() } + "Receiver".catchingUnwrapped { throw CancellationException() } } runCatching { throw IndexOutOfBoundsException() }.nonFatalOrThrow() catching { throw IndexOutOfBoundsException() } } + + @Test + fun testCatchingAs() { + + assertIs( + at.asitplus.catchingAs(type = ::IllegalStateException) { + throw NullPointerException() + }.exceptionOrNull() + ) + assertIs( + at.asitplus.catchingUnwrappedAs(type = ::IllegalStateException) { + throw NullPointerException() + }.exceptionOrNull() + ) + + assertIs( + at.asitplus.catchingAs(typeWithMessage = ::IndexOutOfBoundsException) { + throw NullPointerException() + }.exceptionOrNull() + ) + + assertIs( + at.asitplus.catchingUnwrappedAs(typeWithMessage = ::IndexOutOfBoundsException) { + throw NullPointerException() + }.exceptionOrNull() + ) + + class NestedOnlyException(t: Throwable) : Throwable(t) + + assertIs( + at.asitplus.catchingAs(typeWithoutMessage = ::NestedOnlyException) { + throw NullPointerException() + }.exceptionOrNull() + ) + + assertIs( + at.asitplus.catchingUnwrappedAs(typeWithoutMessage = ::NestedOnlyException) { + throw NullPointerException() + }.exceptionOrNull() + ) + + } }