Skip to content

Commit c8f3e0b

Browse files
authored
Rework internal sync error handling (#1490)
1 parent e7b3c9e commit c8f3e0b

File tree

36 files changed

+430
-621
lines changed

36 files changed

+430
-621
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ childA == childC
2222
```
2323

2424
### Enhancements
25+
* Fulltext queries now support prefix search by using the * operator, like `description TEXT 'alex*'`. (Core issue [#6860](https://github.com/realm/realm-core/issues/6860))
2526
* Realm model classes now generate custom `toString`, `equals` and `hashCode` implementations. This makes it possible to compare by object reference across multiple collections. Note that two objects at different versions will not be considered equal, even
2627
if the content is the same. Custom implementations of these methods will be respected if they are present. (Issue [#1097](https://github.com/realm/realm-kotlin/issues/1097))
2728
* Support for performing geospatial queries using the new classes: `GeoPoint`, `GeoCircle`, `GeoBox`, and `GeoPolygon`. See `GeoPoint` documentation on how to persist locations. (Issue [#1403](https://github.com/realm/realm-kotlin/pull/1403))
@@ -46,7 +47,7 @@ if the content is the same. Custom implementations of these methods will be resp
4647
* Minimum Android SDK: 16.
4748

4849
### Internal
49-
* None.
50+
* Updated to Realm Core 13.19.0, commit ea7c5d5e2900b8411a295aea3d1aa56aa55fff1d.
5051

5152

5253
## 1.10.2 (2023-07-21)

packages/cinterop/src/androidInstrumentedTest/kotlin/io/realm/kotlin/test/sync/SyncEnumTests.kt

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,29 @@
1616

1717
package io.realm.kotlin.test.sync
1818

19+
import io.realm.kotlin.internal.interop.CategoryFlags
1920
import io.realm.kotlin.internal.interop.ErrorCategory
2021
import io.realm.kotlin.internal.interop.ErrorCode
2122
import io.realm.kotlin.internal.interop.realm_auth_provider_e
2223
import io.realm.kotlin.internal.interop.realm_errno_e
2324
import io.realm.kotlin.internal.interop.realm_error_category_e
2425
import io.realm.kotlin.internal.interop.realm_sync_client_metadata_mode_e
2526
import io.realm.kotlin.internal.interop.realm_sync_connection_state_e
26-
import io.realm.kotlin.internal.interop.realm_sync_errno_client_e
2727
import io.realm.kotlin.internal.interop.realm_sync_errno_connection_e
2828
import io.realm.kotlin.internal.interop.realm_sync_errno_session_e
29-
import io.realm.kotlin.internal.interop.realm_sync_error_category_e
3029
import io.realm.kotlin.internal.interop.realm_sync_session_resync_mode_e
3130
import io.realm.kotlin.internal.interop.realm_sync_session_state_e
3231
import io.realm.kotlin.internal.interop.realm_user_state_e
32+
import io.realm.kotlin.internal.interop.realm_web_socket_errno_e
3333
import io.realm.kotlin.internal.interop.sync.AuthProvider
3434
import io.realm.kotlin.internal.interop.sync.CoreConnectionState
3535
import io.realm.kotlin.internal.interop.sync.CoreSyncSessionState
3636
import io.realm.kotlin.internal.interop.sync.CoreUserState
3737
import io.realm.kotlin.internal.interop.sync.MetadataMode
38-
import io.realm.kotlin.internal.interop.sync.ProtocolClientErrorCode
39-
import io.realm.kotlin.internal.interop.sync.ProtocolConnectionErrorCode
40-
import io.realm.kotlin.internal.interop.sync.ProtocolSessionErrorCode
41-
import io.realm.kotlin.internal.interop.sync.SyncErrorCodeCategory
38+
import io.realm.kotlin.internal.interop.sync.SyncConnectionErrorCode
39+
import io.realm.kotlin.internal.interop.sync.SyncSessionErrorCode
4240
import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode
41+
import io.realm.kotlin.internal.interop.sync.WebsocketErrorCode
4342
import org.junit.Test
4443
import kotlin.reflect.KClass
4544
import kotlin.test.BeforeTest
@@ -59,10 +58,11 @@ class SyncEnumTests {
5958
}
6059

6160
@Test
62-
fun appErrorCategory() {
61+
fun errorCategory() {
6362
checkEnum(realm_error_category_e::class) { nativeValue ->
6463
ErrorCategory.of(nativeValue)
6564
}
65+
assertEquals(ErrorCategory.values().size, CategoryFlags.CATEGORY_ORDER.size)
6666
}
6767

6868
@Test
@@ -94,30 +94,23 @@ class SyncEnumTests {
9494
}
9595

9696
@Test
97-
fun protocolClientErrorCode() {
98-
checkEnum(realm_sync_errno_client_e::class) { nativeValue ->
99-
ProtocolClientErrorCode.of(nativeValue)
100-
}
101-
}
102-
103-
@Test
104-
fun protocolConnectionErrorCode() {
97+
fun syncConnectionErrorCode() {
10598
checkEnum(realm_sync_errno_connection_e::class) { nativeValue ->
106-
ProtocolConnectionErrorCode.of(nativeValue)
99+
SyncConnectionErrorCode.of(nativeValue)
107100
}
108101
}
109102

110103
@Test
111-
fun protocolSessionErrorCode() {
104+
fun syncSessionErrorCode() {
112105
checkEnum(realm_sync_errno_session_e::class) { nativeValue ->
113-
ProtocolSessionErrorCode.of(nativeValue)
106+
SyncSessionErrorCode.of(nativeValue)
114107
}
115108
}
116109

117110
@Test
118-
fun syncErrorCodeCategory() {
119-
checkEnum(realm_sync_error_category_e::class) { nativeValue ->
120-
SyncErrorCodeCategory.of(nativeValue)
111+
fun websocketErrorCode() {
112+
checkEnum(realm_web_socket_errno_e::class) { nativeValue ->
113+
WebsocketErrorCode.of(nativeValue)
121114
}
122115
}
123116

packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package io.realm.kotlin.internal.interop
1919
import io.realm.kotlin.internal.interop.sync.AppError
2020
import io.realm.kotlin.internal.interop.sync.CoreSubscriptionSetState
2121
import io.realm.kotlin.internal.interop.sync.SyncError
22-
import io.realm.kotlin.internal.interop.sync.SyncErrorCode
2322

2423
// TODO Could be replace by lambda. See realm_app_config_new networkTransportFactory for example.
2524
interface Callback<T : RealmNativePointer> {
@@ -39,7 +38,7 @@ fun interface SyncErrorCallback {
3938

4039
// Interface exposed towards `library-sync`
4140
interface SyncSessionTransferCompletionCallback {
42-
fun invoke(error: SyncErrorCode?)
41+
fun invoke(error: CoreError?)
4342
}
4443

4544
interface LogCallback {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.realm.kotlin.internal.interop
2+
3+
/**
4+
* Wrapper for C-API `realm_error_t`.
5+
* See https://github.com/realm/realm-core/blob/master/src/realm.h#L231
6+
*/
7+
class CoreError(
8+
categoriesNativeValue: Int,
9+
val errorCodeNativeValue: Int,
10+
messageNativeValue: String?,
11+
) {
12+
val categories: CategoryFlags = CategoryFlags((categoriesNativeValue))
13+
val errorCode: ErrorCode? = ErrorCode.of(errorCodeNativeValue)
14+
val message = messageNativeValue
15+
16+
operator fun contains(category: ErrorCategory): Boolean = category in categories
17+
}
18+
19+
data class CategoryFlags(val categoryFlags: Int) {
20+
21+
companion object {
22+
/**
23+
* See error code mapping to categories here:
24+
* https://github.com/realm/realm-core/blob/master/src/realm/error_codes.cpp#L29
25+
*
26+
* In most cases, only 1 category is assigned, but some errors have multiple. So instead of
27+
* overwhelming the user with many categories, we only select the most important to show
28+
* in the error message. "important" is of course tricky to define, but generally
29+
* we consider vague categories like [ErrorCategory.RLM_ERR_CAT_RUNTIME] as less important
30+
* than more specific ones like [ErrorCategory.RLM_ERR_CAT_JSON_ERROR].
31+
*
32+
* In the current implementation, categories between index 0 and 7 are considered equal
33+
* and the order is somewhat arbitrary. No error codes has multiple of these categories
34+
* associated either.
35+
*/
36+
val CATEGORY_ORDER: List<ErrorCategory> = listOf(
37+
ErrorCategory.RLM_ERR_CAT_CUSTOM_ERROR,
38+
ErrorCategory.RLM_ERR_CAT_WEBSOCKET_ERROR,
39+
ErrorCategory.RLM_ERR_CAT_SYNC_ERROR,
40+
ErrorCategory.RLM_ERR_CAT_SERVICE_ERROR,
41+
ErrorCategory.RLM_ERR_CAT_JSON_ERROR,
42+
ErrorCategory.RLM_ERR_CAT_CLIENT_ERROR,
43+
ErrorCategory.RLM_ERR_CAT_SYSTEM_ERROR,
44+
ErrorCategory.RLM_ERR_CAT_FILE_ACCESS,
45+
ErrorCategory.RLM_ERR_CAT_HTTP_ERROR,
46+
ErrorCategory.RLM_ERR_CAT_INVALID_ARG,
47+
ErrorCategory.RLM_ERR_CAT_APP_ERROR,
48+
ErrorCategory.RLM_ERR_CAT_LOGIC,
49+
ErrorCategory.RLM_ERR_CAT_RUNTIME,
50+
)
51+
}
52+
53+
/**
54+
* Returns a description of the most important category defined in [categoryFlags].
55+
* If no known categories are found, the integer values for all the categories is returned
56+
* as debugging information.
57+
*/
58+
val description: String = CATEGORY_ORDER.firstOrNull { category ->
59+
this.contains(category)
60+
}?.description ?: "$categoryFlags"
61+
62+
/**
63+
* Check whether a given [ErrorCategory] is included in the [categoryFlags].
64+
*/
65+
operator fun contains(category: ErrorCategory): Boolean = (categoryFlags and category.nativeValue) != 0
66+
}

packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreErrorConverter.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,21 @@ object CoreErrorConverter {
3131
path: String?,
3232
userError: Throwable?
3333
): Throwable {
34-
val categories: CategoryFlag = CategoryFlag(categoriesNativeValue)
34+
val categories: CategoryFlags = CategoryFlags(categoriesNativeValue)
3535
val errorCode: ErrorCode? = ErrorCode.of(errorCodeNativeValue)
3636
val message: String = "[$errorCode]: $messageNativeValue"
3737

3838
return userError ?: when {
3939
ErrorCode.RLM_ERR_INDEX_OUT_OF_BOUNDS == errorCode ->
4040
IndexOutOfBoundsException(message)
41-
ErrorCategory.RLM_ERR_CAT_INVALID_ARG in categories ->
41+
ErrorCategory.RLM_ERR_CAT_INVALID_ARG in categories && ErrorCategory.RLM_ERR_CAT_SYNC_ERROR !in categories -> {
42+
// Some sync errors flagged as both logical and illegal. In our case, we consider those
43+
// IllegalState, so discard them them here and let them fall through to the bottom case
4244
IllegalArgumentException(message)
45+
}
4346
ErrorCategory.RLM_ERR_CAT_LOGIC in categories || ErrorCategory.RLM_ERR_CAT_RUNTIME in categories ->
4447
IllegalStateException(message)
4548
else -> Error(message) // This can happen when propagating user level exceptions.
4649
}
4750
}
48-
49-
data class CategoryFlag(val categoryCode: Int) {
50-
operator fun contains(other: ErrorCategory): Boolean = categoryCode and other.nativeValue != 0
51-
}
5251
}

packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ expect enum class ErrorCategory : CodeDescription {
3232
RLM_ERR_CAT_SERVICE_ERROR,
3333
RLM_ERR_CAT_HTTP_ERROR,
3434
RLM_ERR_CAT_CUSTOM_ERROR,
35-
RLM_ERR_CAT_WEBSOCKET_ERROR;
35+
RLM_ERR_CAT_WEBSOCKET_ERROR,
36+
RLM_ERR_CAT_SYNC_ERROR;
3637

3738
companion object {
3839
internal fun of(nativeValue: Int): ErrorCategory?

packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ expect enum class ErrorCode : CodeDescription {
5050
RLM_ERR_SCHEMA_VERSION_MISMATCH,
5151
RLM_ERR_NO_SUBSCRIPTION_FOR_WRITE,
5252
RLM_ERR_OPERATION_ABORTED,
53+
RLM_ERR_AUTO_CLIENT_RESET_FAILED,
54+
RLM_ERR_BAD_SYNC_PARTITION_VALUE,
55+
RLM_ERR_CONNECTION_CLOSED,
56+
RLM_ERR_INVALID_SUBSCRIPTION_QUERY,
57+
RLM_ERR_SYNC_CLIENT_RESET_REQUIRED,
58+
RLM_ERR_SYNC_COMPENSATING_WRITE,
59+
RLM_ERR_SYNC_CONNECT_FAILED,
60+
RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE,
61+
RLM_ERR_SYNC_PERMISSION_DENIED,
62+
RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED,
63+
RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED,
64+
RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED,
65+
RLM_ERR_SYNC_USER_MISMATCH,
66+
RLM_ERR_TLS_HANDSHAKE_FAILED,
67+
RLM_ERR_WRONG_SYNC_TYPE,
68+
RLM_ERR_SYNC_WRITE_NOT_ALLOWED,
5369
RLM_ERR_SYSTEM_ERROR,
5470
RLM_ERR_LOGIC,
5571
RLM_ERR_NOT_SUPPORTED,
@@ -162,9 +178,7 @@ expect enum class ErrorCode : CodeDescription {
162178
RLM_ERR_MAINTENANCE_IN_PROGRESS,
163179
RLM_ERR_USERPASS_TOKEN_INVALID,
164180
RLM_ERR_INVALID_SERVER_RESPONSE,
165-
RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR,
166-
RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR,
167-
RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR,
181+
REALM_ERR_APP_SERVER_ERROR,
168182
RLM_ERR_CALLBACK,
169183
RLM_ERR_UNKNOWN;
170184

packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ import io.realm.kotlin.internal.interop.sync.CoreUserState
2727
import io.realm.kotlin.internal.interop.sync.MetadataMode
2828
import io.realm.kotlin.internal.interop.sync.NetworkTransport
2929
import io.realm.kotlin.internal.interop.sync.ProgressDirection
30-
import io.realm.kotlin.internal.interop.sync.ProtocolClientErrorCode
31-
import io.realm.kotlin.internal.interop.sync.SyncErrorCodeCategory
3230
import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode
3331
import io.realm.kotlin.internal.interop.sync.SyncUserIdentity
3432
import kotlinx.coroutines.CoroutineDispatcher
@@ -127,8 +125,6 @@ typealias RealmMutableSubscriptionSetPointer = NativePointer<RealmMutableSubscri
127125
@Suppress("LongParameterList")
128126
class SyncConnectionParams(
129127
sdkVersion: String,
130-
localAppName: String?,
131-
localAppVersion: String?,
132128
bundleId: String,
133129
platformVersion: String,
134130
device: String,
@@ -137,8 +133,6 @@ class SyncConnectionParams(
137133
frameworkVersion: String
138134
) {
139135
val sdkName = "Kotlin"
140-
val localAppName: String?
141-
val localAppVersion: String?
142136
val bundleId: String
143137
val sdkVersion: String
144138
val platformVersion: String
@@ -156,8 +150,6 @@ class SyncConnectionParams(
156150
init {
157151
this.sdkVersion = sdkVersion
158152
this.bundleId = bundleId
159-
this.localAppName = localAppName
160-
this.localAppVersion = localAppVersion
161153
this.platformVersion = platformVersion
162154
this.device = device
163155
this.deviceVersion = deviceVersion
@@ -624,8 +616,7 @@ expect object RealmInterop {
624616
fun realm_sync_session_resume(syncSession: RealmSyncSessionPointer)
625617
fun realm_sync_session_handle_error_for_testing(
626618
syncSession: RealmSyncSessionPointer,
627-
errorCode: ProtocolClientErrorCode,
628-
category: SyncErrorCodeCategory,
619+
error: ErrorCode,
629620
errorMessage: String,
630621
isFatal: Boolean
631622
)

0 commit comments

Comments
 (0)