Skip to content

Commit 6dadea8

Browse files
Merge pull request #18 from ably/12-encode-and-decode-ObjectMessage-data
[ECO-5390] Encode and decode binary and JSON data per spec
2 parents 0d5a314 + 2715aeb commit 6dadea8

21 files changed

Lines changed: 2402 additions & 418 deletions

.cursor/rules/specification.mdc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ alwaysApply: false
1010
- If you are given a task that requires knowledge of the Specification Document, you must consult the Specification Document before proceeding. You must never make a guess about the contents of the Specification Document.
1111
- If you are given a task that requires you to interpret the Specification Document, but the Specification Document is unclear, be sure to mention this.
1212
- The Specification Document is structured as a list of specification points, each with an identifier. An example identifier is "OD1". In the Specification Document, the start of specification point OD1 would be represented by the string @(OD1)@. These specification points are sometimes referred to as "specification items".
13+
- Some specification points have subpoints. For example REC2 has (amongst others) the subpoint RSC2a, which has subpoints REC2a1 and REC2a2.
14+
- The LiveObjects functionality is referred to the in the Specification simply as "Objects".

.cursor/rules/swift.mdc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,7 @@ When writing Swift:
77

88
- Be sure to satisfy SwiftLint's `explicit_acl` rule ("All declarations should specify Access Control Level keywords explicitly).
99
- When writing an `extension` of a type, favour placing the access level on the declaration of the extension rather than each of its individual members.
10-
- This does not apply when writing test code.
10+
- This does not apply when writing test code.
11+
- When writing initializer expressions, when the type that is being initialized can be inferred, favour using the implicit `.init(…)` form instead of explicitly writing the type name.
12+
- When writing enum value expressions, when the type that is being initialized can be inferred, favour using the implicit `.caseName` form instead of explicitly writing the type name.
13+
- When writing JSONValue or WireValue types, favour using the literal syntax enabled by their conformance to the `ExpressibleBy*Literal` protocols where possible.

