Skip to content

Commit

Permalink
Merge 6b457b2 into 707edcc
Browse files Browse the repository at this point in the history
  • Loading branch information
desistefanova committed May 26, 2023
2 parents 707edcc + 6b457b2 commit 7010680
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 49 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,11 @@
## vNext (TBD)

### Enhancements

* Added `ClientResetError.backupFilePath` where the backup copy of the realm will be placed once the client reset process is complete ([#1291](https://github.com/realm/realm-dart/pull/1291)).
* Added `CompensatingWriteError` that contains detailed information about the writes that have been reverted by the server due to permissions or subscription view restrictions. It will be received on `syncErrorHandle` callbak, which is set to `Configuration.flexibleSync` similarly to other session errors ([#1291](https://github.com/realm/realm-dart/pull/1291)).
* Added `SyncError.detailedMessage` that contains error details. In case of server error, it contains the link to the server log ([#1291](https://github.com/realm/realm-dart/pull/1291)).

* Add `RealmResults.isValid` ([#1231](https://github.com/realm/realm-dart/pull/1231)).
* Support `Decimal128` datatype ([#1192](https://github.com/realm/realm-dart/pull/1192)).
* Realm logging is extended to support logging of all Realm storage level messages. (Core upgrade).
Expand Down
111 changes: 93 additions & 18 deletions lib/src/configuration.dart
Expand Up @@ -608,7 +608,19 @@ class ClientResetError extends SyncError {
/// The [ClientResetError] has error code of [SyncClientErrorCode.autoClientResetFailure]
SyncClientErrorCode get code => SyncClientErrorCode.autoClientResetFailure;
ClientResetError(String message, [this._config]) : super(message, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure.code);
String get _originalFilePath => _userInfo?["ORIGINAL_FILE_PATH"] ?? "";
/// The path where the backup copy of the realm will be placed once the client reset process is complete.
String get backupFilePath => _userInfo?["RECOVERY_FILE_PATH"] ?? "";
final Map<String, String>? _userInfo;
ClientResetError(
String message,
String detailedMessage, [
this._config,
this._userInfo,
]) : super(message, detailedMessage, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure.code);
@override
String toString() {
Expand All @@ -622,6 +634,9 @@ class ClientResetError extends SyncError {
if (_config is! FlexibleSyncConfiguration) {
throw RealmException("The current configuration is not FlexibleSyncConfiguration.");
}
if (_originalFilePath != _config?.path) {
throw RealmException("The current configuration does not match the original realm file path.");
}
final flexibleConfig = _config as FlexibleSyncConfiguration;
return realmCore.immediatelyRunFileActions(flexibleConfig.user.app, flexibleConfig.path);
}
Expand All @@ -636,27 +651,27 @@ class SyncError extends RealmError {
/// The category of the sync error
final SyncErrorCategory category;
SyncError(String message, this.category, this.codeValue) : super(message);
/// Detailed error message.
/// In case of server error, it contains the link to the server log.
final String detailedMessage;
SyncError(String message, this.detailedMessage, this.category, this.codeValue) : super(message);
/// Creates a specific type of [SyncError] instance based on the [category] and the [code] supplied.
static SyncError create(String message, SyncErrorCategory category, int code, {bool isFatal = false}) {
static SyncError create(String message, String detailedMessage, SyncErrorCategory category, int code, {bool isFatal = false}) {
switch (category) {
case SyncErrorCategory.client:
final SyncClientErrorCode errorCode = SyncClientErrorCode.fromInt(code);
if (errorCode == SyncClientErrorCode.autoClientResetFailure) {
return ClientResetError(message);
}
return SyncClientError(message, category, errorCode, isFatal: isFatal);
return SyncClientError(message, detailedMessage, category, SyncClientErrorCode.fromInt(code), isFatal: isFatal);
case SyncErrorCategory.connection:
return SyncConnectionError(message, category, SyncConnectionErrorCode.fromInt(code), isFatal: isFatal);
return SyncConnectionError(message, detailedMessage, category, SyncConnectionErrorCode.fromInt(code), isFatal: isFatal);
case SyncErrorCategory.session:
return SyncSessionError(message, category, SyncSessionErrorCode.fromInt(code), isFatal: isFatal);
return SyncSessionError(message, detailedMessage, category, SyncSessionErrorCode.fromInt(code), isFatal: isFatal);
case SyncErrorCategory.webSocket:
return SyncWebSocketError(message, category, SyncWebSocketErrorCode.fromInt(code));
return SyncWebSocketError(message, detailedMessage, category, SyncWebSocketErrorCode.fromInt(code));
case SyncErrorCategory.system:
case SyncErrorCategory.unknown:
default:
return GeneralSyncError(message, category, code);
return GeneralSyncError(message, detailedMessage, category, code);
}
}
Expand All @@ -680,10 +695,11 @@ class SyncClientError extends SyncError {
SyncClientError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncClientErrorCode errorCode, {
this.isFatal = false,
}) : super(message, category, errorCode.code);
}) : super(message, detailedMessage, category, errorCode.code);
@override
String toString() {
Expand All @@ -702,10 +718,11 @@ class SyncConnectionError extends SyncError {
SyncConnectionError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncConnectionErrorCode errorCode, {
this.isFatal = false,
}) : super(message, category, errorCode.code);
}) : super(message, detailedMessage, category, errorCode.code);
@override
String toString() {
Expand All @@ -724,10 +741,11 @@ class SyncSessionError extends SyncError {
SyncSessionError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncSessionErrorCode errorCode, {
this.isFatal = false,
}) : super(message, category, errorCode.code);
}) : super(message, detailedMessage, category, errorCode.code);
@override
String toString() {
Expand All @@ -744,7 +762,12 @@ class SyncResolveError extends SyncError {
/// The numeric value indicating the type of the network resolution sync error.
SyncResolveErrorCode get code => SyncResolveErrorCode.fromInt(codeValue);
SyncResolveError(String message, SyncErrorCategory category, SyncResolveErrorCode errorCode) : super(message, category, errorCode.index);
SyncResolveError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncResolveErrorCode errorCode,
) : super(message, detailedMessage, category, errorCode.index);
@override
String toString() {
Expand All @@ -757,7 +780,12 @@ class SyncWebSocketError extends SyncError {
/// The numeric value indicating the type of the web socket error.
SyncWebSocketErrorCode get code => SyncWebSocketErrorCode.fromInt(codeValue);
SyncWebSocketError(String message, SyncErrorCategory category, SyncWebSocketErrorCode errorCode) : super(message, category, errorCode.code);
SyncWebSocketError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncWebSocketErrorCode errorCode,
) : super(message, detailedMessage, category, errorCode.code);
@override
String toString() {
Expand All @@ -770,7 +798,12 @@ class GeneralSyncError extends SyncError {
/// The numeric value indicating the type of the general sync error.
int get code => codeValue;
GeneralSyncError(String message, SyncErrorCategory category, int code) : super(message, category, code);
GeneralSyncError(
String message,
String detailedMessage,
SyncErrorCategory category,
int code,
) : super(message, detailedMessage, category, code);
@override
String toString() {
Expand All @@ -792,3 +825,45 @@ enum GeneralSyncErrorCode {
final int code;
const GeneralSyncErrorCode(this.code);
}
/// A class containing the details for a compensating write performed by the server.
class CompensatingWriteInfo {
CompensatingWriteInfo(this.objectType, this.reason, this.primaryKey);
/// The type of the object which was affected by the compensating write.
final String objectType;
/// The reason for the server to perform a compensating write.
final String reason;
/// The primary key of the object which was affected by the compensating write.
final RealmValue primaryKey;
@override
String toString() {
return "CompensatingWriteInfo: objectType: $objectType\n reason: $reason\n primaryKey: $primaryKey\n";
}
}
/// An error type that describes a compensating write error,
/// which indicates that one more object changes have been reverted
/// by the server.
/// {@category Sync}
class CompensatingWriteError extends SyncError {
/// The [CompensatingWriteError] has error code of [SyncSessionErrorCode.compensatingWrite]
SyncSessionErrorCode get code => SyncSessionErrorCode.compensatingWrite;
/// The list of the compensating writes performed by the server.
late final List<CompensatingWriteInfo> compensatingWrites;
CompensatingWriteError(
String message,
String detailedMessage,
this.compensatingWrites,
) : super(message, detailedMessage, SyncErrorCategory.session, SyncSessionErrorCode.compensatingWrite.code);
@override
String toString() {
return "CompensatingWriteError message: $message category: $category code: $code\n $compensatingWrites";
}
}
90 changes: 70 additions & 20 deletions lib/src/native/realm_core.dart
Expand Up @@ -2907,42 +2907,60 @@ extension on Pointer<realm_value_t> {
if (this == nullptr) {
throw RealmException("Can not convert nullptr realm_value to Dart value");
}
return ref.toDartValueByRef(realm);
}
}

switch (ref.type) {
extension on realm_value_t {
Object? toDartValueByRef(Realm? realm) {
switch (type) {
case realm_value_type.RLM_TYPE_NULL:
return null;
case realm_value_type.RLM_TYPE_INT:
return ref.values.integer;
return values.integer;
case realm_value_type.RLM_TYPE_BOOL:
return ref.values.boolean;
return values.boolean;
case realm_value_type.RLM_TYPE_STRING:
return ref.values.string.data.cast<Utf8>().toRealmDartString(length: ref.values.string.size)!;
return values.string.data.cast<Utf8>().toRealmDartString(length: values.string.size)!;
case realm_value_type.RLM_TYPE_FLOAT:
return ref.values.fnum;
return values.fnum;
case realm_value_type.RLM_TYPE_DOUBLE:
return ref.values.dnum;
return values.dnum;
case realm_value_type.RLM_TYPE_LINK:
final objectKey = ref.values.link.target;
final classKey = ref.values.link.target_table;
if (realm == null) {
return null;
}
final objectKey = values.link.target;
final classKey = values.link.target_table;
if (realm.metadata.getByClassKeyIfExists(classKey) == null) return null; // temprorary workaround to avoid crash on assertion
return realmCore._getObject(realm, classKey, objectKey);
case realm_value_type.RLM_TYPE_BINARY:
throw Exception("Not implemented");
case realm_value_type.RLM_TYPE_TIMESTAMP:
final seconds = ref.values.timestamp.seconds;
final nanoseconds = ref.values.timestamp.nanoseconds;
final seconds = values.timestamp.seconds;
final nanoseconds = values.timestamp.nanoseconds;
return DateTime.fromMicrosecondsSinceEpoch(seconds * _microsecondsPerSecond + nanoseconds ~/ _nanosecondsPerMicrosecond, isUtc: true);
case realm_value_type.RLM_TYPE_DECIMAL128:
var decimal = ref.values.decimal128; // NOTE: Does not copy the struct!
var decimal = values.decimal128; // NOTE: Does not copy the struct!
decimal = _realmLib.realm_dart_decimal128_copy(decimal); // This is a workaround to that
return Decimal128Internal.fromNative(decimal);
case realm_value_type.RLM_TYPE_OBJECT_ID:
return ObjectId.fromBytes(cast<Uint8>().asTypedList(12));
return ObjectId.fromBytes(values.object_id.bytes.toIntList(ObjectId.byteLength));
case realm_value_type.RLM_TYPE_UUID:
return Uuid.fromBytes(cast<Uint8>().asTypedList(16).buffer);
return Uuid.fromBytes(values.uuid.bytes.toIntList(16).buffer);
default:
throw RealmException("realm_value_type ${ref.type} not supported");
throw RealmException("realm_value_type $type not supported");
}
}
}

extension on Array<Uint8> {
Uint8List toIntList(int count) {
List<int> result = List.filled(count, this[0]);
for (var i = 1; i < count; i++) {
result[i] = this[i];
}
return Uint8List.fromList(result);
}
}

Expand Down Expand Up @@ -2993,23 +3011,55 @@ extension on Pointer<Utf8> {
}

extension on realm_sync_error {
static int length1(Pointer<Uint8> codeUnits) {
var length = 0;
while (codeUnits[length] != 0) {
length++;
}
return length;
}

SyncError toSyncError(Configuration config) {
final message = detailed_message.cast<Utf8>().toRealmDartString()!;
final message = error_code.message.cast<Utf8>().toRealmDartString()!;
final detailedMessage = detailed_message.cast<Utf8>().toRealmDartString()!;
final SyncErrorCategory category = SyncErrorCategory.values[error_code.category];

//client reset can be requested with is_client_reset_requested disregarding the error_code.value
if (is_client_reset_requested) {
return ClientResetError(message, config);
if (is_client_reset_requested || error_code.value == SyncClientErrorCode.autoClientResetFailure.code) {
Map<String, String> userInfoMap = {};
final userInfoMapPtr = user_info_map.cast<realm_sync_error_user_info>();
for (int i = 0; i < user_info_length; ++i) {
final userInfoItem = userInfoMapPtr[i];
final key = userInfoItem.key.cast<Utf8>().toDartString();
final value = userInfoItem.value.cast<Utf8>().toDartString();
userInfoMap.addEntries([MapEntry(key, value)]);
}
return ClientResetError(message, detailedMessage, config, userInfoMap);
}
if (category == SyncErrorCategory.session) {
final sessionErrorCode = SyncSessionErrorCode.fromInt(error_code.value);
if (sessionErrorCode == SyncSessionErrorCode.compensatingWrite) {
List<CompensatingWriteInfo> compensatingWrites = [];

final compensatingWritesPtr = compensating_writes.cast<realm_sync_error_compensating_write_info>();
for (int i = 0; i < compensating_writes_length; ++i) {
final compensatingWrite = compensatingWritesPtr[i];
final object_name = compensatingWrite.object_name.cast<Utf8>().toDartString();
final reason = compensatingWrite.reason.cast<Utf8>().toDartString();
final primary_key = compensatingWrite.primary_key.toDartValueByRef(null);
compensatingWrites.add(CompensatingWriteInfo(object_name, reason, RealmValue.from(primary_key)));
}
return CompensatingWriteError(message, detailedMessage, compensatingWrites);
}
}

return SyncError.create(message, category, error_code.value, isFatal: is_fatal);
return SyncError.create(message, detailedMessage, category, error_code.value, isFatal: is_fatal);
}
}

extension on Pointer<realm_sync_error_code_t> {
SyncError toSyncError() {
final message = ref.message.cast<Utf8>().toDartString();
return SyncError.create(message, SyncErrorCategory.values[ref.category], ref.value, isFatal: false);
return SyncError.create(message, "", SyncErrorCategory.values[ref.category], ref.value, isFatal: false);
}
}

Expand Down

0 comments on commit 7010680

Please sign in to comment.