Skip to content

Commit 36d76a2

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 5fc3ed3 commit 36d76a2

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
@@ -141,31 +154,95 @@ internal extension ObjectOperation {
141154
wireObjectOperation: WireObjectOperation,
142155
format: AblyPlugin.EncodingFormat
143156
) throws(InternalError) {
144-
action = wireObjectOperation.action
157+
// Decode the objectId first since it's not part of PartialObjectOperation
145158
objectId = wireObjectOperation.objectId
146-
mapOp = try wireObjectOperation.mapOp.map { wireObjectsMapOp throws(InternalError) in
159+
160+
// Delegate to PartialObjectOperation for decoding
161+
let partialOperation = try PartialObjectOperation(
162+
partialWireObjectOperation: PartialWireObjectOperation(
163+
action: wireObjectOperation.action,
164+
mapOp: wireObjectOperation.mapOp,
165+
counterOp: wireObjectOperation.counterOp,
166+
map: wireObjectOperation.map,
167+
counter: wireObjectOperation.counter,
168+
nonce: wireObjectOperation.nonce,
169+
initialValue: wireObjectOperation.initialValue,
170+
),
171+
format: format,
172+
)
173+
174+
// Copy the decoded values
175+
action = partialOperation.action
176+
mapOp = partialOperation.mapOp
177+
counterOp = partialOperation.counterOp
178+
map = partialOperation.map
179+
counter = partialOperation.counter
180+
nonce = partialOperation.nonce
181+
initialValue = partialOperation.initialValue
182+
}
183+
184+
/// Converts this `ObjectOperation` to a `WireObjectOperation`, applying the data encoding rules of OD4.
185+
///
186+
/// - Parameters:
187+
/// - format: The format to use when applying the encoding rules of OD4.
188+
func toWire(format: AblyPlugin.EncodingFormat) -> WireObjectOperation {
189+
let partialWireOperation = PartialObjectOperation(
190+
action: action,
191+
mapOp: mapOp,
192+
counterOp: counterOp,
193+
map: map,
194+
counter: counter,
195+
nonce: nonce,
196+
initialValue: initialValue,
197+
).toWire(format: format)
198+
199+
// Create WireObjectOperation from PartialWireObjectOperation and add objectId
200+
return WireObjectOperation(
201+
action: partialWireOperation.action,
202+
objectId: objectId,
203+
mapOp: partialWireOperation.mapOp,
204+
counterOp: partialWireOperation.counterOp,
205+
map: partialWireOperation.map,
206+
counter: partialWireOperation.counter,
207+
nonce: partialWireOperation.nonce,
208+
initialValue: partialWireOperation.initialValue,
209+
)
210+
}
211+
}
212+
213+
internal extension PartialObjectOperation {
214+
/// Initializes a `PartialObjectOperation` from a `PartialWireObjectOperation`, applying the data decoding rules of OD5.
215+
///
216+
/// - Parameters:
217+
/// - format: The format to use when applying the decoding rules of OD5.
218+
/// - Throws: `InternalError` if JSON or Base64 decoding fails.
219+
init(
220+
partialWireObjectOperation: PartialWireObjectOperation,
221+
format: AblyPlugin.EncodingFormat
222+
) throws(InternalError) {
223+
action = partialWireObjectOperation.action
224+
mapOp = try partialWireObjectOperation.mapOp.map { wireObjectsMapOp throws(InternalError) in
147225
try .init(wireObjectsMapOp: wireObjectsMapOp, format: format)
148226
}
149-
counterOp = wireObjectOperation.counterOp
150-
map = try wireObjectOperation.map.map { wireMap throws(InternalError) in
227+
counterOp = partialWireObjectOperation.counterOp
228+
map = try partialWireObjectOperation.map.map { wireMap throws(InternalError) in
151229
try .init(wireObjectsMap: wireMap, format: format)
152230
}
153-
counter = wireObjectOperation.counter
231+
counter = partialWireObjectOperation.counter
154232

155233
// Do not access on inbound data, per OOP3g
156234
nonce = nil
157235
// Do not access on inbound data, per OOP3h
158236
initialValue = nil
159237
}
160238

161-
/// Converts this `ObjectOperation` to a `WireObjectOperation`, applying the data encoding rules of OD4.
239+
/// Converts this `PartialObjectOperation` to a `PartialWireObjectOperation`, applying the data encoding rules of OD4.
162240
///
163241
/// - Parameters:
164242
/// - format: The format to use when applying the encoding rules of OD4.
165-
func toWire(format: AblyPlugin.EncodingFormat) -> WireObjectOperation {
243+
func toWire(format: AblyPlugin.EncodingFormat) -> PartialWireObjectOperation {
166244
.init(
167245
action: action,
168-
objectId: objectId,
169246
mapOp: mapOp?.toWire(format: format),
170247
counterOp: counterOp,
171248
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)