Skip to content

Commit

Permalink
Rework sync exceptions based on new error codes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Melchior committed Aug 23, 2023
1 parent e7b3c9e commit 0c6a6af
Show file tree
Hide file tree
Showing 31 changed files with 424 additions and 589 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ childA == childC
```

### Enhancements
* 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))
* 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
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))
* 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))
Expand All @@ -46,7 +47,7 @@ if the content is the same. Custom implementations of these methods will be resp
* Minimum Android SDK: 16.

### Internal
* None.
* Updated to Realm Core 13.18.0, commit 48d6d672cb30f86976ba19a44ffffe25ed447128.


## 1.10.2 (2023-07-21)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,29 @@

package io.realm.kotlin.test.sync

import io.realm.kotlin.internal.interop.CategoryFlags
import io.realm.kotlin.internal.interop.ErrorCategory
import io.realm.kotlin.internal.interop.ErrorCode
import io.realm.kotlin.internal.interop.realm_auth_provider_e
import io.realm.kotlin.internal.interop.realm_errno_e
import io.realm.kotlin.internal.interop.realm_error_category_e
import io.realm.kotlin.internal.interop.realm_sync_client_metadata_mode_e
import io.realm.kotlin.internal.interop.realm_sync_connection_state_e
import io.realm.kotlin.internal.interop.realm_sync_errno_client_e
import io.realm.kotlin.internal.interop.realm_sync_errno_connection_e
import io.realm.kotlin.internal.interop.realm_sync_errno_session_e
import io.realm.kotlin.internal.interop.realm_sync_error_category_e
import io.realm.kotlin.internal.interop.realm_sync_session_resync_mode_e
import io.realm.kotlin.internal.interop.realm_sync_session_state_e
import io.realm.kotlin.internal.interop.realm_user_state_e
import io.realm.kotlin.internal.interop.realm_web_socket_errno_e
import io.realm.kotlin.internal.interop.sync.AuthProvider
import io.realm.kotlin.internal.interop.sync.CoreConnectionState
import io.realm.kotlin.internal.interop.sync.CoreSyncSessionState
import io.realm.kotlin.internal.interop.sync.CoreUserState
import io.realm.kotlin.internal.interop.sync.MetadataMode
import io.realm.kotlin.internal.interop.sync.ProtocolClientErrorCode
import io.realm.kotlin.internal.interop.sync.ProtocolConnectionErrorCode
import io.realm.kotlin.internal.interop.sync.ProtocolSessionErrorCode
import io.realm.kotlin.internal.interop.sync.SyncErrorCodeCategory
import io.realm.kotlin.internal.interop.sync.SyncConnectionErrorCode
import io.realm.kotlin.internal.interop.sync.SyncSessionErrorCode
import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode
import io.realm.kotlin.internal.interop.sync.WebsocketErrorCode
import org.junit.Test
import kotlin.reflect.KClass
import kotlin.test.BeforeTest
Expand All @@ -59,10 +58,11 @@ class SyncEnumTests {
}

@Test
fun appErrorCategory() {
fun errorCategory() {
checkEnum(realm_error_category_e::class) { nativeValue ->
ErrorCategory.of(nativeValue)
}
assertEquals(ErrorCategory.values().size, CategoryFlags.CATEGORY_ORDER.size)
}

@Test
Expand Down Expand Up @@ -94,30 +94,23 @@ class SyncEnumTests {
}

@Test
fun protocolClientErrorCode() {
checkEnum(realm_sync_errno_client_e::class) { nativeValue ->
ProtocolClientErrorCode.of(nativeValue)
}
}

@Test
fun protocolConnectionErrorCode() {
fun syncConnectionErrorCode() {
checkEnum(realm_sync_errno_connection_e::class) { nativeValue ->
ProtocolConnectionErrorCode.of(nativeValue)
SyncConnectionErrorCode.of(nativeValue)
}
}

@Test
fun protocolSessionErrorCode() {
fun syncSessionErrorCode() {
checkEnum(realm_sync_errno_session_e::class) { nativeValue ->
ProtocolSessionErrorCode.of(nativeValue)
SyncSessionErrorCode.of(nativeValue)
}
}

@Test
fun syncErrorCodeCategory() {
checkEnum(realm_sync_error_category_e::class) { nativeValue ->
SyncErrorCodeCategory.of(nativeValue)
fun websocketErrorCode() {
checkEnum(realm_web_socket_errno_e::class) { nativeValue ->
WebsocketErrorCode.of(nativeValue)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package io.realm.kotlin.internal.interop
import io.realm.kotlin.internal.interop.sync.AppError
import io.realm.kotlin.internal.interop.sync.CoreSubscriptionSetState
import io.realm.kotlin.internal.interop.sync.SyncError
import io.realm.kotlin.internal.interop.sync.SyncErrorCode

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

// Interface exposed towards `library-sync`
interface SyncSessionTransferCompletionCallback {
fun invoke(error: SyncErrorCode?)
fun invoke(error: CoreError?)
}

interface LogCallback {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.realm.kotlin.internal.interop

/**
* Wrapper for C-API `realm_error_t`.
* See https://github.com/realm/realm-core/blob/master/src/realm.h#L231
*/
class CoreError(
categoriesNativeValue: Int,
val errorCodeNativeValue: Int,
messageNativeValue: String?,
path: String,
userError: Throwable?
) {
val categories: CategoryFlags = CategoryFlags((categoriesNativeValue))
val errorCode: ErrorCode? = ErrorCode.of(errorCodeNativeValue)
val message = messageNativeValue
val realmPath: String = path
val userError: Throwable? = userError

operator fun contains(category: ErrorCategory): Boolean = category in categories
}

data class CategoryFlags(val categoryFlags: Int) {

companion object {
/**
* See error code mapping to categories here:
* https://github.com/realm/realm-core/blob/master/src/realm/error_codes.cpp#L29
*
* In most cases, only 1 category is assigned, but some errors have multiple. So instead of
* overwhelming the user with many categories, we only select the most important to show
* in the error message. "important" is of course tricky to define, but generally
* we consider vague categories like [ErrorCategory.RLM_ERR_CAT_RUNTIME] as less important
* than more specific ones like [ErrorCategory.RLM_ERR_CAT_JSON_ERROR].
*
* In the current implementation, categories between index 0 and 7 are considered equal
* and the order is somewhat arbitrary. No error codes has multiple of these categories
* associated either.
*/
val CATEGORY_ORDER: List<ErrorCategory> = listOf(
ErrorCategory.RLM_ERR_CAT_CUSTOM_ERROR,
ErrorCategory.RLM_ERR_CAT_WEBSOCKET_ERROR,
ErrorCategory.RLM_ERR_CAT_SERVICE_ERROR,
ErrorCategory.RLM_ERR_CAT_JSON_ERROR,
ErrorCategory.RLM_ERR_CAT_CLIENT_ERROR,
ErrorCategory.RLM_ERR_CAT_SYSTEM_ERROR,
ErrorCategory.RLM_ERR_CAT_FILE_ACCESS,
ErrorCategory.RLM_ERR_CAT_HTTP_ERROR,
ErrorCategory.RLM_ERR_CAT_INVALID_ARG,
ErrorCategory.RLM_ERR_CAT_APP_ERROR,
ErrorCategory.RLM_ERR_CAT_LOGIC,
ErrorCategory.RLM_ERR_CAT_RUNTIME,
)
}

/**
* Returns a description of the most important category defined in [categoryFlags].
* If no known categories are found, "Unknown[categoryFlags]" is returned.
*/
val description: String = CATEGORY_ORDER.firstOrNull { category ->
category in this
}?.description ?: "$categoryFlags"

/**
* Check whether a given [ErrorCategory] is included in the [categoryFlags].
*/
operator fun contains(category: ErrorCategory): Boolean = (categoryFlags and category.nativeValue) != 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ object CoreErrorConverter {
path: String?,
userError: Throwable?
): Throwable {
val categories: CategoryFlag = CategoryFlag(categoriesNativeValue)
val categories: CategoryFlags = CategoryFlags(categoriesNativeValue)
val errorCode: ErrorCode? = ErrorCode.of(errorCodeNativeValue)
val message: String = "[$errorCode]: $messageNativeValue"

Expand All @@ -45,8 +45,4 @@ object CoreErrorConverter {
else -> Error(message) // This can happen when propagating user level exceptions.
}
}

data class CategoryFlag(val categoryCode: Int) {
operator fun contains(other: ErrorCategory): Boolean = categoryCode and other.nativeValue != 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ expect enum class ErrorCategory : CodeDescription {
RLM_ERR_CAT_SERVICE_ERROR,
RLM_ERR_CAT_HTTP_ERROR,
RLM_ERR_CAT_CUSTOM_ERROR,
RLM_ERR_CAT_WEBSOCKET_ERROR;
RLM_ERR_CAT_WEBSOCKET_ERROR,
RLM_ERR_CAT_SYNC_ERROR;

companion object {
internal fun of(nativeValue: Int): ErrorCategory?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ expect enum class ErrorCode : CodeDescription {
RLM_ERR_SCHEMA_VERSION_MISMATCH,
RLM_ERR_NO_SUBSCRIPTION_FOR_WRITE,
RLM_ERR_OPERATION_ABORTED,
RLM_ERR_AUTO_CLIENT_RESET_FAILED,
RLM_ERR_BAD_SYNC_PARTITION_VALUE,
RLM_ERR_CONNECTION_CLOSED,
RLM_ERR_INVALID_SUBSCRIPTION_QUERY,
RLM_ERR_SYNC_CLIENT_RESET_REQUIRED,
RLM_ERR_SYNC_COMPENSATING_WRITE,
RLM_ERR_SYNC_CONNECT_FAILED,
RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE,
RLM_ERR_SYNC_PERMISSION_DENIED,
RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED,
RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED,
RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED,
RLM_ERR_SYNC_USER_MISMATCH,
RLM_ERR_TLS_HANDSHAKE_FAILED,
RLM_ERR_WRONG_SYNC_TYPE,
RLM_ERR_SYNC_WRITE_NOT_ALLOWED,
RLM_ERR_SYSTEM_ERROR,
RLM_ERR_LOGIC,
RLM_ERR_NOT_SUPPORTED,
Expand Down Expand Up @@ -162,9 +178,7 @@ expect enum class ErrorCode : CodeDescription {
RLM_ERR_MAINTENANCE_IN_PROGRESS,
RLM_ERR_USERPASS_TOKEN_INVALID,
RLM_ERR_INVALID_SERVER_RESPONSE,
RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR,
RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR,
RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR,
REALM_ERR_APP_SERVER_ERROR,
RLM_ERR_CALLBACK,
RLM_ERR_UNKNOWN;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import io.realm.kotlin.internal.interop.sync.CoreUserState
import io.realm.kotlin.internal.interop.sync.MetadataMode
import io.realm.kotlin.internal.interop.sync.NetworkTransport
import io.realm.kotlin.internal.interop.sync.ProgressDirection
import io.realm.kotlin.internal.interop.sync.ProtocolClientErrorCode
import io.realm.kotlin.internal.interop.sync.SyncErrorCodeCategory
import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode
import io.realm.kotlin.internal.interop.sync.SyncUserIdentity
import kotlinx.coroutines.CoroutineDispatcher
Expand Down Expand Up @@ -127,8 +125,6 @@ typealias RealmMutableSubscriptionSetPointer = NativePointer<RealmMutableSubscri
@Suppress("LongParameterList")
class SyncConnectionParams(
sdkVersion: String,
localAppName: String?,
localAppVersion: String?,
bundleId: String,
platformVersion: String,
device: String,
Expand All @@ -137,8 +133,6 @@ class SyncConnectionParams(
frameworkVersion: String
) {
val sdkName = "Kotlin"
val localAppName: String?
val localAppVersion: String?
val bundleId: String
val sdkVersion: String
val platformVersion: String
Expand All @@ -156,8 +150,6 @@ class SyncConnectionParams(
init {
this.sdkVersion = sdkVersion
this.bundleId = bundleId
this.localAppName = localAppName
this.localAppVersion = localAppVersion
this.platformVersion = platformVersion
this.device = device
this.deviceVersion = deviceVersion
Expand Down Expand Up @@ -624,8 +616,7 @@ expect object RealmInterop {
fun realm_sync_session_resume(syncSession: RealmSyncSessionPointer)
fun realm_sync_session_handle_error_for_testing(
syncSession: RealmSyncSessionPointer,
errorCode: ProtocolClientErrorCode,
category: SyncErrorCodeCategory,
error: ErrorCode,
errorMessage: String,
isFatal: Boolean
)
Expand Down
Loading

0 comments on commit 0c6a6af

Please sign in to comment.