Skip to content

Commit

Permalink
address PR feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
JesusMcCloud committed Oct 25, 2024
1 parent e4b47de commit 8e97246
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 33 deletions.
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
164 changes: 148 additions & 16 deletions kmmresult/src/commonMain/kotlin/at/asitplus/KmmResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -305,26 +306,92 @@ inline fun <T, R> T.catching(block: T.() -> R): KmmResult<R> {
}
}

@Deprecated("Function name was misleading", ReplaceWith("catchingAs(type, block)"))
@Suppress("TooGenericExceptionCaught")
inline fun <reified E : Throwable, R> wrapping(asA: (String?, Throwable) -> E, block: () -> R): KmmResult<R> =
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 <reified E : Throwable, R> wrapping(asA: (String?, Throwable) -> E, block: () -> R): KmmResult<R> =
wrappingPlain(asA, block).wrap()
inline fun <reified E : Throwable, R> catchingAs(type: (String?, Throwable) -> E, block: () -> R): KmmResult<R> =
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 <reified E : Throwable, R> catchingUnwrappedAs(
crossinline typeWithoutMessage: (Throwable) -> E,
block: () -> R
): Result<R> = 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 <reified E : Throwable, R> catchingAs(
crossinline typeWithoutMessage: (Throwable) -> E,
block: () -> R
): KmmResult<R> =
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 <reified E : Throwable, R> wrappingPlain(asA: (String?, Throwable) -> E, block: () -> R): Result<R> {
inline fun <reified E : Throwable, R> catchingUnwrappedAs(
crossinline typeWithMessage: (String?) -> E,
block: () -> R
): Result<R> =
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 <reified E : Throwable, R> catchingAs(
crossinline typeWithMessage: (String?) -> E,
block: () -> R
): KmmResult<R> =
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 <reified E : Throwable, R> catchingUnwrappedAs(type: (String?, Throwable) -> E, block: () -> R): Result<R> {
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)
}
Expand All @@ -334,35 +401,44 @@ inline fun <reified E : Throwable, R> 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 <reified E : Throwable, T, R> T.wrapping(
asA: (String?, Throwable) -> E,
block: T.() -> R
): KmmResult<R> = 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 <reified E : Throwable, T, R> T.wrapping(
asA: (String?, Throwable) -> E,
inline fun <reified E : Throwable, T, R> T.catchingAs(
type: (String?, Throwable) -> E,
block: T.() -> R
): KmmResult<R> = this.wrappingPlain(asA, block).wrap()
): KmmResult<R> = 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 <reified E : Throwable, T, R> T.wrappingPlain(asA: (String?, Throwable) -> E, block: T.() -> R): Result<R> {
inline fun <reified E : Throwable, T, R> T.catchingUnwrappedAs(
type: (String?, Throwable) -> E,
block: T.() -> R
): Result<R> {
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)
}
Expand All @@ -372,8 +448,64 @@ inline fun <reified E : Throwable, T, R> 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 <reified E : Throwable, T, R> T.catchingUnwrappedAs(
typeWithMessage: (String?) -> E,
block: T.() -> R
): Result<R> = 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 <reified E : Throwable, T, R> T.catchingUnwrappedAs(
typeWithoutMessage: (Throwable) -> E,
block: T.() -> R
): Result<R> = 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 <reified E : Throwable, T, R> T.catchingAs(
typeWithMessage: (String?) -> E,
block: T.() -> R
): KmmResult<R> = 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 <reified E : Throwable, T, R> T.catchingAs(
typeWithoutMessage: (Throwable) -> E,
block: T.() -> R
): KmmResult<R> = this.catchingUnwrappedAs(typeWithoutMessage, block).wrap()
12 changes: 2 additions & 10 deletions kmmresult/src/commonMain/kotlin/at/asitplus/NonFatal.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@ inline fun <T> Result<T>.nonFatalOrThrow(): Result<T> = this.onFailure { it.nonF
* logic to avoid a dependency on Arrow for a single function.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> catchingPlain(block: () -> T): Result<T> = runCatching(block).nonFatalOrThrow()

@Suppress("NOTHING_TO_INLINE")
@Deprecated("This function was badly named", ReplaceWith("catchingPlain(block)"))
inline fun <T> catchingUnwrapped(block: () -> T): Result<T> = catchingPlain(block)
inline fun <T> catchingUnwrapped(block: () -> T): Result<T> = runCatching(block).nonFatalOrThrow()

/**
* Non-fatal-only-catching version of stdlib's [runCatching] (calling the specified function [block] with `this` value
Expand All @@ -38,8 +34,4 @@ inline fun <T> catchingUnwrapped(block: () -> T): Result<T> = catchingPlain(bloc
* logic to avoid a dependency on Arrow for a single function.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T, R> T.catchingPlain(block: T.() -> R): Result<R> = runCatching(block).nonFatalOrThrow()

@Suppress("NOTHING_TO_INLINE")
@Deprecated("This function was badly named", ReplaceWith("catchingPlain { }"))
inline fun <T, R> T.catchingUnwrapped(block: T.() -> R): Result<R> = catchingPlain(block)
inline fun <T, R> T.catchingUnwrapped(block: T.() -> R): Result<R> = runCatching(block).nonFatalOrThrow()
50 changes: 46 additions & 4 deletions kmmresult/src/commonTest/kotlin/KmmResultTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -148,7 +148,7 @@ class KmmResultTest {
assertIs<RuntimeException>(ex.cause)
assertEquals(ex.message, "foo")
}
wrapping(asA = ::CustomException) {
catchingAs(type = ::CustomException) {
throw CustomException("bar")
}.let {
val ex = it.exceptionOrNull()
Expand Down Expand Up @@ -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<IllegalStateException>(
at.asitplus.catchingAs(type = ::IllegalStateException) {
throw NullPointerException()
}.exceptionOrNull()
)
assertIs<IllegalStateException>(
at.asitplus.catchingUnwrappedAs(type = ::IllegalStateException) {
throw NullPointerException()
}.exceptionOrNull()
)

assertIs<IndexOutOfBoundsException>(
at.asitplus.catchingAs(typeWithMessage = ::IndexOutOfBoundsException) {
throw NullPointerException()
}.exceptionOrNull()
)

assertIs<IndexOutOfBoundsException>(
at.asitplus.catchingUnwrappedAs(typeWithMessage = ::IndexOutOfBoundsException) {
throw NullPointerException()
}.exceptionOrNull()
)

class NestedOnlyException(t: Throwable) : Throwable(t)

assertIs<NestedOnlyException>(
at.asitplus.catchingAs(typeWithoutMessage = ::NestedOnlyException) {
throw NullPointerException()
}.exceptionOrNull()
)

assertIs<NestedOnlyException>(
at.asitplus.catchingUnwrappedAs(typeWithoutMessage = ::NestedOnlyException) {
throw NullPointerException()
}.exceptionOrNull()
)

}
}

0 comments on commit 8e97246

Please sign in to comment.