diff --git a/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj b/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj index 8aca4138..fe5d6e4d 100644 --- a/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj +++ b/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj @@ -340,7 +340,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -400,7 +400,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; diff --git a/Examples/Example-iOS/Sources/DetailTableViewController.swift b/Examples/Example-iOS/Sources/DetailTableViewController.swift index 5e1eaf0b..6b58d163 100644 --- a/Examples/Example-iOS/Sources/DetailTableViewController.swift +++ b/Examples/Example-iOS/Sources/DetailTableViewController.swift @@ -328,18 +328,26 @@ class DetailTableViewController: UITableViewController { } self.listener?.didReceiveDataSyncEvent = { dataSyncEvent in - switch dataSyncEvent { - case let .entityCreated(entity), let .entityUpdated(entity): - print("The \(entity.className) entity \(entity.id) was created or updated at \(entity.updatedAt)") + print("Received on \(dataSyncEvent.channel) at \(dataSyncEvent.timetoken)") + + switch dataSyncEvent.change { + case let .entityCreated(entityEvent), let .entityUpdated(entityEvent): + let entity = entityEvent.entity + print("The \(entity.className) \(entityEvent.kind) \(entity.id) was created or updated at \(entity.updatedAt)") print("Its revision is \(entity.eTag) and it expires at \(entity.expiresAt)") print("Payload: \(entity.payload ?? "none")") - case let .relationshipCreated(relationship), let .relationshipUpdated(relationship): - print("The \(relationship.className) relationship \(relationship.id) was created or updated") + case let .relationshipCreated(relationshipEvent), let .relationshipUpdated(relationshipEvent): + let relationship = relationshipEvent.relationship + print("The \(relationship.className) \(relationshipEvent.kind) \(relationship.id) was created or updated") print("It connects entity \(relationship.entityAId) to entity \(relationship.entityBId)") - case let .entityDeleted(removed): - print("The \(removed.className) entity \(removed.id) was deleted at \(removed.deletedAt)") - case let .relationshipDeleted(removed): - print("The \(removed.className) relationship \(removed.id) was deleted at \(removed.deletedAt)") + case let .entityDeleted(entityEvent): + let removed = entityEvent.removed + print("The \(removed.className) \(entityEvent.kind) \(removed.id) was deleted at \(removed.deletedAt)") + case let .relationshipDeleted(relationshipEvent): + let removed = relationshipEvent.removed + print("The \(removed.className) \(relationshipEvent.kind) \(removed.id) was deleted at \(removed.deletedAt)") + case let .unknown(unknownEvent): + print("Unrecognized \(unknownEvent.type) change: \(unknownEvent.event)") } } diff --git a/PubNub.xcodeproj/project.pbxproj b/PubNub.xcodeproj/project.pbxproj index f1201b19..e05845eb 100644 --- a/PubNub.xcodeproj/project.pbxproj +++ b/PubNub.xcodeproj/project.pbxproj @@ -253,7 +253,6 @@ 35C6B6DD22F501780054F242 /* Encodable+PubNub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C6B6DC22F501780054F242 /* Encodable+PubNub.swift */; }; 35C6B6E322F515760054F242 /* SubscribeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 350EFBD722C926C300FA33AA /* SubscribeRouter.swift */; }; 35C6B6E422F516040054F242 /* subscription_mixed_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 35C6B6DF22F513D80054F242 /* subscription_mixed_success.json */; }; - DA7A5C000000000000000201 /* subscription_unknownMessageType_success.json in Resources */ = {isa = PBXBuildFile; fileRef = DA7A5C000000000000000200 /* subscription_unknownMessageType_success.json */; }; 35C6B6E622F51A060054F242 /* AnyJSONType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C6B6E522F51A060054F242 /* AnyJSONType.swift */; }; 35C829DC23147AC000F59D3C /* SubscriptionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C829DB23147AC000F59D3C /* SubscriptionState.swift */; }; 35C8FDC625000BC80069E89E /* FileManagementRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35AE6A3525000AC700BBFA37 /* FileManagementRouterTests.swift */; }; @@ -635,6 +634,7 @@ DA7A5C00000000000000004C /* DataSyncQueryParameterAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA7A5C00000000000000004D /* DataSyncQueryParameterAPITests.swift */; }; DA7A5C00000000000000004E /* DataSyncErrorAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA7A5C00000000000000004F /* DataSyncErrorAPITests.swift */; }; DA7A5C000000000000000060 /* Subscribable+PubNub.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA7A5C000000000000000061 /* Subscribable+PubNub.swift */; }; + DA7A5C000000000000000201 /* subscription_unknownMessageType_success.json in Resources */ = {isa = PBXBuildFile; fileRef = DA7A5C000000000000000200 /* subscription_unknownMessageType_success.json */; }; DC1FB72D98DF046B1FA1EED0 /* subscription_dataSyncEntityDelete_success.json in Resources */ = {isa = PBXBuildFile; fileRef = F663C27813A8D0288A85B9E3 /* subscription_dataSyncEntityDelete_success.json */; }; F56167435CA49717E3E0A5A3 /* subscription_dataSyncUserUpdate_success.json in Resources */ = {isa = PBXBuildFile; fileRef = EB86FDD4F6AE4E75660509FA /* subscription_dataSyncUserUpdate_success.json */; }; FEECFED552025EE3D7897E0C /* subscription_dataSyncRelationshipUpdate_success.json in Resources */ = {isa = PBXBuildFile; fileRef = BCCCA408812B8BD920DE6401 /* subscription_dataSyncRelationshipUpdate_success.json */; }; @@ -1013,7 +1013,6 @@ 35B6FBB122F357A7005EE490 /* publish_invalid_json.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = publish_invalid_json.json; sourceTree = ""; }; 35C6B6DC22F501780054F242 /* Encodable+PubNub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encodable+PubNub.swift"; sourceTree = ""; }; 35C6B6DF22F513D80054F242 /* subscription_mixed_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscription_mixed_success.json; sourceTree = ""; }; - DA7A5C000000000000000200 /* subscription_unknownMessageType_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscription_unknownMessageType_success.json; sourceTree = ""; }; 35C6B6E522F51A060054F242 /* AnyJSONType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyJSONType.swift; sourceTree = ""; }; 35C829DB23147AC000F59D3C /* SubscriptionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionState.swift; sourceTree = ""; }; 35C8FDCA25003A9F0069E89E /* file_generateURL_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = file_generateURL_success.json; sourceTree = ""; }; @@ -1394,6 +1393,7 @@ DA7A5C00000000000000004D /* DataSyncQueryParameterAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSyncQueryParameterAPITests.swift; sourceTree = ""; }; DA7A5C00000000000000004F /* DataSyncErrorAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSyncErrorAPITests.swift; sourceTree = ""; }; DA7A5C000000000000000061 /* Subscribable+PubNub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Subscribable+PubNub.swift"; sourceTree = ""; }; + DA7A5C000000000000000200 /* subscription_unknownMessageType_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscription_unknownMessageType_success.json; sourceTree = ""; }; DA7B5C000000000000000065 /* data-sync-users.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "data-sync-users.swift"; sourceTree = ""; }; DA7B5C000000000000000066 /* data-sync-channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "data-sync-channels.swift"; sourceTree = ""; }; DA7B5C000000000000000067 /* data-sync-memberships.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "data-sync-memberships.swift"; sourceTree = ""; }; diff --git a/Snippets/ListenersOld/listeners-old.swift b/Snippets/ListenersOld/listeners-old.swift index eb311ff7..ef3dc017 100644 --- a/Snippets/ListenersOld/listeners-old.swift +++ b/Snippets/ListenersOld/listeners-old.swift @@ -134,19 +134,36 @@ listener.didReceiveBatchSubscription = { events in } case .dataSyncChanged(let dataSyncEvent): - switch dataSyncEvent { - case let .entityCreated(entity): - print("Data Sync entity created: \(entity.id)") - case let .entityUpdated(entity): - print("Data Sync entity updated: \(entity.id)") - case let .entityDeleted(entity): - print("Data Sync entity deleted: \(entity.id)") - case let .relationshipCreated(relationship): - print("Data Sync relationship created: \(relationship.id)") - case let .relationshipUpdated(relationship): - print("Data Sync relationship updated: \(relationship.id)") - case let .relationshipDeleted(relationship): - print("Data Sync relationship deleted: \(relationship.id)") + print("Received on \(dataSyncEvent.channel) at \(dataSyncEvent.timetoken)") + + switch dataSyncEvent.change { + case let .entityCreated(entityEvent): + let entity = entityEvent.entity + print("Entity \(entity.id) was created.") + print("Entity kind: \(entityEvent.kind)") + case let .entityUpdated(entityEvent): + let entity = entityEvent.entity + print("Entity \(entity.id) was updated.") + print("Entity kind: \(entityEvent.kind)") + case let .entityDeleted(entityEvent): + let removedEntity = entityEvent.removed + print("Entity \(removedEntity.id) was deleted.") + print("Entity kind: \(entityEvent.kind)") + case let .relationshipCreated(relationshipEvent): + let relationship = relationshipEvent.relationship + print("Relationship \(relationship.id) was created.") + print("Relationship kind: \(relationshipEvent.kind)") + case let .relationshipUpdated(relationshipEvent): + let relationship = relationshipEvent.relationship + print("Relationship \(relationship.id) was updated.") + print("Relationship kind: \(relationshipEvent.kind)") + case let .relationshipDeleted(relationshipEvent): + let removedRelationship = relationshipEvent.removed + print("Relationship \(removedRelationship.id) was deleted.") + print("Relationship kind: \(relationshipEvent.kind)") + case let .unknown(unknownEvent): + // An object type or action this SDK version doesn't recognize + print("Unrecognized \(unknownEvent.type) change: \(unknownEvent.event)") } } } diff --git a/Snippets/SubscribeNew/subscriptions.swift b/Snippets/SubscribeNew/subscriptions.swift index c8e56ac5..f8d04d2d 100644 --- a/Snippets/SubscribeNew/subscriptions.swift +++ b/Snippets/SubscribeNew/subscriptions.swift @@ -201,19 +201,36 @@ subscription.onAppContext = { appContextEvent in // snippet.on-data-sync // Add a listener to receive Data Sync entity and relationship events dataSyncSubscription.onDataSync = { dataSyncEvent in - switch dataSyncEvent { - case let .entityCreated(entity): - print("Data Sync entity created: \(entity.id)") - case let .entityUpdated(entity): - print("Data Sync entity updated: \(entity.id)") - case let .entityDeleted(entity): - print("Data Sync entity deleted: \(entity.id)") - case let .relationshipCreated(relationship): - print("Data Sync relationship created: \(relationship.id)") - case let .relationshipUpdated(relationship): - print("Data Sync relationship updated: \(relationship.id)") - case let .relationshipDeleted(relationship): - print("Data Sync relationship deleted: \(relationship.id)") + print("Received on \(dataSyncEvent.channel) at \(dataSyncEvent.timetoken)") + + switch dataSyncEvent.change { + case let .entityCreated(entityEvent): + let entity = entityEvent.entity + print("Entity \(entity.id) was created.") + print("Entity kind: \(entityEvent.kind)") + case let .entityUpdated(entityEvent): + let entity = entityEvent.entity + print("Entity \(entity.id) was updated.") + print("Entity kind: \(entityEvent.kind)") + case let .entityDeleted(entityEvent): + let removedEntity = entityEvent.removed + print("Entity \(removedEntity.id) was deleted.") + print("Entity kind: \(entityEvent.kind)") + case let .relationshipCreated(relationshipEvent): + let relationship = relationshipEvent.relationship + print("Relationship \(relationship.id) was created.") + print("Relationship kind: \(relationshipEvent.kind)") + case let .relationshipUpdated(relationshipEvent): + let relationship = relationshipEvent.relationship + print("Relationship \(relationship.id) was updated.") + print("Relationship kind: \(relationshipEvent.kind)") + case let .relationshipDeleted(relationshipEvent): + let removedRelationship = relationshipEvent.removed + print("Relationship \(removedRelationship.id) was deleted.") + print("Relationship kind: \(relationshipEvent.kind)") + case let .unknown(unknownEvent): + // An object type or action this SDK version doesn't recognize + print("Unrecognized \(unknownEvent.type) change: \(unknownEvent.event)") } } // snippet.end diff --git a/Sources/PubNub/KMP/Wrappers/KMPDataSyncEvent.swift b/Sources/PubNub/KMP/Wrappers/KMPDataSyncEvent.swift index 7f37d9f4..6bc85a21 100644 --- a/Sources/PubNub/KMP/Wrappers/KMPDataSyncEvent.swift +++ b/Sources/PubNub/KMP/Wrappers/KMPDataSyncEvent.swift @@ -21,10 +21,14 @@ import Foundation @objc public class KMPDataSyncEvent: NSObject { - @objc public let event: String - - init(event: String = "") { - self.event = event + @objc public let channel: String + @objc public let subscription: String? + @objc public let timetoken: Timetoken + + init(channel: String = "", subscription: String? = nil, timetoken: Timetoken = 0) { + self.channel = channel + self.subscription = subscription + self.timetoken = timetoken } } @@ -32,11 +36,14 @@ public class KMPDataSyncEvent: NSObject { @objc public class KMPDataSyncEntityCreatedResult: KMPDataSyncEvent { + @objc public let kind: String @objc public let entity: KMPDataSyncEntity - init(entity: KMPDataSyncEntity) { - self.entity = entity - super.init(event: "entityCreated") + init(entityEvent: PubNubDataSyncEntityEvent, channel: String, subscription: String?, timetoken: Timetoken) { + self.kind = entityEvent.kind.rawValue + self.entity = KMPDataSyncEntity(entity: entityEvent.entity) + + super.init(channel: channel, subscription: subscription, timetoken: timetoken) } } @@ -44,11 +51,14 @@ public class KMPDataSyncEntityCreatedResult: KMPDataSyncEvent { @objc public class KMPDataSyncEntityUpdatedResult: KMPDataSyncEvent { + @objc public let kind: String @objc public let entity: KMPDataSyncEntity - init(entity: KMPDataSyncEntity) { - self.entity = entity - super.init(event: "entityUpdated") + init(entityEvent: PubNubDataSyncEntityEvent, channel: String, subscription: String?, timetoken: Timetoken) { + self.kind = entityEvent.kind.rawValue + self.entity = KMPDataSyncEntity(entity: entityEvent.entity) + + super.init(channel: channel, subscription: subscription, timetoken: timetoken) } } @@ -56,11 +66,14 @@ public class KMPDataSyncEntityUpdatedResult: KMPDataSyncEvent { @objc public class KMPDataSyncEntityDeletedResult: KMPDataSyncEvent { - @objc public let removedObject: KMPDataSyncRemovedObject + @objc public let kind: String + @objc public let removedEntity: KMPDataSyncRemovedEntity + + init(entityEvent: PubNubDataSyncEntityDeletedEvent, channel: String, subscription: String?, timetoken: Timetoken) { + self.kind = entityEvent.kind.rawValue + self.removedEntity = KMPDataSyncRemovedEntity(removedEntity: entityEvent.removed) - init(removedObject: KMPDataSyncRemovedObject) { - self.removedObject = removedObject - super.init(event: "entityDeleted") + super.init(channel: channel, subscription: subscription, timetoken: timetoken) } } @@ -68,11 +81,14 @@ public class KMPDataSyncEntityDeletedResult: KMPDataSyncEvent { @objc public class KMPDataSyncRelationshipCreatedResult: KMPDataSyncEvent { + @objc public let kind: String @objc public let relationship: KMPDataSyncRelationship - init(relationship: KMPDataSyncRelationship) { - self.relationship = relationship - super.init(event: "relationshipCreated") + init(relationshipEvent: PubNubDataSyncRelationshipEvent, channel: String, subscription: String?, timetoken: Timetoken) { + self.kind = relationshipEvent.kind.rawValue + self.relationship = KMPDataSyncRelationship(relationship: relationshipEvent.relationship) + + super.init(channel: channel, subscription: subscription, timetoken: timetoken) } } @@ -80,11 +96,14 @@ public class KMPDataSyncRelationshipCreatedResult: KMPDataSyncEvent { @objc public class KMPDataSyncRelationshipUpdatedResult: KMPDataSyncEvent { + @objc public let kind: String @objc public let relationship: KMPDataSyncRelationship - init(relationship: KMPDataSyncRelationship) { - self.relationship = relationship - super.init(event: "relationshipUpdated") + init(relationshipEvent: PubNubDataSyncRelationshipEvent, channel: String, subscription: String?, timetoken: Timetoken) { + self.kind = relationshipEvent.kind.rawValue + self.relationship = KMPDataSyncRelationship(relationship: relationshipEvent.relationship) + + super.init(channel: channel, subscription: subscription, timetoken: timetoken) } } @@ -92,18 +111,47 @@ public class KMPDataSyncRelationshipUpdatedResult: KMPDataSyncEvent { @objc public class KMPDataSyncRelationshipDeletedResult: KMPDataSyncEvent { - @objc public let removedObject: KMPDataSyncRemovedRelationship + @objc public let kind: String + @objc public let removedRelationship: KMPDataSyncRemovedRelationship + + init(relationshipEvent: PubNubDataSyncRelationshipDeletedEvent, channel: String, subscription: String?, timetoken: Timetoken) { + self.kind = relationshipEvent.kind.rawValue + self.removedRelationship = KMPDataSyncRemovedRelationship(removedRelationship: relationshipEvent.removed) + + super.init(channel: channel, subscription: subscription, timetoken: timetoken) + } +} - init(removedObject: KMPDataSyncRemovedRelationship) { - self.removedObject = removedObject - super.init(event: "relationshipDeleted") +// MARK: - KMPDataSyncUnknownResult + +@objc +public class KMPDataSyncUnknownResult: KMPDataSyncEvent { + @objc public let type: String + @objc public let event: String + // Named `objectClass` rather than `className` because `NSObject` already exposes a `className` selector + @objc public let objectClass: String? + @objc public let classLevel: String? + @objc public let classVersion: NSNumber? + @objc public let metadata: KMPAnyJSON? + @objc public let payload: KMPAnyJSON? + + init(unknownEvent: PubNubDataSyncUnknownEvent, channel: String, subscription: String?, timetoken: Timetoken) { + self.type = unknownEvent.type + self.event = unknownEvent.event + self.objectClass = unknownEvent.className + self.classLevel = unknownEvent.classLevel?.stringValue + self.classVersion = if let version = unknownEvent.classVersion { NSNumber(value: version) } else { nil } + self.metadata = if let metadata = unknownEvent.concreteMetadata { KMPAnyJSON(metadata) } else { nil } + self.payload = if let payload = unknownEvent.concretePayload { KMPAnyJSON(payload) } else { nil } + + super.init(channel: channel, subscription: subscription, timetoken: timetoken) } } -// MARK: - KMPDataSyncRemovedObject +// MARK: - KMPDataSyncRemovedEntity @objc -public class KMPDataSyncRemovedObject: NSObject { +public class KMPDataSyncRemovedEntity: NSObject { @objc public let id: String // Named `objectClass` rather than `className` because `NSObject` already exposes a `className` selector @objc public let objectClass: String @@ -111,12 +159,12 @@ public class KMPDataSyncRemovedObject: NSObject { @objc public let classVersion: Int @objc public let deletedAt: Date - init(removedObject: PubNubDataSyncRemovedObject) { - self.id = removedObject.id - self.objectClass = removedObject.className - self.classLevel = removedObject.classLevel.stringValue - self.classVersion = removedObject.classVersion - self.deletedAt = removedObject.deletedAt + init(removedEntity: PubNubDataSyncRemovedEntity) { + self.id = removedEntity.id + self.objectClass = removedEntity.className + self.classLevel = removedEntity.classLevel.stringValue + self.classVersion = removedEntity.classVersion + self.deletedAt = removedEntity.deletedAt } } @@ -127,12 +175,14 @@ public class KMPDataSyncRemovedRelationship: NSObject { @objc public let id: String // Named `objectClass` rather than `className` because `NSObject` already exposes a `className` selector @objc public let objectClass: String + @objc public let classLevel: String @objc public let classVersion: Int @objc public let deletedAt: Date init(removedRelationship: PubNubDataSyncRemovedRelationship) { self.id = removedRelationship.id self.objectClass = removedRelationship.className + self.classLevel = removedRelationship.classLevel.stringValue self.classVersion = removedRelationship.classVersion self.deletedAt = removedRelationship.deletedAt } @@ -142,20 +192,45 @@ public class KMPDataSyncRemovedRelationship: NSObject { extension KMPDataSyncEvent { static func from(event: PubNubDataSyncEvent) -> KMPDataSyncEvent { - switch event { - case .entityCreated(let entity): - return KMPDataSyncEntityCreatedResult(entity: KMPDataSyncEntity(entity: entity)) - case .entityUpdated(let entity): - return KMPDataSyncEntityUpdatedResult(entity: KMPDataSyncEntity(entity: entity)) - case .entityDeleted(let removedObject): - return KMPDataSyncEntityDeletedResult(removedObject: KMPDataSyncRemovedObject(removedObject: removedObject)) - case .relationshipCreated(let relationship): - return KMPDataSyncRelationshipCreatedResult(relationship: KMPDataSyncRelationship(relationship: relationship)) - case .relationshipUpdated(let relationship): - return KMPDataSyncRelationshipUpdatedResult(relationship: KMPDataSyncRelationship(relationship: relationship)) - case .relationshipDeleted(let removedRelationship): + let channel = event.channel + let subscription = event.subscription + let timetoken = event.timetoken + + switch event.change { + case .entityCreated(let entityEvent): + return KMPDataSyncEntityCreatedResult( + entityEvent: entityEvent, + channel: channel, subscription: subscription, timetoken: timetoken + ) + case .entityUpdated(let entityEvent): + return KMPDataSyncEntityUpdatedResult( + entityEvent: entityEvent, + channel: channel, subscription: subscription, timetoken: timetoken + ) + case .entityDeleted(let entityEvent): + return KMPDataSyncEntityDeletedResult( + entityEvent: entityEvent, + channel: channel, subscription: subscription, timetoken: timetoken + ) + case .relationshipCreated(let relationshipEvent): + return KMPDataSyncRelationshipCreatedResult( + relationshipEvent: relationshipEvent, + channel: channel, subscription: subscription, timetoken: timetoken + ) + case .relationshipUpdated(let relationshipEvent): + return KMPDataSyncRelationshipUpdatedResult( + relationshipEvent: relationshipEvent, + channel: channel, subscription: subscription, timetoken: timetoken + ) + case .relationshipDeleted(let relationshipEvent): return KMPDataSyncRelationshipDeletedResult( - removedObject: KMPDataSyncRemovedRelationship(removedRelationship: removedRelationship) + relationshipEvent: relationshipEvent, + channel: channel, subscription: subscription, timetoken: timetoken + ) + case .unknown(let unknownEvent): + return KMPDataSyncUnknownResult( + unknownEvent: unknownEvent, + channel: channel, subscription: subscription, timetoken: timetoken ) } } diff --git a/Sources/PubNub/Models/PubNubMessage.swift b/Sources/PubNub/Models/PubNubMessage.swift index 9f1a9752..465fe45b 100644 --- a/Sources/PubNub/Models/PubNubMessage.swift +++ b/Sources/PubNub/Models/PubNubMessage.swift @@ -16,6 +16,7 @@ public enum PubNubMessageType: Int, Codable, Hashable { case object = 2 case messageAction = 3 case file = 4 + case dataSync = 5 case unknown = 999 } diff --git a/Sources/PubNub/Networking/Routers/Subscribe Payloads/SubscribeDataSyncPayload.swift b/Sources/PubNub/Networking/Routers/Subscribe Payloads/SubscribeDataSyncPayload.swift index 867b8612..f23679dc 100644 --- a/Sources/PubNub/Networking/Routers/Subscribe Payloads/SubscribeDataSyncPayload.swift +++ b/Sources/PubNub/Networking/Routers/Subscribe Payloads/SubscribeDataSyncPayload.swift @@ -11,9 +11,7 @@ import Foundation struct SubscribeDataSyncPayload { - let version: String - let source: String - let event: PubNubDataSyncEvent + let change: PubNubDataSyncEvent.Change enum Action: String, Codable, Hashable { case create @@ -32,7 +30,6 @@ struct SubscribeDataSyncPayload { extension SubscribeDataSyncPayload: Decodable { enum CodingKeys: String, CodingKey { - case version case metadata case data } @@ -61,15 +58,44 @@ extension SubscribeDataSyncPayload: Decodable { case userId } + // swiftlint:disable:next function_body_length init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let metadata = try container.nestedContainer(keyedBy: MetadataCodingKeys.self, forKey: .metadata) - version = try container.decode(String.self, forKey: .version) - source = try metadata.decode(String.self, forKey: .source) + let source = try metadata.decode(String.self, forKey: .source) + let rawAction = try metadata.decode(String.self, forKey: .event) + let rawType = try metadata.decode(String.self, forKey: .type) + + // Anything that isn't recognizably a DataSync envelope is rejected rather than reported as an + // unknown change, so `PubNubDataSyncUnknownEvent` only ever describes a DataSync change + guard source == "data-sync", !rawAction.isEmpty, !rawType.isEmpty else { + throw DecodingError.dataCorruptedError( + forKey: .metadata, + in: container, + debugDescription: "Not a DataSync envelope: source '\(source)', event '\(rawAction)', type '\(rawType)'" + ) + } + + // An object type or action added to the service after this SDK version was released is + // surfaced as a DataSync change with its envelope intact, rather than falling back to a message + guard let action = Action(rawValue: rawAction), let type = ObjectType(rawValue: rawType) else { + change = .unknown( + PubNubDataSyncUnknownEvent( + type: rawType, + event: rawAction, + className: try metadata.decodeIfPresent(String.self, forKey: .className), + classLevel: try metadata.decodeIfPresent(String.self, forKey: .classLevel).map { + PubNubDataSyncClassLevel(stringValue: $0) + }, + classVersion: try metadata.decodeIfPresent(Int.self, forKey: .classVersion), + metadata: try container.decodeIfPresent(AnyJSON.self, forKey: .metadata), + payload: try container.decodeIfPresent(AnyJSON.self, forKey: .data) + ) + ) + return + } - let action = try metadata.decode(Action.self, forKey: .event) - let type = try metadata.decode(ObjectType.self, forKey: .type) let className = try metadata.decode(String.self, forKey: .className) let classLevel = PubNubDataSyncClassLevel(stringValue: try metadata.decode(String.self, forKey: .classLevel)) let classVersion = try metadata.decode(Int.self, forKey: .classVersion) @@ -91,7 +117,13 @@ extension SubscribeDataSyncPayload: Decodable { status: try data.decodeIfPresent(String.self, forKey: .status), payload: try data.decodeIfPresent(AnyJSON.self, forKey: .payload) ) - event = action == .create ? .entityCreated(entity) : .entityUpdated(entity) + let entityEvent = PubNubDataSyncEntityEvent( + kind: type.entityKind, + entity: entity + ) + + change = action == .create ? .entityCreated(entityEvent) : .entityUpdated(entityEvent) + case (.relationship, .create), (.relationship, .update), (.membership, .create), (.membership, .update): // A membership frames its two sides as `channelId` and `userId` let entityAKey: DataCodingKeys = type == .membership ? .channelId : .entityAId @@ -110,31 +142,78 @@ extension SubscribeDataSyncPayload: Decodable { status: try data.decodeIfPresent(String.self, forKey: .status), payload: try data.decodeIfPresent(AnyJSON.self, forKey: .payload) ) - event = action == .create ? .relationshipCreated(relationship) : .relationshipUpdated(relationship) + let relationshipEvent = PubNubDataSyncRelationshipEvent( + kind: type.relationshipKind, + relationship: relationship + ) + + change = action == .create ? .relationshipCreated(relationshipEvent) : .relationshipUpdated(relationshipEvent) + case (.entity, .delete), (.user, .delete), (.channel, .delete): - let removed = PubNubDataSyncRemovedObject( + let removed = PubNubDataSyncRemovedEntity( id: identifier, className: className, classLevel: classLevel, classVersion: classVersion, deletedAt: try data.decode(Date.self, forKey: .deletedAt) ) - event = .entityDeleted(removed) + change = .entityDeleted( + PubNubDataSyncEntityDeletedEvent( + kind: type.entityKind, + removed: removed + ) + ) + case (.relationship, .delete), (.membership, .delete): - event = .relationshipDeleted( - PubNubDataSyncRemovedRelationship( - id: identifier, - className: className, - classVersion: classVersion, - deletedAt: try data.decode(Date.self, forKey: .deletedAt) + change = .relationshipDeleted( + PubNubDataSyncRelationshipDeletedEvent( + kind: type.relationshipKind, + removed: PubNubDataSyncRemovedRelationship( + id: identifier, + className: className, + classLevel: classLevel, + classVersion: classVersion, + deletedAt: try data.decode(Date.self, forKey: .deletedAt) + ) ) ) } } } +private extension SubscribeDataSyncPayload.ObjectType { + var entityKind: PubNubDataSyncEntityKind { + switch self { + case .entity: + return .custom + case .user: + return .user + case .channel: + return .channel + case .relationship, .membership: + preconditionFailure("A relationship type cannot be converted to an entity kind") + } + } + + var relationshipKind: PubNubDataSyncRelationshipKind { + switch self { + case .relationship: + return .custom + case .membership: + return .membership + case .entity, .user, .channel: + preconditionFailure("An entity type cannot be converted to a relationship kind") + } + } +} + extension SubscribeMessagePayload { - func asDataSyncEvent() -> PubNubDataSyncEvent? { - try? payload.decode(SubscribeDataSyncPayload.self).event + func decodeDataSyncEvent() throws -> PubNubDataSyncEvent { + PubNubDataSyncEvent( + channel: channel, + subscription: subscription, + timetoken: publishTimetoken.timetoken, + change: try payload.decode(SubscribeDataSyncPayload.self).change + ) } } diff --git a/Sources/PubNub/Networking/Routers/SubscribeRouter.swift b/Sources/PubNub/Networking/Routers/SubscribeRouter.swift index ed4dab98..82f24d0f 100644 --- a/Sources/PubNub/Networking/Routers/SubscribeRouter.swift +++ b/Sources/PubNub/Networking/Routers/SubscribeRouter.swift @@ -352,7 +352,7 @@ public struct SubscribeMessagePayload: Codable, Hashable, CustomStringConvertibl case .file: return .file case .dataSync: - return .unknown + return .dataSync case .presence: return .unknown } diff --git a/Sources/PubNub/Subscribe/API/PubNubDataSyncEvent.swift b/Sources/PubNub/Subscribe/API/PubNubDataSyncEvent.swift index c175a0ff..d5e878ce 100644 --- a/Sources/PubNub/Subscribe/API/PubNubDataSyncEvent.swift +++ b/Sources/PubNub/Subscribe/API/PubNubDataSyncEvent.swift @@ -10,33 +10,136 @@ import Foundation -/// Possible subevents for DataSync -public enum PubNubDataSyncEvent { - /// An entity was created - case entityCreated(PubNubDataSyncEntity) - /// An entity was updated - case entityUpdated(PubNubDataSyncEntity) - /// An entity was deleted - case entityDeleted(PubNubDataSyncRemovedObject) - /// A relationship was created - case relationshipCreated(PubNubDataSyncRelationship) - /// A relationship was updated - case relationshipUpdated(PubNubDataSyncRelationship) - /// A relationship was deleted - case relationshipDeleted(PubNubDataSyncRemovedRelationship) -} - -/// A DataSync entity or relationship that was deleted. -public struct PubNubDataSyncRemovedObject: Hashable { - /// The unique identifier of the deleted entity or relationship +/// The type of a DataSync entity event. +public enum PubNubDataSyncEntityKind: String, Hashable { + /// A user, including classes that extend User + case user + /// A channel, including classes that extend Channel + case channel + /// A custom entity + case custom +} + +/// The type of a DataSync relationship event. +public enum PubNubDataSyncRelationshipKind: String, Hashable { + /// A membership + case membership + /// A custom relationship + case custom +} + +/// A created or updated DataSync entity event. +public struct PubNubDataSyncEntityEvent: Hashable { + /// The entity type + public let kind: PubNubDataSyncEntityKind + /// The created or updated entity + public let entity: PubNubDataSyncEntity +} + +/// A deleted DataSync entity event. +public struct PubNubDataSyncEntityDeletedEvent: Hashable { + /// The entity type + public let kind: PubNubDataSyncEntityKind + /// Information identifying the deleted entity + public let removed: PubNubDataSyncRemovedEntity +} + +/// A created or updated DataSync relationship event. +public struct PubNubDataSyncRelationshipEvent: Hashable { + /// The relationship type + public let kind: PubNubDataSyncRelationshipKind + /// The created or updated relationship + public let relationship: PubNubDataSyncRelationship +} + +/// A deleted DataSync relationship event. +public struct PubNubDataSyncRelationshipDeletedEvent: Hashable { + /// The relationship type + public let kind: PubNubDataSyncRelationshipKind + /// Information identifying the deleted relationship + public let removed: PubNubDataSyncRemovedRelationship +} + +/// A DataSync change received over subscribe. +public struct PubNubDataSyncEvent: Hashable { + /// The channel the event was received on + public let channel: String + /// The channel group or wildcard subscription match (if exists) + public let subscription: String? + /// The `Timetoken` for when the event was published + public let timetoken: Timetoken + /// The change the event describes + public let change: Change + + /// Possible DataSync changes + public enum Change: Hashable { + /// An entity was created + case entityCreated(PubNubDataSyncEntityEvent) + /// An entity was updated + case entityUpdated(PubNubDataSyncEntityEvent) + /// An entity was deleted + case entityDeleted(PubNubDataSyncEntityDeletedEvent) + /// A relationship was created + case relationshipCreated(PubNubDataSyncRelationshipEvent) + /// A relationship was updated + case relationshipUpdated(PubNubDataSyncRelationshipEvent) + /// A relationship was deleted + case relationshipDeleted(PubNubDataSyncRelationshipDeletedEvent) + /// A change this version of the SDK doesn't recognize. + case unknown(PubNubDataSyncUnknownEvent) + } +} + +/// A DataSync change whose object type or action this version of the SDK doesn't recognize. +public struct PubNubDataSyncUnknownEvent: Hashable { + /// The object type received from the server, taken from `metadata.type` + public let type: String + /// The action received from the server, taken from `metadata.event` + public let event: String + /// The name of the object's class, if the server sent one + public let className: String? + /// The level the object's class is registered at, if the server sent one + public let classLevel: PubNubDataSyncClassLevel? + /// The version of the object's class, if the server sent one + public let classVersion: Int? + /// The unmodified `metadata` object + public var metadata: JSONCodable? { concreteMetadata } + /// The unmodified `data` object, if the server sent one + public var payload: JSONCodable? { concretePayload } + + let concreteMetadata: AnyJSON? + let concretePayload: AnyJSON? + + init( + type: String, + event: String, + className: String? = nil, + classLevel: PubNubDataSyncClassLevel? = nil, + classVersion: Int? = nil, + metadata: JSONCodable? = nil, + payload: JSONCodable? = nil + ) { + self.type = type + self.event = event + self.className = className + self.classLevel = classLevel + self.classVersion = classVersion + self.concreteMetadata = metadata?.codableValue + self.concretePayload = payload?.codableValue + } +} + +/// A DataSync entity that was deleted. +public struct PubNubDataSyncRemovedEntity: Hashable { + /// The unique identifier of the deleted entity public let id: String - /// The name of the deleted object's class + /// The name of the deleted entity's class public let className: String - /// The level the deleted object's class is registered at + /// The level the deleted entity's class is registered at public let classLevel: PubNubDataSyncClassLevel - /// The version of the deleted object's class + /// The version of the deleted entity's class public let classVersion: Int - /// The date the object was deleted + /// The date the entity was deleted public let deletedAt: Date } @@ -46,6 +149,8 @@ public struct PubNubDataSyncRemovedRelationship: Hashable { public let id: String /// The name of the deleted relationship's class public let className: String + /// The level the deleted relationship's class is registered at + public let classLevel: PubNubDataSyncClassLevel /// The version of the deleted relationship's class public let classVersion: Int /// The date the relationship was deleted diff --git a/Sources/PubNub/Subscribe/API/Subscription.swift b/Sources/PubNub/Subscribe/API/Subscription.swift index 07ef8df7..fdabf976 100644 --- a/Sources/PubNub/Subscribe/API/Subscription.swift +++ b/Sources/PubNub/Subscribe/API/Subscription.swift @@ -152,11 +152,14 @@ extension Subscription: SubscribeMessagesReceiver { } func event(from payload: SubscribeMessagePayload) -> PubNubEvent? { - guard subscriptionTopology.matches(payload), payload.publishTimetoken.timetoken >= timetoken ?? 0 else { + guard + subscriptionTopology.matches(payload), + payload.publishTimetoken.timetoken >= timetoken ?? 0, + let event = payload.asPubNubEvent() + else { return nil } - let event = payload.asPubNubEvent() return options.filterCriteriaSatisfied(event: event) ? event : nil } } diff --git a/Sources/PubNub/Subscribe/Core/SubscriptionSession.swift b/Sources/PubNub/Subscribe/Core/SubscriptionSession.swift index 83b119e6..114f2ded 100644 --- a/Sources/PubNub/Subscribe/Core/SubscriptionSession.swift +++ b/Sources/PubNub/Subscribe/Core/SubscriptionSession.swift @@ -333,7 +333,7 @@ extension SubscriptionSession: SubscribeMessagesReceiver { func onPayloadsReceived(payloads: [SubscribeMessagePayload]) -> [PubNubEvent] { // Translates payloads into PubNub Subscibe Loop events - let events = payloads.map { $0.asPubNubEvent() } + let events = payloads.compactMap { $0.asPubNubEvent() } // Emits events from the SubscriptionSession emit(events: events) // Emits events to the underlying attached listeners diff --git a/Sources/PubNub/Subscribe/Legacy/SubscriptionStream.swift b/Sources/PubNub/Subscribe/Legacy/SubscriptionStream.swift index 76f4f722..62b610a6 100644 --- a/Sources/PubNub/Subscribe/Legacy/SubscriptionStream.swift +++ b/Sources/PubNub/Subscribe/Legacy/SubscriptionStream.swift @@ -110,7 +110,7 @@ public final class CoreListener: BaseSubscriptionListener { } override public func emit(batch: [SubscribeMessagePayload]) { - emitDidReceive(subscription: batch.map { $0.asPubNubEvent() }) + emitDidReceive(subscription: batch.compactMap { $0.asPubNubEvent() }) } public func emitDidReceive(subscription batch: [PubNubEvent]) { diff --git a/Sources/PubNub/Subscribe/Mapping/SubscribeMessagePayload+PubNubEvent.swift b/Sources/PubNub/Subscribe/Mapping/SubscribeMessagePayload+PubNubEvent.swift index 1719f5de..d0451957 100644 --- a/Sources/PubNub/Subscribe/Mapping/SubscribeMessagePayload+PubNubEvent.swift +++ b/Sources/PubNub/Subscribe/Mapping/SubscribeMessagePayload+PubNubEvent.swift @@ -12,7 +12,7 @@ import Foundation extension SubscribeMessagePayload { // swiftlint:disable:next cyclomatic_complexity - func asPubNubEvent() -> PubNubEvent { + func asPubNubEvent() -> PubNubEvent? { switch messageType { case .message: return .messageReceived(PubNubMessageBase(from: self)) @@ -43,10 +43,10 @@ extension SubscribeMessagePayload { } return .fileChanged(.uploaded(fileMessage)) case .dataSync: - guard let dataSyncEvent = asDataSyncEvent() else { + guard let event = try? decodeDataSyncEvent() else { return .messageReceived(PubNubMessageBase(from: self)) } - return .dataSyncChanged(dataSyncEvent) + return .dataSyncChanged(event) case .presence: guard let presence = PubNubPresenceChangeBase(from: self) else { return .messageReceived(PubNubMessageBase(from: self)) diff --git a/Tests/PubNubIntegrationTests/DataSyncChannelEventIntegrationTests.swift b/Tests/PubNubIntegrationTests/DataSyncChannelEventIntegrationTests.swift index 4c232fa8..64b3640f 100644 --- a/Tests/PubNubIntegrationTests/DataSyncChannelEventIntegrationTests.swift +++ b/Tests/PubNubIntegrationTests/DataSyncChannelEventIntegrationTests.swift @@ -36,16 +36,19 @@ final class DataSyncChannelEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityCreated(entity) = event, entity.id == channelId else { + guard case let .entityCreated(entityEvent) = event.change, entityEvent.entity.id == channelId else { return } + let entity = entityEvent.entity + XCTAssertEqual(entityEvent.kind, .channel) XCTAssertEqual(entity.className, "Channel") XCTAssertEqual(entity.classLevel, .global) XCTAssertEqual(entity.classVersion, self.channelClassVersion) XCTAssertEqual(entity.status, "active") XCTAssertFalse(entity.eTag.isEmpty) XCTAssertNotNil(entity.payload) + createExpect.fulfill() } @@ -102,15 +105,18 @@ final class DataSyncChannelEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityUpdated(entity) = event, entity.id == channelId else { + guard case let .entityUpdated(entityEvent) = event.change, entityEvent.entity.id == channelId else { return } + let entity = entityEvent.entity + XCTAssertEqual(entityEvent.kind, .channel) XCTAssertEqual(entity.className, "Channel") XCTAssertEqual(entity.classLevel, .global) XCTAssertEqual(entity.classVersion, self.channelClassVersion) XCTAssertFalse(entity.eTag.isEmpty) XCTAssertNotNil(entity.payload) + updateExpect.fulfill() } @@ -162,13 +168,16 @@ final class DataSyncChannelEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityDeleted(removed) = event, removed.id == channelId else { + guard case let .entityDeleted(entityEvent) = event.change, entityEvent.removed.id == channelId else { return } + let removed = entityEvent.removed + XCTAssertEqual(entityEvent.kind, .channel) XCTAssertEqual(removed.className, "Channel") XCTAssertEqual(removed.classLevel, .global) XCTAssertEqual(removed.classVersion, self.channelClassVersion) + deleteExpect.fulfill() } diff --git a/Tests/PubNubIntegrationTests/DataSyncEntityEndpointIntegrationTests.swift b/Tests/PubNubIntegrationTests/DataSyncEntityEndpointIntegrationTests.swift index 4cc50e90..9788faf6 100644 --- a/Tests/PubNubIntegrationTests/DataSyncEntityEndpointIntegrationTests.swift +++ b/Tests/PubNubIntegrationTests/DataSyncEntityEndpointIntegrationTests.swift @@ -490,7 +490,7 @@ class DataSyncEntityEndpointIntegrationTests: XCTestCase { patchExpect.fulfill() case let .failure(error): XCTAssertNotNil(error.pubNubError) - XCTAssertEqual(error.pubNubError?.reason, .badRequest) + XCTAssertEqual(error.pubNubError?.reason, .conflict) client.dataSync.getEntity(id: patientId) { fetchResult in switch fetchResult { diff --git a/Tests/PubNubIntegrationTests/DataSyncEntityEventIntegrationTests.swift b/Tests/PubNubIntegrationTests/DataSyncEntityEventIntegrationTests.swift index d2d072c1..892e7eb2 100644 --- a/Tests/PubNubIntegrationTests/DataSyncEntityEventIntegrationTests.swift +++ b/Tests/PubNubIntegrationTests/DataSyncEntityEventIntegrationTests.swift @@ -44,14 +44,17 @@ final class DataSyncEntityEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityCreated(entity) = event, entity.id == self.patientId else { + guard case let .entityCreated(entityEvent) = event.change, entityEvent.entity.id == self.patientId else { return } + let entity = entityEvent.entity + XCTAssertEqual(entityEvent.kind, .custom) XCTAssertEqual(entity.id, "swift-patient-alice") XCTAssertEqual(entity.className, HealthcareClass.patient.name) XCTAssertFalse(entity.eTag.isEmpty) XCTAssertNotNil(entity.payload) + createExpect.fulfill() } @@ -106,14 +109,17 @@ final class DataSyncEntityEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityUpdated(entity) = event, entity.id == self.patientId else { + guard case let .entityUpdated(entityEvent) = event.change, entityEvent.entity.id == self.patientId else { return } + let entity = entityEvent.entity + XCTAssertEqual(entityEvent.kind, .custom) XCTAssertEqual(entity.id, "swift-patient-alice") XCTAssertEqual(entity.className, HealthcareClass.patient.name) XCTAssertFalse(entity.eTag.isEmpty) XCTAssertNotNil(entity.payload) + updateExpect.fulfill() } @@ -164,12 +170,15 @@ final class DataSyncEntityEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityDeleted(removed) = event, removed.id == self.patientId else { + guard case let .entityDeleted(entityEvent) = event.change, entityEvent.removed.id == self.patientId else { return } + let removed = entityEvent.removed + XCTAssertEqual(entityEvent.kind, .custom) XCTAssertEqual(removed.id, "swift-patient-alice") XCTAssertEqual(removed.className, HealthcareClass.patient.name) + deleteExpect.fulfill() } @@ -218,10 +227,12 @@ final class DataSyncEntityEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .relationshipCreated(relationship) = event, relationship.id == self.relationshipId else { + guard case let .relationshipCreated(relEvent) = event.change, relEvent.relationship.id == self.relationshipId else { return } + let relationship = relEvent.relationship + XCTAssertEqual(relEvent.kind, .custom) XCTAssertEqual(relationship.id, "swift-rel-attending-carter-alice") XCTAssertEqual(relationship.className, HealthcareClass.attendingPhysician.name) XCTAssertEqual(relationship.entityAId, "swift-practitioner-carter") @@ -298,16 +309,19 @@ final class DataSyncEntityEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .relationshipUpdated(relationship) = event, relationship.id == self.relationshipId else { + guard case let .relationshipUpdated(relEvent) = event.change, relEvent.relationship.id == self.relationshipId else { return } + let relationship = relEvent.relationship + XCTAssertEqual(relEvent.kind, .custom) XCTAssertEqual(relationship.id, "swift-rel-attending-carter-alice") XCTAssertEqual(relationship.className, HealthcareClass.attendingPhysician.name) XCTAssertEqual(relationship.entityAId, "swift-practitioner-carter") XCTAssertEqual(relationship.entityBId, "swift-patient-alice") XCTAssertFalse(relationship.eTag.isEmpty) XCTAssertNotNil(relationship.payload) + updateExpect.fulfill() } @@ -373,12 +387,15 @@ final class DataSyncEntityEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .relationshipDeleted(removed) = event, removed.id == self.relationshipId else { + guard case let .relationshipDeleted(relEvent) = event.change, relEvent.removed.id == self.relationshipId else { return } + let removed = relEvent.removed + XCTAssertEqual(relEvent.kind, .custom) XCTAssertEqual(removed.id, "swift-rel-attending-carter-alice") XCTAssertEqual(removed.className, HealthcareClass.attendingPhysician.name) + deleteExpect.fulfill() } diff --git a/Tests/PubNubIntegrationTests/DataSyncMembershipEventIntegrationTests.swift b/Tests/PubNubIntegrationTests/DataSyncMembershipEventIntegrationTests.swift index 23448930..d64bdbf1 100644 --- a/Tests/PubNubIntegrationTests/DataSyncMembershipEventIntegrationTests.swift +++ b/Tests/PubNubIntegrationTests/DataSyncMembershipEventIntegrationTests.swift @@ -44,10 +44,12 @@ final class DataSyncMembershipEventIntegrationTests: XCTestCase { ) subscription.onDataSync = { event in - guard case let .relationshipCreated(relationship) = event, relationship.id == membershipId else { + guard case let .relationshipCreated(relEvent) = event.change, relEvent.relationship.id == membershipId else { return } + let relationship = relEvent.relationship + XCTAssertEqual(relEvent.kind, .membership) XCTAssertEqual(relationship.className, "Membership") XCTAssertEqual(relationship.classVersion, self.membershipClassVersion) XCTAssertEqual(relationship.entityAId, channelId) @@ -55,6 +57,7 @@ final class DataSyncMembershipEventIntegrationTests: XCTestCase { XCTAssertEqual(relationship.status, "active") XCTAssertFalse(relationship.eTag.isEmpty) XCTAssertNotNil(relationship.payload) + createExpect.fulfill() } @@ -122,16 +125,19 @@ final class DataSyncMembershipEventIntegrationTests: XCTestCase { ) subscription.onDataSync = { event in - guard case let .relationshipUpdated(relationship) = event, relationship.id == membershipId else { + guard case let .relationshipUpdated(relEvent) = event.change, relEvent.relationship.id == membershipId else { return } + let relationship = relEvent.relationship + XCTAssertEqual(relEvent.kind, .membership) XCTAssertEqual(relationship.className, "Membership") XCTAssertEqual(relationship.classVersion, self.membershipClassVersion) XCTAssertEqual(relationship.entityAId, channelId) XCTAssertEqual(relationship.entityBId, userId) XCTAssertFalse(relationship.eTag.isEmpty) XCTAssertNotNil(relationship.payload) + updateExpect.fulfill() } @@ -195,12 +201,15 @@ final class DataSyncMembershipEventIntegrationTests: XCTestCase { ) subscription.onDataSync = { event in - guard case let .relationshipDeleted(removed) = event, removed.id == membershipId else { + guard case let .relationshipDeleted(relationshipEvent) = event.change, relationshipEvent.removed.id == membershipId else { return } + let removed = relationshipEvent.removed + XCTAssertEqual(relationshipEvent.kind, .membership) XCTAssertEqual(removed.className, "Membership") XCTAssertEqual(removed.classVersion, self.membershipClassVersion) + deleteExpect.fulfill() } diff --git a/Tests/PubNubIntegrationTests/DataSyncRelationshipEndpointIntegrationTests.swift b/Tests/PubNubIntegrationTests/DataSyncRelationshipEndpointIntegrationTests.swift index 8566f873..c780050b 100644 --- a/Tests/PubNubIntegrationTests/DataSyncRelationshipEndpointIntegrationTests.swift +++ b/Tests/PubNubIntegrationTests/DataSyncRelationshipEndpointIntegrationTests.swift @@ -496,7 +496,7 @@ class DataSyncRelationshipEndpointIntegrationTests: XCTestCase { patchExpect.fulfill() case let .failure(error): XCTAssertNotNil(error.pubNubError) - XCTAssertEqual(error.pubNubError?.reason, .badRequest) + XCTAssertEqual(error.pubNubError?.reason, .conflict) client.dataSync.getRelationship(id: relationshipId) { fetchResult in switch fetchResult { diff --git a/Tests/PubNubIntegrationTests/DataSyncUserEventIntegrationTests.swift b/Tests/PubNubIntegrationTests/DataSyncUserEventIntegrationTests.swift index 2c29b001..b0b127ea 100644 --- a/Tests/PubNubIntegrationTests/DataSyncUserEventIntegrationTests.swift +++ b/Tests/PubNubIntegrationTests/DataSyncUserEventIntegrationTests.swift @@ -36,16 +36,19 @@ final class DataSyncUserEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityCreated(entity) = event, entity.id == userId else { + guard case let .entityCreated(entityEvent) = event.change, entityEvent.entity.id == userId else { return } + let entity = entityEvent.entity + XCTAssertEqual(entityEvent.kind, .user) XCTAssertEqual(entity.className, "User") XCTAssertEqual(entity.classLevel, .global) XCTAssertEqual(entity.classVersion, self.userClassVersion) XCTAssertEqual(entity.status, "active") XCTAssertFalse(entity.eTag.isEmpty) XCTAssertNotNil(entity.payload) + createExpect.fulfill() } @@ -99,15 +102,18 @@ final class DataSyncUserEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityUpdated(entity) = event, entity.id == userId else { + guard case let .entityUpdated(entityEvent) = event.change, entityEvent.entity.id == userId else { return } + let entity = entityEvent.entity + XCTAssertEqual(entityEvent.kind, .user) XCTAssertEqual(entity.className, "User") XCTAssertEqual(entity.classLevel, .global) XCTAssertEqual(entity.classVersion, self.userClassVersion) XCTAssertFalse(entity.eTag.isEmpty) XCTAssertNotNil(entity.payload) + updateExpect.fulfill() } @@ -159,13 +165,16 @@ final class DataSyncUserEventIntegrationTests: XCTestCase { .subscription() subscription.onDataSync = { event in - guard case let .entityDeleted(removed) = event, removed.id == userId else { + guard case let .entityDeleted(entityEvent) = event.change, entityEvent.removed.id == userId else { return } + let removed = entityEvent.removed + XCTAssertEqual(entityEvent.kind, .user) XCTAssertEqual(removed.className, "User") XCTAssertEqual(removed.classLevel, .global) XCTAssertEqual(removed.classVersion, self.userClassVersion) + deleteExpect.fulfill() } diff --git a/Tests/PubNubUnitTests/Networking/Routers/SubscribeRouterTests+DataSync.swift b/Tests/PubNubUnitTests/Networking/Routers/SubscribeRouterTests+DataSync.swift index 71843ec1..7474596d 100644 --- a/Tests/PubNubUnitTests/Networking/Routers/SubscribeRouterTests+DataSync.swift +++ b/Tests/PubNubUnitTests/Networking/Routers/SubscribeRouterTests+DataSync.swift @@ -103,7 +103,7 @@ extension SubscribeRouterTests { } func test_Subscribe_WithDataSyncEntityDeleteEvent_ReceivesRemovedEntity() throws { - let expectedRemoval = PubNubDataSyncRemovedObject( + let expectedRemoval = PubNubDataSyncRemovedEntity( id: "hcn-patient-dubois", className: "patient", classLevel: .subKey, @@ -195,6 +195,7 @@ extension SubscribeRouterTests { let expectedRemoval = PubNubDataSyncRemovedRelationship( id: "hcn-rel-attending-tanaka-dubois", className: "attending-physician", + classLevel: .subKey, classVersion: 1, deletedAt: try XCTUnwrap(DateFormatter.iso8601.date(from: "2026-07-28T09:07:47.243657Z")) ) @@ -213,7 +214,7 @@ extension SubscribeRouterTests { ] for (rawClassLevel, expectedLevel) in levels { - let event = mockDataSyncPayload(classLevel: rawClassLevel).asPubNubEvent() + let event = try XCTUnwrap(mockDataSyncPayload(classLevel: rawClassLevel).asPubNubEvent()) let entity = try XCTUnwrap(event.createdDataSyncEntity, "\(rawClassLevel) should decode") XCTAssertEqual(entity.classLevel, expectedLevel, "\(rawClassLevel) should decode as \(expectedLevel)") @@ -222,7 +223,7 @@ extension SubscribeRouterTests { } func test_Subscribe_WithUnrecognizedDataSyncClassLevel_ReceivesUnknownLevel() throws { - let event = mockDataSyncPayload(classLevel: "Space").asPubNubEvent() + let event = try XCTUnwrap(mockDataSyncPayload(classLevel: "Space").asPubNubEvent()) let entity = try XCTUnwrap(event.createdDataSyncEntity) XCTAssertEqual(entity.classLevel, .unknown("Space")) @@ -230,13 +231,13 @@ extension SubscribeRouterTests { } func test_Subscribe_WithDataSyncNullableFieldsAbsent_ReceivesEntity() throws { - let event = mockDataSyncPayload(data: [ + let event = try XCTUnwrap(mockDataSyncPayload(data: [ "id": "hcn-patient-alice", "createdAt": "2026-07-28T09:11:17.077390Z", "updatedAt": "2026-07-28T09:11:17.077390Z", "eTag": "3w5e111hppk83", "expiresAt": "2027-07-29T00:00:00Z" - ]).asPubNubEvent() + ]).asPubNubEvent()) let entity = try XCTUnwrap(event.createdDataSyncEntity) @@ -245,7 +246,7 @@ extension SubscribeRouterTests { } func test_Subscribe_WithDataSyncNullableFieldsNull_ReceivesEntity() throws { - let event = mockDataSyncPayload(data: [ + let event = try XCTUnwrap(mockDataSyncPayload(data: [ "id": "hcn-patient-alice", "createdAt": "2026-07-28T09:11:17.077390Z", "updatedAt": "2026-07-28T09:11:17.077390Z", @@ -253,7 +254,7 @@ extension SubscribeRouterTests { "expiresAt": "2027-07-29T00:00:00Z", "status": NSNull(), "payload": NSNull() - ]).asPubNubEvent() + ]).asPubNubEvent()) let entity = try XCTUnwrap(event.createdDataSyncEntity) @@ -261,7 +262,7 @@ extension SubscribeRouterTests { XCTAssertNil(entity.payload) } - func test_Subscribe_WithDataSyncMissingRequiredFields_ReceivesMessage() throws { + func test_Subscribe_WithDataSyncMissingRequiredFields_FallsBackToMessage() throws { for missingField in ["eTag"] { var data: [String: Any] = [ "id": "hcn-patient-alice", @@ -273,23 +274,117 @@ extension SubscribeRouterTests { data.removeValue(forKey: missingField) let payload = mockDataSyncPayload(data: data) - let event = payload.asPubNubEvent() + let event = try XCTUnwrap(payload.asPubNubEvent(), "An envelope missing \(missingField) should be reported") + let message = try XCTUnwrap(event.message, "An envelope missing \(missingField) should fall back to a message") - XCTAssertNotNil(event.message, "An envelope missing \(missingField) should degrade to a message") + XCTAssertEqual(message.messageType, .dataSync) + XCTAssertNil(event.dataSyncEvent) } } - func test_Subscribe_WithMalformedDataSyncEnvelope_ReceivesMessage() { - let payload = generateMessage(with: .dataSync, payload: MalformedDataSyncPayload().codableValue) - XCTAssertNotNil(payload.asPubNubEvent().message) + func test_Subscribe_WithMalformedDataSyncEnvelope_FallsBackToMessage() throws { + let payload = generateMessage( + with: .dataSync, + subscription: "patient-refs", + channel: "alice", + payload: MalformedDataSyncPayload().codableValue + ) + let message = try XCTUnwrap(payload.asPubNubEvent()?.message) + + XCTAssertEqual(message.messageType, .dataSync) + XCTAssertEqual(message.payload.codableValue, MalformedDataSyncPayload().codableValue) + XCTAssertEqual(message.channel, "alice") + XCTAssertEqual(message.subscription, "patient-refs") + XCTAssertEqual(message.published, 122412) + XCTAssertEqual(message.publisher, "publisher") + } + + func test_Subscribe_WithUnrecognizedDataSyncObjectType_ReportsUnknownChange() throws { + let event = try XCTUnwrap(mockDataSyncPayload(type: "organization").asPubNubEvent()) + let unknown = try XCTUnwrap(event.dataSyncEvent?.unknown) + + XCTAssertEqual(unknown.type, "organization") + XCTAssertEqual(unknown.event, "create") + XCTAssertEqual(unknown.className, "patient") + XCTAssertEqual(unknown.classLevel, .subKey) + XCTAssertEqual(unknown.classVersion, 1) } - func test_Subscribe_WithUnrecognizedDataSyncObjectType_ReceivesMessage() { - XCTAssertNotNil(mockDataSyncPayload(type: "organization").asPubNubEvent().message) + func test_Subscribe_WithUnrecognizedDataSyncAction_ReportsUnknownChange() throws { + let event = try XCTUnwrap(mockDataSyncPayload(event: "archive").asPubNubEvent()) + let unknown = try XCTUnwrap(event.dataSyncEvent?.unknown) + + XCTAssertEqual(unknown.type, "entity") + XCTAssertEqual(unknown.event, "archive") + } + + func test_Subscribe_WithUnknownDataSyncChange_PreservesRawEnvelope() throws { + let event = try XCTUnwrap(mockDataSyncPayload(type: "organization").asPubNubEvent()) + let unknown = try XCTUnwrap(event.dataSyncEvent?.unknown) + + XCTAssertEqual(unknown.metadata?.codableValue[rawValue: "type"] as? String, "organization") + XCTAssertEqual(unknown.payload?.codableValue[rawValue: "id"] as? String, "hcn-patient-alice") } - func test_DataSyncAction_MapsToUnknownMessageType() { - XCTAssertEqual(SubscribeMessagePayload.Action.dataSync.asPubNubMessageType, .unknown) + func test_Subscribe_WithNonDataSyncSource_FallsBackToMessage() throws { + let message = try XCTUnwrap(mockDataSyncPayload(source: "objects").asPubNubEvent()?.message) + XCTAssertEqual(message.messageType, .dataSync) + } + + func test_Subscribe_WithEmptyDataSyncDiscriminators_FallsBackToMessage() throws { + XCTAssertNotNil(try XCTUnwrap(mockDataSyncPayload(event: "").asPubNubEvent()).message) + XCTAssertNotNil(try XCTUnwrap(mockDataSyncPayload(type: "").asPubNubEvent()).message) + } + + func test_Subscribe_WithDataSyncEvent_ReportsChannelAndTimetoken() throws { + let payload = mockDataSyncPayload(channel: "__audit__alice") + let event = try XCTUnwrap(payload.asPubNubEvent()?.dataSyncEvent) + + XCTAssertEqual(event.channel, "__audit__alice") + XCTAssertEqual(event.timetoken, 122412) + XCTAssertNil(event.subscription) + XCTAssertNotNil(event.createdEntity) + } + + func test_Subscribe_WithChannelGroupSubscription_DeliversDataSyncEventWithSubscription() { + let pubnub = PubNub(configuration: config) + let subscription = pubnub.channelGroup("patient-refs").subscription() + let dataSyncExpectation = expectation(description: "Group-delivered DataSync event") + + subscription.onDataSync = { event in + XCTAssertEqual(event.channel, "alice") + XCTAssertEqual(event.subscription, "patient-refs") + dataSyncExpectation.fulfill() + } + + subscription.onPayloadsReceived(payloads: [ + mockDataSyncPayload(channel: "alice", subscription: "patient-refs") + ]) + + wait(for: [dataSyncExpectation], timeout: 0.1) + } + + func test_Subscribe_WithDataSyncEntityKinds_PreservesMetadataTypeForExtendedClasses() throws { + let kinds: [String: (className: String, expected: PubNubDataSyncEntityKind)] = [ + "entity": ("patient", .custom), + "user": ("MyUser", .user), + "channel": ("MyChannel", .channel) + ] + + for (type, kind) in kinds { + let event = try XCTUnwrap(mockDataSyncPayload(type: type, className: kind.className).asPubNubEvent()) + let entityEvent = try XCTUnwrap(event.createdDataSyncEntityEvent) + + XCTAssertEqual(entityEvent.kind, kind.expected) + XCTAssertEqual(entityEvent.entity.className, kind.className) + } + + XCTAssertEqual(PubNubDataSyncEntityKind.custom.rawValue, "custom") + XCTAssertEqual(PubNubDataSyncRelationshipKind.custom.rawValue, "custom") + } + + func test_DataSyncAction_MapsToDataSyncMessageType() { + XCTAssertEqual(SubscribeMessagePayload.Action.dataSync.asPubNubMessageType, .dataSync) } } @@ -351,7 +446,7 @@ extension SubscribeRouterTests { XCTAssertEqual( removed, - PubNubDataSyncRemovedObject( + PubNubDataSyncRemovedEntity( id: "alice", className: "User", classLevel: .global, @@ -402,7 +497,7 @@ extension SubscribeRouterTests { XCTAssertEqual( removed, - PubNubDataSyncRemovedObject( + PubNubDataSyncRemovedEntity( id: "general", className: "Channel", classLevel: .global, @@ -470,6 +565,7 @@ extension SubscribeRouterTests { PubNubDataSyncRemovedRelationship( id: "general__alice", className: "Membership", + classLevel: .global, classVersion: 1, deletedAt: try XCTUnwrap(DateFormatter.iso8601.date(from: "2026-08-13T16:05:15.029431Z")) ) @@ -480,6 +576,93 @@ extension SubscribeRouterTests { // MARK: - DataSync Delivery Through The Subscribe Loop extension SubscribeRouterTests { + func test_Subscribe_WithBrokenDataSyncEnvelope_DeliversItAsMessageToModernListener() { + let pubnub = PubNub(configuration: config) + let subscription = pubnub.channel("test-channel").subscription() + let dataSyncExpectation = expectation(description: "Valid DataSync event") + let messageExpectation = expectation(description: "Fallback message") + + subscription.onDataSync = { _ in + dataSyncExpectation.fulfill() + } + subscription.onMessage = { message in + XCTAssertEqual(message.messageType, .dataSync) + messageExpectation.fulfill() + } + + let events = subscription.onPayloadsReceived(payloads: [ + generateMessage(with: .dataSync, channel: "test-channel", payload: MalformedDataSyncPayload().codableValue), + mockDataSyncPayload(channel: "test-channel") + ]) + + XCTAssertEqual(events.count, 2) + wait(for: [dataSyncExpectation, messageExpectation], timeout: 0.1) + } + + func test_Subscribe_WithBrokenDataSyncEnvelope_DeliversItAsMessageToLegacyListener() { + let listener = SubscriptionListener(queue: .main) + let dataSyncExpectation = expectation(description: "Valid DataSync event") + let messageExpectation = expectation(description: "Fallback message") + + listener.didReceiveDataSyncEvent = { _ in + dataSyncExpectation.fulfill() + } + listener.didReceiveMessage = { message in + XCTAssertEqual(message.messageType, .dataSync) + messageExpectation.fulfill() + } + + listener.emit(batch: [ + generateMessage(with: .dataSync, payload: MalformedDataSyncPayload().codableValue), + mockDataSyncPayload() + ]) + + wait(for: [dataSyncExpectation, messageExpectation], timeout: 0.1) + } + + func test_Subscribe_WithUnknownDataSyncChange_DeliversItToModernListener() { + let pubnub = PubNub(configuration: config) + let subscription = pubnub.channel("test-channel").subscription() + let dataSyncExpectation = expectation(description: "Unknown DataSync change") + let messageExpectation = expectation(description: "No fallback message") + messageExpectation.isInverted = true + + subscription.onDataSync = { event in + if case .unknown = event.change { + dataSyncExpectation.fulfill() + } + } + subscription.onMessage = { _ in + messageExpectation.fulfill() + } + + subscription.onPayloadsReceived(payloads: [ + mockDataSyncPayload(channel: "test-channel", type: "organization") + ]) + + wait(for: [dataSyncExpectation, messageExpectation], timeout: 0.1) + } + + func test_Subscribe_WithUnknownDataSyncChange_DeliversItToLegacyListener() { + let listener = SubscriptionListener(queue: .main) + let dataSyncExpectation = expectation(description: "Unknown DataSync change") + let messageExpectation = expectation(description: "No fallback message") + messageExpectation.isInverted = true + + listener.didReceiveDataSyncEvent = { event in + if case .unknown = event.change { + dataSyncExpectation.fulfill() + } + } + listener.didReceiveMessage = { _ in + messageExpectation.fulfill() + } + + listener.emit(batch: [mockDataSyncPayload(event: "archive")]) + + wait(for: [dataSyncExpectation, messageExpectation], timeout: 0.1) + } + func test_Subscribe_WithDataSyncEntityCreateEvent_EmitsToListeners() throws { let events = try emittedDataSyncEvents( fromFixture: "subscription_dataSyncEntityCreate_success", @@ -746,66 +929,86 @@ private extension SubscribeRouterTests { private extension PubNubDataSyncEvent { var createdEntity: PubNubDataSyncEntity? { - guard case let .entityCreated(e) = self else { return nil } - return e + guard case let .entityCreated(entityEvent) = change else { return nil } + return entityEvent.entity } var updatedEntity: PubNubDataSyncEntity? { - guard case let .entityUpdated(e) = self else { return nil } - return e + guard case let .entityUpdated(entityEvent) = change else { return nil } + return entityEvent.entity } - var deletedEntity: PubNubDataSyncRemovedObject? { - guard case let .entityDeleted(e) = self else { return nil } - return e + var deletedEntity: PubNubDataSyncRemovedEntity? { + guard case let .entityDeleted(entityEvent) = change else { return nil } + return entityEvent.removed } var createdRelationship: PubNubDataSyncRelationship? { - guard case let .relationshipCreated(r) = self else { return nil } - return r + guard case let .relationshipCreated(relationshipEvent) = change else { return nil } + return relationshipEvent.relationship } var updatedRelationship: PubNubDataSyncRelationship? { - guard case let .relationshipUpdated(r) = self else { return nil } - return r + guard case let .relationshipUpdated(relationshipEvent) = change else { return nil } + return relationshipEvent.relationship } var deletedRelationship: PubNubDataSyncRemovedRelationship? { - guard case let .relationshipDeleted(r) = self else { return nil } - return r + guard case let .relationshipDeleted(relationshipEvent) = change else { return nil } + return relationshipEvent.removed + } + + var unknown: PubNubDataSyncUnknownEvent? { + guard case let .unknown(unknownEvent) = change else { return nil } + return unknownEvent } } // MARK: Helpers private extension PubNubEvent { + var dataSyncEvent: PubNubDataSyncEvent? { + guard case let .dataSyncChanged(dataSyncEvent) = self else { return nil } + return dataSyncEvent + } + var createdDataSyncEntity: PubNubDataSyncEntity? { - guard case let .dataSyncChanged(.entityCreated(e)) = self else { return nil } - return e + createdDataSyncEntityEvent?.entity } var updatedDataSyncEntity: PubNubDataSyncEntity? { - guard case let .dataSyncChanged(.entityUpdated(e)) = self else { return nil } - return e + guard case let .entityUpdated(entityEvent)? = dataSyncEvent?.change else { return nil } + return entityEvent.entity } - var deletedDataSyncEntity: PubNubDataSyncRemovedObject? { - guard case let .dataSyncChanged(.entityDeleted(e)) = self else { return nil } - return e + var deletedDataSyncEntity: PubNubDataSyncRemovedEntity? { + guard case let .entityDeleted(entityEvent)? = dataSyncEvent?.change else { return nil } + return entityEvent.removed } var createdDataSyncRelationship: PubNubDataSyncRelationship? { - guard case let .dataSyncChanged(.relationshipCreated(r)) = self else { return nil } - return r + createdDataSyncRelationshipEvent?.relationship } var updatedDataSyncRelationship: PubNubDataSyncRelationship? { - guard case let .dataSyncChanged(.relationshipUpdated(r)) = self else { return nil } - return r + guard case let .relationshipUpdated(relationshipEvent)? = dataSyncEvent?.change else { return nil } + return relationshipEvent.relationship } var deletedDataSyncRelationship: PubNubDataSyncRemovedRelationship? { - guard case let .dataSyncChanged(.relationshipDeleted(r)) = self else { return nil } - return r + guard case let .relationshipDeleted(relationshipEvent)? = dataSyncEvent?.change else { return nil } + return relationshipEvent.removed + } + + var createdDataSyncEntityEvent: PubNubDataSyncEntityEvent? { + guard case let .entityCreated(entityEvent)? = dataSyncEvent?.change else { return nil } + return entityEvent + } + + var createdDataSyncRelationshipEvent: PubNubDataSyncRelationshipEvent? { + guard case let .relationshipCreated(relationshipEvent)? = dataSyncEvent?.change else { return nil } + return relationshipEvent } } + +// swiftlint:disable:this file_length diff --git a/Tests/PubNubUnitTests/Networking/Routers/SubscribeRouterTests.swift b/Tests/PubNubUnitTests/Networking/Routers/SubscribeRouterTests.swift index 72131f58..9f8a232c 100644 --- a/Tests/PubNubUnitTests/Networking/Routers/SubscribeRouterTests.swift +++ b/Tests/PubNubUnitTests/Networking/Routers/SubscribeRouterTests.swift @@ -220,7 +220,7 @@ extension SubscribeRouterTests { func decodeEvent(from resource: String) throws -> PubNubEvent { let payload = try XCTUnwrap(decodeSubscribeResponse(from: resource).messages.first) - return payload.asPubNubEvent() + return try XCTUnwrap(payload.asPubNubEvent()) } } diff --git a/Tests/PubNubUnitTests/Subscribe/Legacy/SubscriptionStreamTests.swift b/Tests/PubNubUnitTests/Subscribe/Legacy/SubscriptionStreamTests.swift index 1a2e6f5a..cdd0eef0 100644 --- a/Tests/PubNubUnitTests/Subscribe/Legacy/SubscriptionStreamTests.swift +++ b/Tests/PubNubUnitTests/Subscribe/Legacy/SubscriptionStreamTests.swift @@ -110,7 +110,7 @@ class SubscriptionListenerTests: XCTestCase { let listener = SubscriptionListener() listener.didReceiveDataSyncEvent = { event in - if case .entityCreated = event { + if case .entityCreated = event.change { expectation.fulfill() } } diff --git a/Tests/PubNubUnitTests/Support/Factories/SubscribePayloadFactory.swift b/Tests/PubNubUnitTests/Support/Factories/SubscribePayloadFactory.swift index ebaf7a95..395e024f 100644 --- a/Tests/PubNubUnitTests/Support/Factories/SubscribePayloadFactory.swift +++ b/Tests/PubNubUnitTests/Support/Factories/SubscribePayloadFactory.swift @@ -113,7 +113,9 @@ func mockPresenceChangePayload( func mockDataSyncPayload( channel: String = "channel", + subscription: String? = nil, event: String = "create", + source: String = "data-sync", type: String = "entity", className: String = "patient", classLevel: String = "SubKey", @@ -129,12 +131,13 @@ func mockDataSyncPayload( ) -> SubscribeMessagePayload { generateMessage( with: .dataSync, + subscription: subscription, channel: channel, payload: AnyJSON([ "version": "1.0", "metadata": [ "event": event, - "source": "data-sync", + "source": source, "type": type, "className": className, "classLevel": classLevel,