Add catchingExceptions method to replace runCatching (#4797)

- Add `runCatchingExceptions` and `mapCatchingExceptions` to replace `runCatching` and `mapCatching`.
- Make `tryOrNull { ... }` catch only exceptions too.
- Apply the changes to the whole project.
- Add new Rust fakes for tests to handle the code that's now unblocked - previously it just threw an `UnsatisfiedLinkError` which we ignored.
- Add a new `detekt-rules` project with a `RunCatchingRule` to prevent `runCatching` and `mapCatching` usages.
This commit is contained in:
Jorge Martin Espinosa
2025-06-04 09:02:26 +02:00
committed by GitHub
parent 01d6012760
commit 58a3ea8b1f
144 changed files with 716 additions and 375 deletions

View File

@@ -7,11 +7,20 @@
package io.element.android.libraries.core.data
inline fun <A> tryOrNull(onError: ((Throwable) -> Unit) = { }, operation: () -> A): A? {
import kotlin.coroutines.cancellation.CancellationException
/**
* Can be used to catch [Exception]s in a block of code, returning `null` if an exception occurs.
*
* If the block throws a [CancellationException], it will be rethrown.
*/
inline fun <A> tryOrNull(onException: ((Exception) -> Unit) = { }, operation: () -> A): A? {
return try {
operation()
} catch (any: Throwable) {
onError.invoke(any)
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
onException.invoke(e)
null
}
}

View File

@@ -7,6 +7,61 @@
package io.element.android.libraries.core.extensions
import kotlin.coroutines.cancellation.CancellationException
/**
* Can be used to catch exceptions in a block of code and return a [Result].
* If the block throws a [CancellationException], it will be rethrown.
* If it throws any other exception, it will be wrapped in a [Result.failure].
*
* [Error]s are not caught by this function, as they are not meant to be caught in normal application flow.
*/
inline fun <T> runCatchingExceptions(
block: () -> T
): Result<T> {
return try {
Result.success(block())
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Result.failure(e)
}
}
/**
* Can be used to catch exceptions in a block of code and return a [Result].
* If the block throws a [CancellationException], it will be rethrown.
* If it throws any other exception, it will be wrapped in a [Result.failure].
*
* [Error]s are not caught by this function, as they are not meant to be caught in normal application flow.
*/
inline fun <T, R> T.runCatchingExceptions(
block: T.() -> R
): Result<R> {
return try {
Result.success(block())
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Result.failure(e)
}
}
/**
* Can be used to transform a [Result] into another [Result] by applying a [block] to the value if it is successful.
* If the original [Result] is a failure, the exception will be wrapped in a new [Result.failure].
*
* This is a safer version of [Result.mapCatching].
*/
inline fun <R, T> Result<T>.mapCatchingExceptions(
block: (T) -> R,
): Result<R> {
return fold(
onSuccess = { value -> runCatchingExceptions { block(value) } },
onFailure = { exception -> Result.failure(exception) }
)
}
/**
* Can be used to transform some Throwable into some other.
*/
@@ -33,12 +88,16 @@ inline fun <R, T> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> {
* @return The result of the transform or a caught exception wrapped in a [Result].
*/
inline fun <R, T> Result<T>.flatMapCatching(transform: (T) -> Result<R>): Result<R> {
return mapCatching(transform).fold(
return mapCatchingExceptions(transform).fold(
onSuccess = { it },
onFailure = { Result.failure(it) }
)
}
/**
* Can be used to execute a block of code after the [Result] has been processed, regardless of whether it was successful or not.
* The block receives the exception if there was one, or `null` if the result was successful.
*/
inline fun <T> Result<T>.finally(block: (exception: Throwable?) -> Unit): Result<T> {
onSuccess { block(null) }
onFailure(block)