Skip to content

Commit 59783b1

Browse files
Update the way that JSON map values are encoded on wire
This implements the change described in [1]. It has not yet been specified — have created #46 for adding specification point references later. (We need to implement it now because Realtime has removed support for the previous wire representation.) Resolves #44. [1] https://ably.atlassian.net/wiki/spaces/LOB/pages/4203380843/LODR-040+ObjectData+JSON+encoding+of+object+and+array+values
1 parent 3045a8a commit 59783b1

File tree

8 files changed

+122
-177
lines changed

8 files changed

+122
-177
lines changed

Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -692,16 +692,16 @@ internal final class InternalDefaultLiveMap: Sendable {
692692

693693
// RTLM5d2e: If ObjectsMapEntry.data.string exists, return it
694694
if let string = entry.data.string {
695-
switch string {
696-
case let .string(string):
697-
return .primitive(.string(string))
698-
case let .json(objectOrArray):
699-
switch objectOrArray {
700-
case let .array(array):
701-
return .primitive(.jsonArray(array))
702-
case let .object(object):
703-
return .primitive(.jsonObject(object))
704-
}
695+
return .primitive(.string(string))
696+
}
697+
698+
// TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
699+
if let json = entry.data.json {
700+
switch json {
701+
case let .array(array):
702+
return .primitive(.jsonArray(array))
703+
case let .object(object):
704+
return .primitive(.jsonObject(object))
705705
}
706706
}
707707

Sources/AblyLiveObjects/Protocol/ObjectMessage.swift

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,12 @@ internal struct ObjectOperation {
5656
}
5757

5858
internal struct ObjectData {
59-
/// The values that the `string` property might hold, before being encoded per OD4 or after being decoded per OD5.
60-
internal enum StringPropertyContent {
61-
case string(String)
62-
case json(JSONObjectOrArray)
63-
}
64-
6559
internal var objectId: String? // OD2a
66-
internal var encoding: String? // OD2b
6760
internal var boolean: Bool? // OD2c
6861
internal var bytes: Data? // OD2d
6962
internal var number: NSNumber? // OD2e
70-
internal var string: StringPropertyContent? // OD2f
63+
internal var string: String? // OD2f
64+
internal var json: JSONObjectOrArray? // TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
7165
}
7266

7367
internal struct ObjectsMapOp {
@@ -264,9 +258,9 @@ internal extension ObjectData {
264258
format: AblyPlugin.EncodingFormat
265259
) throws(InternalError) {
266260
objectId = wireObjectData.objectId
267-
encoding = wireObjectData.encoding
268261
boolean = wireObjectData.boolean
269262
number = wireObjectData.number
263+
string = wireObjectData.string
270264

271265
// OD5: Decode data based on format
272266
switch format {
@@ -300,16 +294,12 @@ internal extension ObjectData {
300294
}
301295
}
302296

303-
if let wireString = wireObjectData.string {
304-
// OD5a2, OD5b3: If ObjectData.encoding is set to "json", the ObjectData.string content is decoded by parsing the string as JSON
305-
if wireObjectData.encoding == "json" {
306-
let jsonValue = try JSONObjectOrArray(jsonString: wireString)
307-
string = .json(jsonValue)
308-
} else {
309-
string = .string(wireString)
310-
}
297+
// TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
298+
if let wireJson = wireObjectData.json {
299+
let jsonValue = try JSONObjectOrArray(jsonString: wireJson)
300+
json = jsonValue
311301
} else {
312-
string = nil
302+
json = nil
313303
}
314304
}
315305

@@ -334,20 +324,6 @@ internal extension ObjectData {
334324
nil
335325
}
336326

337-
// OD4c4: A string payload is encoded as a MessagePack string type, and the result is set on the ObjectData.string attribute
338-
// OD4d4: A string payload is represented as a JSON string and set on the ObjectData.string attribute
339-
let (wireString, wireEncoding): (String?, String?) = if let stringContent = string {
340-
switch stringContent {
341-
case let .string(str):
342-
(str, nil)
343-
case let .json(jsonValue):
344-
// OD4c5, OD4d5: A payload consisting of a JSON-encodable object or array is stringified as a JSON object or array, represented as a JSON string and the result is set on the ObjectData.string attribute. The ObjectData.encoding attribute is then set to "json"
345-
(jsonValue.toJSONString, "json")
346-
}
347-
} else {
348-
(nil, nil)
349-
}
350-
351327
let wireNumber: NSNumber? = if let number {
352328
switch format {
353329
case .json:
@@ -363,11 +339,14 @@ internal extension ObjectData {
363339

364340
return .init(
365341
objectId: objectId,
366-
encoding: wireEncoding,
367342
boolean: boolean,
368343
bytes: wireBytes,
369344
number: wireNumber,
370-
string: wireString,
345+
// OD4c4: A string payload is encoded as a MessagePack string type, and the result is set on the ObjectData.string attribute
346+
// OD4d4: A string payload is represented as a JSON string and set on the ObjectData.string attribute
347+
string: string,
348+
// TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
349+
json: json?.toJSONString,
371350
)
372351
}
373352
}

Sources/AblyLiveObjects/Protocol/WireObjectMessage.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -475,30 +475,30 @@ extension WireObjectsMapEntry: WireObjectCodable {
475475

476476
internal struct WireObjectData {
477477
internal var objectId: String? // OD2a
478-
internal var encoding: String? // OD2b
479478
internal var boolean: Bool? // OD2c
480479
internal var bytes: StringOrData? // OD2d
481480
internal var number: NSNumber? // OD2e
482481
internal var string: String? // OD2f
482+
internal var json: String? // TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
483483
}
484484

485485
extension WireObjectData: WireObjectCodable {
486486
internal enum WireKey: String {
487487
case objectId
488-
case encoding
489488
case boolean
490489
case bytes
491490
case number
492491
case string
492+
case json
493493
}
494494

495495
internal init(wireObject: [String: WireValue]) throws(InternalError) {
496496
objectId = try wireObject.optionalStringValueForKey(WireKey.objectId.rawValue)
497-
encoding = try wireObject.optionalStringValueForKey(WireKey.encoding.rawValue)
498497
boolean = try wireObject.optionalBoolValueForKey(WireKey.boolean.rawValue)
499498
bytes = try wireObject.optionalDecodableValueForKey(WireKey.bytes.rawValue)
500499
number = try wireObject.optionalNumberValueForKey(WireKey.number.rawValue)
501500
string = try wireObject.optionalStringValueForKey(WireKey.string.rawValue)
501+
json = try wireObject.optionalStringValueForKey(WireKey.json.rawValue)
502502
}
503503

504504
internal var toWireObject: [String: WireValue] {
@@ -507,9 +507,6 @@ extension WireObjectData: WireObjectCodable {
507507
if let objectId {
508508
result[WireKey.objectId.rawValue] = .string(objectId)
509509
}
510-
if let encoding {
511-
result[WireKey.encoding.rawValue] = .string(encoding)
512-
}
513510
if let boolean {
514511
result[WireKey.boolean.rawValue] = .bool(boolean)
515512
}
@@ -522,6 +519,9 @@ extension WireObjectData: WireObjectCodable {
522519
if let string {
523520
result[WireKey.string.rawValue] = .string(string)
524521
}
522+
if let json {
523+
result[WireKey.json.rawValue] = .string(json)
524+
}
525525

526526
return result
527527
}

Tests/AblyLiveObjectsTests/Helpers/TestFactories.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ struct TestFactories {
429429
entry: mapEntry(
430430
tombstone: tombstone,
431431
timeserial: timeserial,
432-
data: ObjectData(string: .string(value)),
432+
data: ObjectData(string: value),
433433
),
434434
)
435435
}
@@ -448,7 +448,7 @@ struct TestFactories {
448448
entry: internalMapEntry(
449449
tombstone: tombstone,
450450
timeserial: timeserial,
451-
data: ObjectData(string: .string(value)),
451+
data: ObjectData(string: value),
452452
),
453453
)
454454
}
@@ -539,7 +539,7 @@ struct TestFactories {
539539
entries: [String: String] = ["key1": "value1", "key2": "value2"],
540540
) -> ObjectsMap {
541541
let mapEntries = entries.mapValues { value in
542-
mapEntry(data: ObjectData(string: .string(value)))
542+
mapEntry(data: ObjectData(string: value))
543543
}
544544
return objectsMap(entries: mapEntries)
545545
}
@@ -567,7 +567,7 @@ struct TestFactories {
567567
objectId: objectId,
568568
mapOp: ObjectsMapOp(
569569
key: key,
570-
data: ObjectData(string: .string(value)),
570+
data: ObjectData(string: value),
571571
),
572572
),
573573
serial: serial,
@@ -676,7 +676,7 @@ struct TestFactories {
676676
entries: [String: String] = ["key1": "value1", "key2": "value2"],
677677
) -> InboundObjectMessage {
678678
let mapEntries = entries.mapValues { value in
679-
mapEntry(data: ObjectData(string: .string(value)))
679+
mapEntry(data: ObjectData(string: value))
680680
}
681681
return rootObjectMessage(entries: mapEntries)
682682
}

Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -81,33 +81,35 @@ struct InternalDefaultLiveMapTests {
8181
#expect(result?.numberValue == 123.456)
8282
}
8383

84-
// @specOneOf(1/3) RTLM5d2e - When `string` is a string
84+
// @spec RTLM5d2e
8585
@Test
8686
func returnsStringValue() throws {
8787
let logger = TestLogger()
88-
let entry = TestFactories.internalMapEntry(data: ObjectData(string: .string("test")))
88+
let entry = TestFactories.internalMapEntry(data: ObjectData(string: "test"))
8989
let coreSDK = MockCoreSDK(channelState: .attaching)
9090
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main, clock: MockSimpleClock())
9191
let result = try map.get(key: "key", coreSDK: coreSDK, delegate: MockLiveMapObjectPoolDelegate())
9292
#expect(result?.stringValue == "test")
9393
}
9494

95-
// @specOneOf(2/3) RTLM5d2e - When `string` is a JSON array
95+
// TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
96+
// Tests when `json` is a JSON array
9697
@Test
9798
func returnsJSONArrayValue() throws {
9899
let logger = TestLogger()
99-
let entry = TestFactories.internalMapEntry(data: ObjectData(string: .json(.array(["foo"]))))
100+
let entry = TestFactories.internalMapEntry(data: ObjectData(json: .array(["foo"])))
100101
let coreSDK = MockCoreSDK(channelState: .attaching)
101102
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main, clock: MockSimpleClock())
102103
let result = try map.get(key: "key", coreSDK: coreSDK, delegate: MockLiveMapObjectPoolDelegate())
103104
#expect(result?.jsonArrayValue == ["foo"])
104105
}
105106

106-
// @specOneOf(3/3) RTLM5d2e - When `string` is a JSON object
107+
// TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
108+
// Tests when `json` is a JSON object
107109
@Test
108110
func returnsJSONObjectValue() throws {
109111
let logger = TestLogger()
110-
let entry = TestFactories.internalMapEntry(data: ObjectData(string: .json(.object(["foo": "bar"]))))
112+
let entry = TestFactories.internalMapEntry(data: ObjectData(json: .object(["foo": "bar"])))
111113
let coreSDK = MockCoreSDK(channelState: .attaching)
112114
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main, clock: MockSimpleClock())
113115
let result = try map.get(key: "key", coreSDK: coreSDK, delegate: MockLiveMapObjectPoolDelegate())
@@ -316,11 +318,11 @@ struct InternalDefaultLiveMapTests {
316318
let map = InternalDefaultLiveMap(
317319
testsOnly_data: [
318320
// tombstone is nil, so not considered tombstoned
319-
"active1": TestFactories.internalMapEntry(data: ObjectData(string: .string("value1"))),
321+
"active1": TestFactories.internalMapEntry(data: ObjectData(string: "value1")),
320322
// tombstone is false, so not considered tombstoned[
321-
"active2": TestFactories.internalMapEntry(tombstone: false, data: ObjectData(string: .string("value2"))),
322-
"tombstoned": TestFactories.internalMapEntry(tombstone: true, data: ObjectData(string: .string("tombstoned"))),
323-
"tombstoned2": TestFactories.internalMapEntry(tombstone: true, data: ObjectData(string: .string("tombstoned2"))),
323+
"active2": TestFactories.internalMapEntry(tombstone: false, data: ObjectData(string: "value2")),
324+
"tombstoned": TestFactories.internalMapEntry(tombstone: true, data: ObjectData(string: "tombstoned")),
325+
"tombstoned2": TestFactories.internalMapEntry(tombstone: true, data: ObjectData(string: "tombstoned2")),
324326
],
325327
objectID: "arbitrary",
326328
logger: logger,
@@ -362,9 +364,9 @@ struct InternalDefaultLiveMapTests {
362364
let delegate = MockLiveMapObjectPoolDelegate()
363365
let map = InternalDefaultLiveMap(
364366
testsOnly_data: [
365-
"key1": TestFactories.internalMapEntry(data: ObjectData(string: .string("value1"))),
366-
"key2": TestFactories.internalMapEntry(data: ObjectData(string: .string("value2"))),
367-
"key3": TestFactories.internalMapEntry(data: ObjectData(string: .string("value3"))),
367+
"key1": TestFactories.internalMapEntry(data: ObjectData(string: "value1")),
368+
"key2": TestFactories.internalMapEntry(data: ObjectData(string: "value2")),
369+
"key3": TestFactories.internalMapEntry(data: ObjectData(string: "value3")),
368370
],
369371
objectID: "arbitrary",
370372
logger: logger,
@@ -410,9 +412,9 @@ struct InternalDefaultLiveMapTests {
410412
"boolean": TestFactories.internalMapEntry(data: ObjectData(boolean: true)), // RTLM5d2b
411413
"bytes": TestFactories.internalMapEntry(data: ObjectData(bytes: Data([0x01, 0x02, 0x03]))), // RTLM5d2c
412414
"number": TestFactories.internalMapEntry(data: ObjectData(number: NSNumber(value: 42))), // RTLM5d2d
413-
"string": TestFactories.internalMapEntry(data: ObjectData(string: .string("hello"))), // RTLM5d2e
414-
"jsonArray": TestFactories.internalMapEntry(data: ObjectData(string: .json(.array(["foo"])))), // RTLM5d2e
415-
"jsonObject": TestFactories.internalMapEntry(data: ObjectData(string: .json(.object(["foo": "bar"])))), // RTLM5d2e
415+
"string": TestFactories.internalMapEntry(data: ObjectData(string: "hello")), // RTLM5d2e
416+
"jsonArray": TestFactories.internalMapEntry(data: ObjectData(json: .array(["foo"]))), // TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
417+
"jsonObject": TestFactories.internalMapEntry(data: ObjectData(json: .object(["foo": "bar"]))), // TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
416418
"mapRef": TestFactories.internalMapEntry(data: ObjectData(objectId: "map:ref@123")), // RTLM5d2f2
417419
"counterRef": TestFactories.internalMapEntry(data: ObjectData(objectId: "counter:ref@456")), // RTLM5d2f2
418420
],
@@ -465,7 +467,7 @@ struct InternalDefaultLiveMapTests {
465467
let delegate = MockLiveMapObjectPoolDelegate()
466468
let coreSDK = MockCoreSDK(channelState: .attaching)
467469
let map = InternalDefaultLiveMap(
468-
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: "ts2", data: ObjectData(string: .string("existing")))],
470+
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: "ts2", data: ObjectData(string: "existing"))],
469471
objectID: "arbitrary",
470472
logger: logger,
471473
userCallbackQueue: .main,
@@ -505,7 +507,7 @@ struct InternalDefaultLiveMapTests {
505507
let delegate = MockLiveMapObjectPoolDelegate()
506508
let coreSDK = MockCoreSDK(channelState: .attaching)
507509
let map = InternalDefaultLiveMap(
508-
testsOnly_data: ["key1": TestFactories.internalMapEntry(tombstone: true, timeserial: "ts1", data: ObjectData(string: .string("existing")))],
510+
testsOnly_data: ["key1": TestFactories.internalMapEntry(tombstone: true, timeserial: "ts1", data: ObjectData(string: "existing"))],
509511
objectID: "arbitrary",
510512
logger: logger,
511513
userCallbackQueue: .main,
@@ -677,7 +679,7 @@ struct InternalDefaultLiveMapTests {
677679
let delegate = MockLiveMapObjectPoolDelegate()
678680
let coreSDK = MockCoreSDK(channelState: .attaching)
679681
let map = InternalDefaultLiveMap(
680-
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: "ts2", data: ObjectData(string: .string("existing")))],
682+
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: "ts2", data: ObjectData(string: "existing"))],
681683
objectID: "arbitrary",
682684
logger: logger,
683685
userCallbackQueue: .main,
@@ -703,7 +705,7 @@ struct InternalDefaultLiveMapTests {
703705
let delegate = MockLiveMapObjectPoolDelegate()
704706
let coreSDK = MockCoreSDK(channelState: .attaching)
705707
let map = InternalDefaultLiveMap(
706-
testsOnly_data: ["key1": TestFactories.internalMapEntry(tombstone: false, timeserial: "ts1", data: ObjectData(string: .string("existing")))],
708+
testsOnly_data: ["key1": TestFactories.internalMapEntry(tombstone: false, timeserial: "ts1", data: ObjectData(string: "existing"))],
707709
objectID: "arbitrary",
708710
logger: logger,
709711
userCallbackQueue: .main,
@@ -828,7 +830,7 @@ struct InternalDefaultLiveMapTests {
828830
let delegate = MockLiveMapObjectPoolDelegate()
829831
let coreSDK = MockCoreSDK(channelState: .attaching)
830832
let map = InternalDefaultLiveMap(
831-
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: entrySerial, data: ObjectData(string: .string("existing")))],
833+
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: entrySerial, data: ObjectData(string: "existing"))],
832834
objectID: "arbitrary",
833835
logger: logger,
834836
userCallbackQueue: .main,
@@ -839,7 +841,7 @@ struct InternalDefaultLiveMapTests {
839841
_ = map.testsOnly_applyMapSetOperation(
840842
key: "key1",
841843
operationTimeserial: operationSerial,
842-
operationData: ObjectData(string: .string("new")),
844+
operationData: ObjectData(string: "new"),
843845
objectsPool: &pool,
844846
)
845847

@@ -1049,7 +1051,7 @@ struct InternalDefaultLiveMapTests {
10491051

10501052
let operation = TestFactories.objectOperation(
10511053
action: .known(.mapSet),
1052-
mapOp: ObjectsMapOp(key: "key1", data: ObjectData(string: .string("new"))),
1054+
mapOp: ObjectsMapOp(key: "key1", data: ObjectData(string: "new")),
10531055
)
10541056

10551057
// Apply operation with serial "ts1" which is lexicographically less than existing "ts2" and thus will be applied per RTLO4a (this is a non-pathological case of RTOL4a, that spec point being fully tested elsewhere)
@@ -1136,7 +1138,7 @@ struct InternalDefaultLiveMapTests {
11361138

11371139
let operation = TestFactories.objectOperation(
11381140
action: .known(.mapSet),
1139-
mapOp: ObjectsMapOp(key: "key1", data: ObjectData(string: .string("new"))),
1141+
mapOp: ObjectsMapOp(key: "key1", data: ObjectData(string: "new")),
11401142
)
11411143

11421144
// Apply MAP_SET operation

0 commit comments

Comments
 (0)