Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rickclephas/nserrorkt
A Kotlin Multiplatform Library to improve NSError interop
https://github.com/rickclephas/nserrorkt
kmm kmp kotlin kotlin-multiplatform kotlin-multiplatform-mobile kotlin-native
Last synced: 16 days ago
JSON representation
A Kotlin Multiplatform Library to improve NSError interop
- Host: GitHub
- URL: https://github.com/rickclephas/nserrorkt
- Owner: rickclephas
- License: mit
- Created: 2022-02-06T17:24:20.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2024-01-05T12:51:36.000Z (10 months ago)
- Last Synced: 2024-10-11T11:06:53.107Z (about 1 month ago)
- Topics: kmm, kmp, kotlin, kotlin-multiplatform, kotlin-multiplatform-mobile, kotlin-native
- Language: Kotlin
- Homepage:
- Size: 175 KB
- Stars: 53
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# 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](https://kotlinlang.org/docs/native-objc-interop.html#errors-and-exceptions).
Though currently there is [no way][KT-50539] for application or library code to use this interop directly,
meaning applications and libraries need to create their own instead.[KT-50539]: https://youtrack.jetbrains.com/issue/KT-50539
### Ktor
The [Ktor](https://ktor.io/) Darwin client does this by [wrapping][ktor-wrapping] a `NSError` in a custom `Exception`:
```kotlin
@OptIn(UnsafeNumber::class)
internal fun handleNSError(requestData: HttpRequestData, error: NSError): Throwable = when (error.code) {
NSURLErrorTimedOut -> SocketTimeoutException(requestData)
else -> DarwinHttpRequestException(error)
}
```[ktor-wrapping]: https://github.com/ktorio/ktor/blob/0877d6a91f3879b91853d412abd167054dccb333/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/TimeoutUtils.kt#L27-L31
Which is a great solution for your Kotlin code as it allows you to access the wrapped `NSError`.
However once these `Exception`s 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](https://kotlinlang.slack.com/archives/C3PQML5NU/p1640081553385000).
### KMP-NativeCoroutines
[KMP-NativeCoroutines](https://github.com/rickclephas/KMP-NativeCoroutines) has a similar problem
where it needs to [convert][kmp-nc-convert] `Exception`s to `NSError`s:
```kotlin
@OptIn(UnsafeNumber::class)
internal fun Throwable.asNSError(): NSError {
val userInfo = mutableMapOf()
userInfo["KotlinException"] = this.freeze()
val message = message
if (message != null) {
userInfo[NSLocalizedDescriptionKey] = message
}
return NSError.errorWithDomain("KotlinException", 0.convert(), userInfo)
}
```[kmp-nc-convert]: https://github.com/rickclephas/KMP-NativeCoroutines/blob/c1b7821bfb822cdc458d5fae1a1939abb3e7b1e0/kmp-nativecoroutines-core/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/NSError.kt#L17-L26
It produces similar `NSError`s 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
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
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:
- [`Throwable.asNSError`](#asnserror)
- [`Throwable.throwAsNSError`](#throwasnserror)
- [`NSError.asThrowable`](#asthrowable)and 2 extension properties:
- [`Throwable.isNSError`](#isnserror)
- [`NSError.isThrowable`](#isthrowable)### asNSError
``````kotlin
/**
* 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
``````kotlin
/**
* 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): NSError
``````### asThrowable
``````kotlin
/**
* 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
``````kotlin
/**
* Indicates if `this` [Throwable] represents a [NSError].
*/
val Throwable.isNSError: Boolean
``````### isThrowable
``````kotlin
/**
* Indicates if `this` [NSError] represents a [Throwable].
*/
val NSError.isThrowable: Boolean
``````