2020-09-24 02:27:16 +00:00
|
|
|
package dev.inmo.micro_utils.coroutines
|
|
|
|
|
2020-12-22 16:45:36 +00:00
|
|
|
import kotlinx.coroutines.*
|
2020-12-22 09:16:06 +00:00
|
|
|
import kotlin.coroutines.CoroutineContext
|
|
|
|
import kotlin.coroutines.coroutineContext
|
2020-12-22 16:45:36 +00:00
|
|
|
import kotlin.reflect.KClass
|
2020-09-24 02:27:16 +00:00
|
|
|
|
|
|
|
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
2020-12-22 09:16:06 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This instance will be used in all calls of [safely] where exception handler has not been passed
|
|
|
|
*/
|
|
|
|
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
|
|
|
|
|
2020-12-22 09:21:25 +00:00
|
|
|
/**
|
|
|
|
* Key for [SafelyExceptionHandler] which can be used in [CoroutineContext.get] to get current default
|
|
|
|
* [SafelyExceptionHandler]
|
|
|
|
*/
|
2020-12-22 16:45:36 +00:00
|
|
|
class SafelyExceptionHandlerKey<T>() : CoroutineContext.Key<SafelyExceptionHandler<T>>
|
|
|
|
private val nothingSafelyEceptionHandlerKey = SafelyExceptionHandlerKey<Nothing>()
|
|
|
|
private val unitSafelyEceptionHandlerKey = SafelyExceptionHandlerKey<Unit>()
|
2020-12-22 09:21:25 +00:00
|
|
|
|
2020-12-22 16:45:36 +00:00
|
|
|
private val exceptionHandlersKeysCache = mutableMapOf<>()
|
2020-12-22 09:21:25 +00:00
|
|
|
/**
|
|
|
|
* Shortcut for creating instance of [SafelyExceptionHandlerKey]
|
|
|
|
*/
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
2020-12-22 16:45:36 +00:00
|
|
|
inline fun <T> safelyExceptionHandlerKey() = SafelyExceptionHandlerKey(T::class)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shortcut for getting instance of [SafelyExceptionHandler] from current [coroutineContext]
|
|
|
|
*/
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
|
|
suspend inline fun <reified T : Any> safelyExceptionHandler() = coroutineContext[safelyExceptionHandlerKey<T>()]
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
|
|
inline fun <reified T : Any> ExceptionHandler<T>.safelyExceptionHandler() = SafelyExceptionHandler(this, T::class)
|
2020-12-22 09:21:25 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrapper for [ExceptionHandler] which can be used in [CoroutineContext] to set local (for [CoroutineContext]) default
|
|
|
|
* [ExceptionHandler]. To get it use [CoroutineContext.get] with key [SafelyExceptionHandlerKey]
|
|
|
|
*
|
|
|
|
* @see SafelyExceptionHandlerKey
|
|
|
|
* @see ExceptionHandler
|
|
|
|
*/
|
2020-12-22 16:45:36 +00:00
|
|
|
class SafelyExceptionHandler<T : Any>(
|
|
|
|
val handler: ExceptionHandler<T>,
|
|
|
|
returnKClass: KClass<T>
|
2020-12-22 09:16:06 +00:00
|
|
|
) : CoroutineContext.Element {
|
2020-12-22 16:45:36 +00:00
|
|
|
override val key: CoroutineContext.Key<*> = SafelyExceptionHandlerKey(returnKClass)
|
2020-12-22 09:16:06 +00:00
|
|
|
}
|
|
|
|
|
2020-09-24 02:27:16 +00:00
|
|
|
/**
|
|
|
|
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
|
|
|
|
*
|
2020-12-22 09:21:25 +00:00
|
|
|
* Priorities of [ExceptionHandler]s:
|
|
|
|
*
|
|
|
|
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
|
|
|
|
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
|
|
|
|
* * [defaultSafelyExceptionHandler]
|
|
|
|
*
|
2020-09-24 02:27:16 +00:00
|
|
|
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
|
|
|
* exception will be available for catching
|
2020-12-22 09:16:06 +00:00
|
|
|
*
|
|
|
|
* @see defaultSafelyExceptionHandler
|
|
|
|
* @see safelyWithoutExceptions
|
2020-12-22 09:21:25 +00:00
|
|
|
* @see SafelyExceptionHandlerKey
|
|
|
|
* @see SafelyExceptionHandler
|
2020-09-24 02:27:16 +00:00
|
|
|
*/
|
|
|
|
suspend inline fun <T> safely(
|
2020-12-22 16:45:36 +00:00
|
|
|
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
|
|
|
block: suspend CoroutineScope.() -> T
|
2020-09-24 02:27:16 +00:00
|
|
|
): T {
|
2020-12-22 16:45:36 +00:00
|
|
|
val contextHandler = if (onException === defaultSafelyExceptionHandler) {
|
|
|
|
coroutineContext[nothingSafelyEceptionHandlerKey] ?:
|
|
|
|
safelyExceptionHandler<Unit>() ?.let { unitHandler ->
|
|
|
|
val handler = unitHandler.handler
|
|
|
|
SafelyExceptionHandler<T> {
|
|
|
|
handler(it)
|
|
|
|
onException(it)
|
|
|
|
}
|
|
|
|
} ?:
|
|
|
|
onException.safelyExceptionHandler()
|
|
|
|
} else {
|
|
|
|
onException.safelyExceptionHandler()
|
|
|
|
}
|
2020-09-24 02:27:16 +00:00
|
|
|
return try {
|
2020-12-22 16:45:36 +00:00
|
|
|
withContext(contextHandler) {
|
|
|
|
supervisorScope(block)
|
2020-12-22 09:16:06 +00:00
|
|
|
}
|
2020-12-22 16:45:36 +00:00
|
|
|
} catch (e: Throwable) {
|
|
|
|
contextHandler.handler(e)
|
2020-09-24 02:27:16 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-02 15:25:33 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Shortcut for [safely] without exception handler (instead of this you will receive null as a result)
|
|
|
|
*/
|
|
|
|
suspend inline fun <T> safelyWithoutExceptions(
|
|
|
|
noinline block: suspend CoroutineScope.() -> T
|
|
|
|
): T? = safely({ null }, block)
|