Skip to content

Commit c2e7005

Browse files
Create a PartialObjectOperation type
We need a type that represents (when considering [1] at cb11ba8) RTO13's "partial ObjectOperation"; namely an ObjectOperation without an objectId property. This is something that is easy to represent in TypeScript but a bit of a faff in Swift; we have to create a new type for it. Code largely generated by Cursor at my instruction. [1] ably/specification#353
1 parent 782a614 commit c2e7005

2 files changed

Lines changed: 141 additions & 14 deletions

File tree

Sources/AblyLiveObjects/Protocol/ObjectMessage.swift

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ internal struct OutboundObjectMessage {
3131
internal var serialTimestamp: Date? // OM2j
3232
}
3333

34+
/// A partial version of `ObjectOperation` that excludes the `objectId` property. Used for encoding initial values where the `objectId` is not yet known.
35+
///
36+
/// `ObjectOperation` delegates its encoding and decoding to `PartialObjectOperation`.
37+
internal struct PartialObjectOperation {
38+
internal var action: WireEnum<ObjectOperationAction> // OOP3a
39+
internal var mapOp: ObjectsMapOp? // OOP3c
40+
internal var counterOp: WireObjectsCounterOp? // OOP3d
41+
internal var map: ObjectsMap? // OOP3e
42+
internal var counter: WireObjectsCounter? // OOP3f
43+
internal var nonce: String? // OOP3g
44+
internal var initialValue: String? // OOP3h
45+
}
46+
3447
internal struct ObjectOperation {
3548
internal var action: WireEnum<ObjectOperationAction> // OOP3a
3649
internal var objectId: String // OOP3b
@@ -135,31 +148,95 @@ internal extension ObjectOperation {
135148
wireObjectOperation: WireObjectOperation,
136149
format: AblyPlugin.EncodingFormat
137150
) throws(InternalError) {
138-
action = wireObjectOperation.action
151+
// Decode the objectId first since it's not part of PartialObjectOperation
139152
objectId = wireObjectOperation.objectId
140-
mapOp = try wireObjectOperation.mapOp.map { wireObjectsMapOp throws(InternalError) in
153+
154+
// Delegate to PartialObjectOperation for decoding
155+
let partialOperation = try PartialObjectOperation(
156+
partialWireObjectOperation: PartialWireObjectOperation(
157+
action: wireObjectOperation.action,
158+
mapOp: wireObjectOperation.mapOp,
159+
counterOp: wireObjectOperation.counterOp,
160+
map: wireObjectOperation.map,
161+
counter: wireObjectOperation.counter,
162+
nonce: wireObjectOperation.nonce,
163+
initialValue: wireObjectOperation.initialValue,
164+
),
165+
format: format,
166+
)
167+
168+
// Copy the decoded values
169+
action = partialOperation.action
170+
mapOp = partialOperation.mapOp
171+
counterOp = partialOperation.counterOp
172+
map = partialOperation.map
173+
counter = partialOperation.counter
174+
nonce = partialOperation.nonce
175+
initialValue = partialOperation.initialValue
176+
}
177+
178+
/// Converts this `ObjectOperation` to a `WireObjectOperation`, applying the data encoding rules of OD4.
179+
///
180+
/// - Parameters:
181+
/// - format: The format to use when applying the encoding rules of OD4.
182+
func toWire(format: AblyPlugin.EncodingFormat) -> WireObjectOperation {
183+
let partialWireOperation = PartialObjectOperation(
184+
action: action,
185+
mapOp: mapOp,
186+
counterOp: counterOp,
187+
map: map,
188+
counter: counter,
189+
nonce: nonce,
190+
initialValue: initialValue,
191+
).toWire(format: format)
192+
193+
// Create WireObjectOperation from PartialWireObjectOperation and add objectId
194+
return WireObjectOperation(
195+
action: partialWireOperation.action,
196+
objectId: objectId,
197+
mapOp: partialWireOperation.mapOp,
198+
counterOp: partialWireOperation.counterOp,
199+
map: partialWireOperation.map,
200+
counter: partialWireOperation.counter,
201+
nonce: partialWireOperation.nonce,
202+
initialValue: partialWireOperation.initialValue,
203+
)
204+
}
205+
}
206+
207+
internal extension PartialObjectOperation {
208+
/// Initializes a `PartialObjectOperation` from a `PartialWireObjectOperation`, applying the data decoding rules of OD5.
209+
///
210+
/// - Parameters:
211+
/// - format: The format to use when applying the decoding rules of OD5.
212+
/// - Throws: `InternalError` if JSON or Base64 decoding fails.
213+
init(
214+
partialWireObjectOperation: PartialWireObjectOperation,
215+
format: AblyPlugin.EncodingFormat
216+
) throws(InternalError) {
217+
action = partialWireObjectOperation.action
218+
mapOp = try partialWireObjectOperation.mapOp.map { wireObjectsMapOp throws(InternalError) in
141219
try .init(wireObjectsMapOp: wireObjectsMapOp, format: format)
142220
}
143-
counterOp = wireObjectOperation.counterOp
144-
map = try wireObjectOperation.map.map { wireMap throws(InternalError) in
221+
counterOp = partialWireObjectOperation.counterOp
222+
map = try partialWireObjectOperation.map.map { wireMap throws(InternalError) in
145223
try .init(wireObjectsMap: wireMap, format: format)
146224
}
147-
counter = wireObjectOperation.counter
225+
counter = partialWireObjectOperation.counter
148226

149227
// Do not access on inbound data, per OOP3g
150228
nonce = nil
151229
// Do not access on inbound data, per OOP3h
152230
initialValue = nil
153231
}
154232

155-
/// Converts this `ObjectOperation` to a `WireObjectOperation`, applying the data encoding rules of OD4.
233+
/// Converts this `PartialObjectOperation` to a `PartialWireObjectOperation`, applying the data encoding rules of OD4.
156234
///
157235
/// - Parameters:
158236
/// - format: The format to use when applying the encoding rules of OD4.
159-
func toWire(format: AblyPlugin.EncodingFormat) -> WireObjectOperation {
237+
func toWire(format: AblyPlugin.EncodingFormat) -> PartialWireObjectOperation {
160238
.init(
161239
action: action,
162-
objectId: objectId,
163240
mapOp: mapOp?.toWire(format: format),
164241
counterOp: counterOp,
165242
map: map?.toWire(format: format),

Sources/AblyLiveObjects/Protocol/WireObjectMessage.swift

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,11 @@ internal enum ObjectsMapSemantics: Int {
157157
case lww = 0
158158
}
159159

160-
internal struct WireObjectOperation {
160+
/// A partial version of `WireObjectOperation` that excludes the `objectId` property. Used for encoding initial values where the `objectId` is not yet known.
161+
///
162+
/// `WireObjectOperation` delegates its encoding and decoding to `PartialWireObjectOperation`.
163+
internal struct PartialWireObjectOperation {
161164
internal var action: WireEnum<ObjectOperationAction> // OOP3a
162-
internal var objectId: String // OOP3b
163165
internal var mapOp: WireObjectsMapOp? // OOP3c
164166
internal var counterOp: WireObjectsCounterOp? // OOP3d
165167
internal var map: WireObjectsMap? // OOP3e
@@ -168,10 +170,9 @@ internal struct WireObjectOperation {
168170
internal var initialValue: String? // OOP3h
169171
}
170172

171-
extension WireObjectOperation: WireObjectCodable {
173+
extension PartialWireObjectOperation: WireObjectCodable {
172174
internal enum WireKey: String {
173175
case action
174-
case objectId
175176
case mapOp
176177
case counterOp
177178
case map
@@ -182,7 +183,6 @@ extension WireObjectOperation: WireObjectCodable {
182183

183184
internal init(wireObject: [String: WireValue]) throws(InternalError) {
184185
action = try wireObject.wireEnumValueForKey(WireKey.action.rawValue)
185-
objectId = try wireObject.stringValueForKey(WireKey.objectId.rawValue)
186186
mapOp = try wireObject.optionalDecodableValueForKey(WireKey.mapOp.rawValue)
187187
counterOp = try wireObject.optionalDecodableValueForKey(WireKey.counterOp.rawValue)
188188
map = try wireObject.optionalDecodableValueForKey(WireKey.map.rawValue)
@@ -197,7 +197,6 @@ extension WireObjectOperation: WireObjectCodable {
197197
internal var toWireObject: [String: WireValue] {
198198
var result: [String: WireValue] = [
199199
WireKey.action.rawValue: .number(action.rawValue as NSNumber),
200-
WireKey.objectId.rawValue: .string(objectId),
201200
]
202201

203202
if let mapOp {
@@ -223,6 +222,57 @@ extension WireObjectOperation: WireObjectCodable {
223222
}
224223
}
225224

225+
internal struct WireObjectOperation {
226+
internal var action: WireEnum<ObjectOperationAction> // OOP3a
227+
internal var objectId: String // OOP3b
228+
internal var mapOp: WireObjectsMapOp? // OOP3c
229+
internal var counterOp: WireObjectsCounterOp? // OOP3d
230+
internal var map: WireObjectsMap? // OOP3e
231+
internal var counter: WireObjectsCounter? // OOP3f
232+
internal var nonce: String? // OOP3g
233+
internal var initialValue: String? // OOP3h
234+
}
235+
236+
extension WireObjectOperation: WireObjectCodable {
237+
internal enum WireKey: String {
238+
case objectId
239+
}
240+
241+
internal init(wireObject: [String: WireValue]) throws(InternalError) {
242+
// Decode the objectId first since it's not part of PartialWireObjectOperation
243+
objectId = try wireObject.stringValueForKey(WireKey.objectId.rawValue)
244+
245+
// Delegate to PartialWireObjectOperation for decoding
246+
let partialOperation = try PartialWireObjectOperation(wireObject: wireObject)
247+
248+
// Copy the decoded values
249+
action = partialOperation.action
250+
mapOp = partialOperation.mapOp
251+
counterOp = partialOperation.counterOp
252+
map = partialOperation.map
253+
counter = partialOperation.counter
254+
nonce = partialOperation.nonce
255+
initialValue = partialOperation.initialValue
256+
}
257+
258+
internal var toWireObject: [String: WireValue] {
259+
var result = PartialWireObjectOperation(
260+
action: action,
261+
mapOp: mapOp,
262+
counterOp: counterOp,
263+
map: map,
264+
counter: counter,
265+
nonce: nonce,
266+
initialValue: initialValue,
267+
).toWireObject
268+
269+
// Add the objectId field
270+
result[WireKey.objectId.rawValue] = .string(objectId)
271+
272+
return result
273+
}
274+
}
275+
226276
internal struct WireObjectState {
227277
internal var objectId: String // OST2a
228278
internal var siteTimeserials: [String: String] // OST2b

0 commit comments

Comments
 (0)