rickclephas / NSErrorKt

A Kotlin Multiplatform Library to improve NSError interop

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

NSErrorKt

A Kotlin Multiplatform Library to improve NSError interop.

Why do we need this?

Kotlin already has Throwable to NSError interop for straightforward cases as described in the docs.
Though currently there is no way for application or library code to use this interop directly, meaning applications and libraries need to create their own instead.

Ktor

The Ktor Darwin client does this by wrapping a NSError in a custom Exception:

@OptIn(UnsafeNumber::class)
internal fun handleNSError(requestData: HttpRequestData, error: NSError): Throwable = when (error.code) {
    NSURLErrorTimedOut -> SocketTimeoutException(requestData)
    else -> DarwinHttpRequestException(error)
}

Which is a great solution for your Kotlin code as it allows you to access the wrapped NSError.
However once these Exceptions reach Swift/ObjC, Kotlin will convert them to a NSError.
Since Kotlin doesn't know this Exception is a wrapped NSError it will wrap the Exception in a NSError again.

This results in hard to log errors like this one.

KMP-NativeCoroutines

KMP-NativeCoroutines has a similar problem where it needs to convert Exceptions to NSErrors:

@OptIn(UnsafeNumber::class)
internal fun Throwable.asNSError(): NSError {
    val userInfo = mutableMapOf<Any?, Any>()
    userInfo["KotlinException"] = this.freeze()
    val message = message
    if (message != null) {
        userInfo[NSLocalizedDescriptionKey] = message
    }
    return NSError.errorWithDomain("KotlinException", 0.convert(), userInfo)
}

It produces similar NSErrors to the once Kotlin creates, but it doesn't unwrap an already wrapped NSError.
And in case such an NSError reaches Kotlin again it will be wrapped in an Exception instead of being unwrapped.

So depending on your code this might result in hard to log errors as well.

Compatibility

The latest version of the library uses Kotlin version 1.9.22.

Warning

This library exposes the Kotlin NSError interop logic provided by the Kotlin Native runtime.
These internals aren't part of any public API and are considered to be an implementation detail!
Use at your own risk and beware that using the library with other Kotlin versions might not work as expected.

Installation

Simply add the dependency to the appleMain source-set of your Kotlin module:

kotlin {
    sourceSets {
        appleMain {
            dependencies {
                api("com.rickclephas.kmp:nserror-kt:0.2.0")
            }
        }
    }
}

If you want to use the library in your Swift code, make sure to export it from your shared Kotlin module:

kotlin {
    targets.withType(KotlinNativeTarget::class) {
        if (!konanTarget.family.isAppleFamily) return@withType
        binaries.framework {
            export("com.rickclephas.kmp:nserror-kt:0.2.0")
        }
    }
}

Usage

To solve the interop issues this library exposes the Kotlin NSError interop logic to your application and library code.

Consisting of 3 extension functions:

and 2 extension properties:

asNSError

/**
 * Converts `this` [Throwable] to a [NSError].
 *
 * If `this` [Throwable] represents a [NSError], the original [NSError] is returned.
 * For other [Throwable]s a `KotlinException` [NSError] is returned:
 * ```
 * NSError.errorWithDomain("KotlinException", 0, mapOf(
 *     "KotlinException" to this,
 *     NSLocalizedDescriptionKey to this.message
 * ))
 * ```
 *
 * @see throwAsNSError
 * @see asThrowable
 */
fun Throwable.asNSError(): NSError

throwAsNSError

/**
 * Tries to convert `this` [Throwable] to a [NSError].
 *
 * If `this` [Throwable] is an instance of one of the [exceptionClasses] or their subclasses,
 * it is converted to a [NSError] in the same way [asNSError] would.
 *
 * Other [Throwable]s are considered unhandled and will cause program termination
 * in the same way a [Throws] annotated function would.
 *
 * @see asNSError
 * @see asThrowable
 * @see Throws
 */
fun Throwable.throwAsNSError(vararg exceptionClasses: KClass<out Throwable>): NSError

asThrowable

/**
 * Converts `this` [NSError] to a [Throwable].
 *
 * If `this` [NSError] represents a [Throwable], the original [Throwable] is returned.
 * For other [NSError]s an [ObjCErrorException] will be returned.
 *
 * @see asNSError
 */
fun NSError.asThrowable(): Throwable

isNSError

/**
 * Indicates if `this` [Throwable] represents a [NSError].
 */
val Throwable.isNSError: Boolean

isThrowable

/**
 * Indicates if `this` [NSError] represents a [Throwable].
 */
val NSError.isThrowable: Boolean

About

A Kotlin Multiplatform Library to improve NSError interop

License:MIT License


Languages

Language:Kotlin 100.0%