Sources/AblyLiveObjects/DefaultLiveObjects.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ internal class DefaultLiveObjects: Objects {
88
private let pluginAPI: AblyPlugin.PluginAPIProtocol
99

1010
// These drive the testsOnly_* properties that expose the received ProtocolMessages to the test suite.
11-
private let receivedObjectProtocolMessages: AsyncStream<[InboundWireObjectMessage]>
12-
private let receivedObjectProtocolMessagesContinuation: AsyncStream<[InboundWireObjectMessage]>.Continuation
13-
private let receivedObjectSyncProtocolMessages: AsyncStream<[InboundWireObjectMessage]>
14-
private let receivedObjectSyncProtocolMessagesContinuation: AsyncStream<[InboundWireObjectMessage]>.Continuation
11+
private let receivedObjectProtocolMessages: AsyncStream<[InboundObjectMessage]>
12+
private let receivedObjectProtocolMessagesContinuation: AsyncStream<[InboundObjectMessage]>.Continuation
13+
private let receivedObjectSyncProtocolMessages: AsyncStream<[InboundObjectMessage]>
14+
private let receivedObjectSyncProtocolMessagesContinuation: AsyncStream<[InboundObjectMessage]>.Continuation
1515

1616
internal init(channel: ARTRealtimeChannel, logger: AblyPlugin.Logger, pluginAPI: AblyPlugin.PluginAPIProtocol) {
1717
self.channel = channel
@@ -62,26 +62,26 @@ internal class DefaultLiveObjects: Objects {
6262
testsOnly_onChannelAttachedHasObjects = hasObjects
6363
}
6464

65-
internal var testsOnly_receivedObjectProtocolMessages: AsyncStream<[InboundWireObjectMessage]> {
65+
internal var testsOnly_receivedObjectProtocolMessages: AsyncStream<[InboundObjectMessage]> {
6666
receivedObjectProtocolMessages
6767
}
6868

69-
internal func handleObjectProtocolMessage(wireObjectMessages: [InboundWireObjectMessage]) {
70-
receivedObjectProtocolMessagesContinuation.yield(wireObjectMessages)
69+
internal func handleObjectProtocolMessage(objectMessages: [InboundObjectMessage]) {
70+
receivedObjectProtocolMessagesContinuation.yield(objectMessages)
7171
}
7272

73-
internal var testsOnly_receivedObjectSyncProtocolMessages: AsyncStream<[InboundWireObjectMessage]> {
73+
internal var testsOnly_receivedObjectSyncProtocolMessages: AsyncStream<[InboundObjectMessage]> {
7474
receivedObjectSyncProtocolMessages
7575
}
7676

77-
internal func handleObjectSyncProtocolMessage(wireObjectMessages: [InboundWireObjectMessage], protocolMessageChannelSerial _: String) {
78-
receivedObjectSyncProtocolMessagesContinuation.yield(wireObjectMessages)
77+
internal func handleObjectSyncProtocolMessage(objectMessages: [InboundObjectMessage], protocolMessageChannelSerial _: String) {
78+
receivedObjectSyncProtocolMessagesContinuation.yield(objectMessages)
7979
}
8080

8181
// MARK: - Sending `OBJECT` ProtocolMessage
8282

8383
// This is currently exposed so that we can try calling it from the tests in the early days of the SDK to check that we can send an OBJECT ProtocolMessage. We'll probably make it private later on.
84-
internal func testsOnly_sendObject(objectMessages: [OutboundWireObjectMessage]) async throws(InternalError) {
84+
internal func testsOnly_sendObject(objectMessages: [OutboundObjectMessage]) async throws(InternalError) {
8585
guard let channel else {
8686
return
8787
}

Sources/AblyLiveObjects/Internal/DefaultInternalPlugin.swift

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,77 +46,90 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
4646
Self.objectsProperty(for: channel, pluginAPI: pluginAPI)
4747
}
4848

49-
/// A class that wraps a ``WireObjectMessage``.
49+
/// A class that wraps an object message.
5050
///
51-
/// We need this intermediate type because we want `WireObjectMessage` to be a struct — because it's nicer to work with internally — but a struct can't conform to the class-bound `AblyPlugin.WireObjectMessage` protocol.
52-
private final class WireObjectMessageBox<T>: AblyPlugin.ObjectMessageProtocol where T: Sendable {
53-
internal let wireObjectMessage: T
51+
/// We need this intermediate type because we want object messages to be structs — because they're nicer to work with internally — but a struct can't conform to the class-bound `AblyPlugin.ObjectMessageProtocol`.
52+
private final class ObjectMessageBox<T>: AblyPlugin.ObjectMessageProtocol where T: Sendable {
53+
internal let objectMessage: T
5454

55-
init(wireObjectMessage: T) {
56-
self.wireObjectMessage = wireObjectMessage
55+
init(objectMessage: T) {
56+
self.objectMessage = objectMessage
5757
}
5858
}
5959

60-
internal func decodeObjectMessage(_ serialized: [String: Any], context: DecodingContextProtocol, error errorPtr: AutoreleasingUnsafeMutablePointer<ARTErrorInfo?>?) -> (any ObjectMessageProtocol)? {
61-
let jsonObject = JSONValue.objectFromAblyPluginData(serialized)
60+
internal func decodeObjectMessage(
61+
_ serialized: [String: Any],
62+
context: DecodingContextProtocol,
63+
format: EncodingFormat,
64+
error errorPtr: AutoreleasingUnsafeMutablePointer<ARTErrorInfo?>?,
65+
) -> (any ObjectMessageProtocol)? {
66+
let wireObject = WireValue.objectFromAblyPluginData(serialized)
6267

6368
do {
6469
let wireObjectMessage = try InboundWireObjectMessage(
65-
jsonObject: jsonObject,
70+
wireObject: wireObject,
6671
decodingContext: context,
6772
)
68-
return WireObjectMessageBox(wireObjectMessage: wireObjectMessage)
73+
let objectMessage = try InboundObjectMessage(
74+
wireObjectMessage: wireObjectMessage,
75+
format: format,
76+
)
77+
return ObjectMessageBox(objectMessage: objectMessage)
6978
} catch {
7079
errorPtr?.pointee = error.toARTErrorInfo()
7180
return nil
7281
}
7382
}
7483

75-
internal func encodeObjectMessage(_ publicObjectMessage: any AblyPlugin.ObjectMessageProtocol) -> [String: Any] {
76-
guard let wireObjectMessageBox = publicObjectMessage as? WireObjectMessageBox<OutboundWireObjectMessage> else {
77-
preconditionFailure("Expected to receive the same WireObjectMessage type as we emit")
84+
internal func encodeObjectMessage(
85+
_ publicObjectMessage: any AblyPlugin.ObjectMessageProtocol,
86+
format: EncodingFormat,
87+
) -> [String: Any] {
88+
guard let outboundObjectMessageBox = publicObjectMessage as? ObjectMessageBox<OutboundObjectMessage> else {
89+
preconditionFailure("Expected to receive the same OutboundObjectMessage type as we emit")
7890
}
7991

80-
return wireObjectMessageBox.wireObjectMessage.toJSONObject.toAblyPluginDataDictionary
92+
let wireObjectMessage = outboundObjectMessageBox.objectMessage.toWire(format: format)
93+
return wireObjectMessage.toWireObject.toAblyPluginDataDictionary
8194
}
8295

8396
internal func onChannelAttached(_ channel: ARTRealtimeChannel, hasObjects: Bool) {
8497
objectsProperty(for: channel).onChannelAttached(hasObjects: hasObjects)
8598
}
8699

87100
internal func handleObjectProtocolMessage(withObjectMessages publicObjectMessages: [any AblyPlugin.ObjectMessageProtocol], channel: ARTRealtimeChannel) {
88-
guard let wireObjectMessageBoxes = publicObjectMessages as? [WireObjectMessageBox<InboundWireObjectMessage>] else {
89-
preconditionFailure("Expected to receive the same WireObjectMessage type as we emit")
101+
guard let inboundObjectMessageBoxes = publicObjectMessages as? [ObjectMessageBox<InboundObjectMessage>] else {
102+
preconditionFailure("Expected to receive the same InboundObjectMessage type as we emit")
90103
}
91104

92-
let wireObjectMessages = wireObjectMessageBoxes.map(\.wireObjectMessage)
105+
let objectMessages = inboundObjectMessageBoxes.map(\.objectMessage)
93106

94107
objectsProperty(for: channel).handleObjectProtocolMessage(
95-
wireObjectMessages: wireObjectMessages,
108+
objectMessages: objectMessages,
96109
)
97110
}
98111

99112
internal func handleObjectSyncProtocolMessage(withObjectMessages publicObjectMessages: [any AblyPlugin.ObjectMessageProtocol], protocolMessageChannelSerial: String, channel: ARTRealtimeChannel) {
100-
guard let objectMessageBoxes = publicObjectMessages as? [WireObjectMessageBox<InboundWireObjectMessage>] else {
101-
preconditionFailure("Expected to receive the same WireObjectMessage type as we emit")
113+
guard let inboundObjectMessageBoxes = publicObjectMessages as? [ObjectMessageBox<InboundObjectMessage>] else {
114+
preconditionFailure("Expected to receive the same InboundObjectMessage type as we emit")
102115
}
103116

104-
let wireObjectMessages = objectMessageBoxes.map(\.wireObjectMessage)
117+
let objectMessages = inboundObjectMessageBoxes.map(\.objectMessage)
105118

106119
objectsProperty(for: channel).handleObjectSyncProtocolMessage(
107-
wireObjectMessages: wireObjectMessages,
120+
objectMessages: objectMessages,
108121
protocolMessageChannelSerial: protocolMessageChannelSerial,
109122
)
110123
}
111124

112125
// MARK: - Sending `OBJECT` ProtocolMessage
113126

114127
internal static func sendObject(
115-
objectMessages: [OutboundWireObjectMessage],
128+
objectMessages: [OutboundObjectMessage],
116129
channel: ARTRealtimeChannel,
117130
pluginAPI: PluginAPIProtocol,
118131
) async throws(InternalError) {
119-
let objectMessageBoxes: [WireObjectMessageBox<OutboundWireObjectMessage>] = objectMessages.map { .init(wireObjectMessage: $0) }
132+
let objectMessageBoxes: [ObjectMessageBox<OutboundObjectMessage>] = objectMessages.map { .init(objectMessage: $0) }
120133

121134
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, InternalError>, _>) in
122135
pluginAPI.sendObject(

0 commit comments

Comments
 (0)