From 99c80eb5ab7daa6439227a3f548788bb3f335620 Mon Sep 17 00:00:00 2001 From: Diana Maria Perez Afanador Date: Tue, 2 Jul 2024 11:22:56 +0200 Subject: [PATCH 1/5] Throw any sync errors tagged with warning action from the server via the sync error handler --- CHANGELOG.md | 2 ++ Realm/ObjectServerTests/RealmServer.swift | 35 ++++++------------- .../SwiftFlexibleSyncServerTests.swift | 24 +++++++++++++ Realm/RLMError.mm | 2 +- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9756001584..f4f9686211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ x.y.z Release notes (yyyy-MM-dd) * Code sign our published xcframeworks. By Apple's requirements, we should sign our release binaries so Xcode can validate it was signed by the same developer on every new version. ([Apple](https://developer.apple.com/support/third-party-SDK-requirements/)). +* Throw any sync errors tagged with warning action from the server via the sync error handler. + ([#8020](https://github.com/realm/realm-swift/issues/8020)). ### Fixed * ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) diff --git a/Realm/ObjectServerTests/RealmServer.swift b/Realm/ObjectServerTests/RealmServer.swift index 31543f125a..c1d97fca9c 100644 --- a/Realm/ObjectServerTests/RealmServer.swift +++ b/Realm/ObjectServerTests/RealmServer.swift @@ -1028,10 +1028,7 @@ public class RealmServer: NSObject { // Retrieve Atlas App Services AppId with ClientAppId using the Admin API public func retrieveAppServerId(_ clientAppId: String) throws -> String { - guard let session = session else { - fatalError() - } - + let session = try XCTUnwrap(session) let appsListInfo = try session.apps.get().get() guard let appsList = appsListInfo as? [[String: Any]] else { throw URLError(.badServerResponse) @@ -1052,9 +1049,7 @@ public class RealmServer: NSObject { } public func retrieveSyncServiceId(appServerId: String) throws -> String { - guard let session = session else { - fatalError() - } + let session = try XCTUnwrap(session) let app = session.apps[appServerId] // Get all services guard let syncServices = try app.services.get().get() as? [[String: Any]] else { @@ -1083,14 +1078,11 @@ public class RealmServer: NSObject { } } - public func isSyncEnabled(flexibleSync: Bool = false, appServerId: String, syncServiceId: String) throws -> Bool { - let configOption = flexibleSync ? "flexible_sync" : "sync" - guard let session = session else { - fatalError() - } + public func isSyncEnabled(appServerId: String, syncServiceId: String) throws -> Bool { + let session = try XCTUnwrap(session) let app = session.apps[appServerId] let response = try app.services[syncServiceId].config.get().get() as? [String: Any] - guard let syncInfo = response?[configOption] as? [String: Any] else { + guard let syncInfo = response?["flexible_sync"] as? [String: Any] else { return false } return syncInfo["state"] as? String == "enabled" @@ -1113,28 +1105,23 @@ public class RealmServer: NSObject { return app.sync.config.put(["development_mode_enabled": true]) } - public func disableSync(flexibleSync: Bool = false, appServerId: String, syncServiceId: String) - -> Result { - let configOption = flexibleSync ? "flexible_sync" : "sync" - guard let session = session else { - return .failure(URLError(.unknown)) - } + public func disableSync(appServerId: String, syncServiceId: String) throws -> Any? { + let session = try XCTUnwrap(session) let app = session.apps[appServerId] - return app.services[syncServiceId].config.patch([configOption: ["state": ""]]) + return app.services[syncServiceId].config.patch(["flexible_sync": ["state": ""]]) } - public func enableSync(flexibleSync: Bool = false, appServerId: String, syncServiceId: String, syncServiceConfiguration: [String: Any]) -> Result { - let configOption = flexibleSync ? "flexible_sync" : "sync" + public func enableSync(appServerId: String, syncServiceId: String, syncServiceConfiguration: [String: Any]) -> Result { var syncConfig = syncServiceConfiguration guard let session = session else { return .failure(URLError(.unknown)) } let app = session.apps[appServerId] - guard var syncInfo = syncConfig[configOption] as? [String: Any] else { + guard var syncInfo = syncConfig["flexible_sync"] as? [String: Any] else { return .failure(URLError(.unknown)) } syncInfo["state"] = "enabled" - syncConfig[configOption] = syncInfo + syncConfig["flexible_sync"] = syncInfo return app.services[syncServiceId].config.patch(syncConfig) } diff --git a/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift b/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift index 1b5d04e422..b8c17f9817 100644 --- a/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift +++ b/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift @@ -1186,6 +1186,30 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { XCTAssertEqual(downloadCount.value, 0) XCTAssertEqual(uploadCount.value, 0) } + + func testFlexibleSyncNotEnabledError() throws { + let appServerId = try RealmServer.shared.retrieveAppServerId(appId) + let syncServerId = try RealmServer.shared.retrieveSyncServiceId(appServerId: appServerId) + _ = try RealmServer.shared.disableSync(appServerId: appServerId, syncServiceId: syncServerId) + var isEnable = try RealmServer.shared.isSyncEnabled(appServerId: appServerId, syncServiceId: syncServerId) + XCTAssertFalse(isEnable) + + let ex = expectation(description: "Waiting for error handler to be called...") + ex.assertForOverFulfill = false // error handler can legally be called multiple times + app.syncManager.errorHandler = { @Sendable (error, _) in + assertSyncError(error, .clientInternalError, "Sync is not enabled for this app") + ex.fulfill() + } + + _ = try Realm(configuration: configuration()) // Sync is disabled so we cannot use async open + wait(for: [ex], timeout: 10.0) + + let configuration = try RealmServer.shared.getSyncServiceConfiguration(appServerId: appServerId, syncServiceId: syncServerId) + _ = try RealmServer.shared.enableSync(appServerId: appServerId, syncServiceId: syncServerId, syncServiceConfiguration: configuration!).get() + + isEnable = try RealmServer.shared.isSyncEnabled(appServerId: appServerId, syncServiceId: syncServerId) + XCTAssertTrue(isEnable) + } } #endif // os(macOS) diff --git a/Realm/RLMError.mm b/Realm/RLMError.mm index 040b6f1a3c..325d71d80f 100644 --- a/Realm/RLMError.mm +++ b/Realm/RLMError.mm @@ -292,7 +292,7 @@ - (NSString *)reason { errorCode = RLMSyncErrorClientResetError; else if (isSyncError) errorCode = RLMSyncErrorClientSessionError; - else if (!error.is_fatal) + else if (!error.is_fatal && error.server_requests_action != realm::sync::ProtocolErrorInfo::Action::Warning) return nil; break; } From ca58da6b51f84c92723f44c172c10980eb8f2586 Mon Sep 17 00:00:00 2001 From: Diana Maria Perez Afanador Date: Tue, 2 Jul 2024 11:31:09 +0200 Subject: [PATCH 2/5] Reword CHANGELOG for Logger feature --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4f9686211..41b728c879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ x.y.z Release notes (yyyy-MM-dd) ============================================================= ### Enhancements -* Added support for filtering logs by category. Users wil have more fine grained control over - the log level for each category as well. +* Added support for setting the log level for a category in `Logger`. User will have more fine grained control over + the log level for each category. ```swift Logger.setLogLevel(.info, category: Category.Storage.transactions) ``` From 18a72ddae5d9e38390d53d5dd2b089737bc92700 Mon Sep 17 00:00:00 2001 From: Diana Maria Perez Afanador Date: Thu, 4 Jul 2024 15:48:54 +0200 Subject: [PATCH 3/5] Solver PR comments --- CHANGELOG.md | 1 + Realm/ObjectServerTests/RealmServer.swift | 11 +++++++++++ .../SwiftFlexibleSyncServerTests.swift | 18 ++++-------------- Realm/RLMError.h | 5 +++++ Realm/RLMError.mm | 4 +++- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b728c879..656c3e3559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ x.y.z Release notes (yyyy-MM-dd) binaries so Xcode can validate it was signed by the same developer on every new version. ([Apple](https://developer.apple.com/support/third-party-SDK-requirements/)). * Throw any sync errors tagged with warning action from the server via the sync error handler. +* Report sync warnings from the server such as sync being disabled server-side to the sync error handler. ([#8020](https://github.com/realm/realm-swift/issues/8020)). ### Fixed diff --git a/Realm/ObjectServerTests/RealmServer.swift b/Realm/ObjectServerTests/RealmServer.swift index c1d97fca9c..b2bea2f228 100644 --- a/Realm/ObjectServerTests/RealmServer.swift +++ b/Realm/ObjectServerTests/RealmServer.swift @@ -469,6 +469,7 @@ class Admin { public enum SyncMode { case pbs(String) // partition based case flx([String]) // flexible sync + case notSync } // MARK: RealmServer @@ -815,6 +816,10 @@ public class RealmServer: NSObject { } } + if case .notSync = syncMode { + return clientAppId + } + app.secrets.post(on: group, [ "name": "BackingDB_uri", "value": "mongodb://localhost:26000" @@ -927,6 +932,8 @@ public class RealmServer: NSObject { "delete": true ]] ]).get() + default: + fatalError() } _ = try app.services[serviceId].config.patch(serviceConfig).get() @@ -1011,6 +1018,10 @@ public class RealmServer: NSObject { return try createApp(syncMode: .pbs(partitionKeyType), types: types, persistent: persistent) } + @objc public func createNotSyncApp() throws -> AppId { + return try createApp(syncMode: .notSync, types: [], persistent: false) + } + /// Delete all Apps created without `persistent: true` @objc func deleteApps() throws { for appId in appIds { diff --git a/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift b/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift index b8c17f9817..967b8a2e2a 100644 --- a/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift +++ b/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift @@ -1188,27 +1188,17 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { } func testFlexibleSyncNotEnabledError() throws { - let appServerId = try RealmServer.shared.retrieveAppServerId(appId) - let syncServerId = try RealmServer.shared.retrieveSyncServiceId(appServerId: appServerId) - _ = try RealmServer.shared.disableSync(appServerId: appServerId, syncServiceId: syncServerId) - var isEnable = try RealmServer.shared.isSyncEnabled(appServerId: appServerId, syncServiceId: syncServerId) - XCTAssertFalse(isEnable) - + let appId = try RealmServer.shared.createNotSyncApp() + let app = app(id: appId) let ex = expectation(description: "Waiting for error handler to be called...") ex.assertForOverFulfill = false // error handler can legally be called multiple times app.syncManager.errorHandler = { @Sendable (error, _) in - assertSyncError(error, .clientInternalError, "Sync is not enabled for this app") + assertSyncError(error, .serverWarning, "Sync is not enabled for this app") ex.fulfill() } - _ = try Realm(configuration: configuration()) // Sync is disabled so we cannot use async open + _ = try Realm(configuration: configuration(app: app)) // Sync is disabled so we cannot use async open wait(for: [ex], timeout: 10.0) - - let configuration = try RealmServer.shared.getSyncServiceConfiguration(appServerId: appServerId, syncServiceId: syncServerId) - _ = try RealmServer.shared.enableSync(appServerId: appServerId, syncServiceId: syncServerId, syncServiceConfiguration: configuration!).get() - - isEnable = try RealmServer.shared.isSyncEnabled(appServerId: appServerId, syncServiceId: syncServerId) - XCTAssertTrue(isEnable) } } diff --git a/Realm/RLMError.h b/Realm/RLMError.h index 9e278cac69..60292a222e 100644 --- a/Realm/RLMError.h +++ b/Realm/RLMError.h @@ -328,6 +328,11 @@ typedef RLM_ERROR_ENUM(NSInteger, RLMSyncError, RLMSyncErrorDomain) { Connecting to the server failed due to a TLS issue such as an invalid certificate. */ RLMSyncErrorTLSHandshakeFailed = 13, + /** + An error indicating the server send down an error with a warning action, which + needs to be executed by the client or the end user. + */ + RLMSyncErrorServerWarning = 14, }; #pragma mark - RLMSyncAuthError diff --git a/Realm/RLMError.mm b/Realm/RLMError.mm index 325d71d80f..72cc0b8388 100644 --- a/Realm/RLMError.mm +++ b/Realm/RLMError.mm @@ -292,7 +292,9 @@ - (NSString *)reason { errorCode = RLMSyncErrorClientResetError; else if (isSyncError) errorCode = RLMSyncErrorClientSessionError; - else if (!error.is_fatal && error.server_requests_action != realm::sync::ProtocolErrorInfo::Action::Warning) + else if (error.server_requests_action == realm::sync::ProtocolErrorInfo::Action::Warning) + errorCode = RLMSyncErrorServerWarning; + else if (!error.is_fatal) return nil; break; } From 32e36ee45c98e46e02469588d04624325e3480a2 Mon Sep 17 00:00:00 2001 From: Diana Maria Perez Afanador Date: Wed, 24 Jul 2024 16:22:45 +0200 Subject: [PATCH 4/5] Solve PR comments --- Realm/RLMError.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Realm/RLMError.h b/Realm/RLMError.h index 60292a222e..c006ea4b8e 100644 --- a/Realm/RLMError.h +++ b/Realm/RLMError.h @@ -329,8 +329,11 @@ typedef RLM_ERROR_ENUM(NSInteger, RLMSyncError, RLMSyncErrorDomain) { */ RLMSyncErrorTLSHandshakeFailed = 13, /** - An error indicating the server send down an error with a warning action, which - needs to be executed by the client or the end user. + The server has encountered an error that it wants the user to know about, + but is not necessarily fatal. + + An error with this code may indicate that either sync is not enabled or it's trying to connect to + an edge server app. */ RLMSyncErrorServerWarning = 14, }; From 6191b22f948ccc48427e688f06c016d809f4922b Mon Sep 17 00:00:00 2001 From: Diana Maria Perez Afanador Date: Wed, 24 Jul 2024 16:25:03 +0200 Subject: [PATCH 5/5] Solve PR comments --- CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 656c3e3559..130175e413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,14 @@ x.y.z Release notes (yyyy-MM-dd) ============================================================= ### Enhancements -* Added support for setting the log level for a category in `Logger`. User will have more fine grained control over - the log level for each category. +* Added support for filtering logs by category. Users wil have more fine grained control over + the log level for each category as well. ```swift Logger.setLogLevel(.info, category: Category.Storage.transactions) ``` * Code sign our published xcframeworks. By Apple's requirements, we should sign our release binaries so Xcode can validate it was signed by the same developer on every new version. ([Apple](https://developer.apple.com/support/third-party-SDK-requirements/)). -* Throw any sync errors tagged with warning action from the server via the sync error handler. * Report sync warnings from the server such as sync being disabled server-side to the sync error handler. ([#8020](https://github.com/realm/realm-swift/issues/8020)).