Skip to content

Commit fa89d36

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 TODO check no references to "encoding" TODO check no references to "weirdly worded" TODO update the TODOs Resolves #44. [1] https://ably.atlassian.net/wiki/spaces/LOB/pages/4203380843/LODR-040+ObjectData+JSON+encoding+of+object+and+array+values
1 parent c919b36 commit fa89d36

File tree

8 files changed

+120
-175
lines changed

8 files changed

+120
-175
lines changed

Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift

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

714714
// RTLM5d2e: If ObjectsMapEntry.data.string exists, return it
715715
if let string = entry.data.string {
716-
switch string {
717-
case let .string(string):
718-
return .primitive(.string(string))
719-
case let .json(objectOrArray):
720-
switch objectOrArray {
721-
case let .array(array):
722-
return .primitive(.jsonArray(array))
723-
case let .object(object):
724-
return .primitive(.jsonObject(object))
725-
}
716+
return .primitive(.string(string))
717+
}
718+
719+
// TODO: Needs specification
720+
if let json = entry.data.json {
721+
switch json {
722+
case let .array(array):
723+
return .primitive(.jsonArray(array))
724+
case let .object(object):
725+
return .primitive(.jsonObject(object))
726726
}
727727
}
728728

Sources/AblyLiveObjects/Protocol/ObjectMessage.swift

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

4545
internal struct ObjectData {
46-
/// The values that the `string` property might hold, before being encoded per OD4 or after being decoded per OD5.
47-
internal enum StringPropertyContent {
48-
case string(String)
49-
case json(JSONObjectOrArray)
50-
}
51-
5246
internal var objectId: String? // OD2a
53-
internal var encoding: String? // OD2b
5447
internal var boolean: Bool? // OD2c
5548
internal var bytes: Data? // OD2d
5649
internal var number: NSNumber? // OD2e
57-
internal var string: StringPropertyContent? // OD2f
50+
internal var string: String? // OD2f
51+
internal var json: JSONObjectOrArray? // TODO: Needs specification
5852
}
5953

6054
internal struct ObjectsMapOp {
@@ -187,9 +181,9 @@ internal extension ObjectData {
187181
format: AblyPlugin.EncodingFormat
188182
) throws(InternalError) {
189183
objectId = wireObjectData.objectId
190-
encoding = wireObjectData.encoding
191184
boolean = wireObjectData.boolean
192185
number = wireObjectData.number
186+
string = wireObjectData.string
193187

194188
// OD5: Decode data based on format
195189
switch format {
@@ -223,16 +217,12 @@ internal extension ObjectData {
223217
}
224218
}
225219

226-
if let wireString = wireObjectData.string {
227-
// OD5a2, OD5b3: If ObjectData.encoding is set to "json", the ObjectData.string content is decoded by parsing the string as JSON
228-
if wireObjectData.encoding == "json" {
229-
let jsonValue = try JSONObjectOrArray(jsonString: wireString)
230-
string = .json(jsonValue)
231-
} else {
232-
string = .string(wireString)
233-
}
220+
// TODO: Needs specification
221+
if let wireJson = wireObjectData.json {
222+
let jsonValue = try JSONObjectOrArray(jsonString: wireJson)
223+
json = jsonValue
234224
} else {
235-
string = nil
225+
json = nil
236226
}
237227
}
238228

@@ -257,20 +247,6 @@ internal extension ObjectData {
257247
nil
258248
}
259249

260-
// OD4c4: A string payload is encoded as a MessagePack string type, and the result is set on the ObjectData.string attribute
261-
// OD4d4: A string payload is represented as a JSON string and set on the ObjectData.string attribute
262-
let (wireString, wireEncoding): (String?, String?) = if let stringContent = string {
263-
switch stringContent {
264-
case let .string(str):
265-
(str, nil)
266-
case let .json(jsonValue):
267-
// 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"
268-
(jsonValue.toJSONString, "json")
269-
}
270-
} else {
271-
(nil, nil)
272-
}
273-
274250
let wireNumber: NSNumber? = if let number {
275251
switch format {
276252
case .json:
@@ -286,11 +262,14 @@ internal extension ObjectData {
286262

287263
return .init(
288264
objectId: objectId,
289-
encoding: wireEncoding,
290265
boolean: boolean,
291266
bytes: wireBytes,
292267
number: wireNumber,
293-
string: wireString,
268+
// OD4c4: A string payload is encoded as a MessagePack string type, and the result is set on the ObjectData.string attribute
269+
// OD4d4: A string payload is represented as a JSON string and set on the ObjectData.string attribute
270+
string: string,
271+
// TODO: Needs specification
272+
json: json?.toJSONString,
294273
)
295274
}
296275
}

Sources/AblyLiveObjects/Protocol/WireObjectMessage.swift

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

426426
internal struct WireObjectData {
427427
internal var objectId: String? // OD2a
428-
internal var encoding: String? // OD2b
429428
internal var boolean: Bool? // OD2c
430429
internal var bytes: StringOrData? // OD2d
431430
internal var number: NSNumber? // OD2e
432431
internal var string: String? // OD2f
432+
internal var json: String? // TODO: Needs specification
433433
}
434434

435435
extension WireObjectData: WireObjectCodable {
436436
internal enum WireKey: String {
437437
case objectId
438-
case encoding
439438
case boolean
440439
case bytes
441440
case number
442441
case string
442+
case json
443443
}
444444

445445
internal init(wireObject: [String: WireValue]) throws(InternalError) {
446446
objectId = try wireObject.optionalStringValueForKey(WireKey.objectId.rawValue)
447-
encoding = try wireObject.optionalStringValueForKey(WireKey.encoding.rawValue)
448447
boolean = try wireObject.optionalBoolValueForKey(WireKey.boolean.rawValue)
449448
bytes = try wireObject.optionalDecodableValueForKey(WireKey.bytes.rawValue)
450449
number = try wireObject.optionalNumberValueForKey(WireKey.number.rawValue)
451450
string = try wireObject.optionalStringValueForKey(WireKey.string.rawValue)
451+
json = try wireObject.optionalStringValueForKey(WireKey.json.rawValue)
452452
}
453453

454454
internal var toWireObject: [String: WireValue] {
@@ -457,9 +457,6 @@ extension WireObjectData: WireObjectCodable {
457457
if let objectId {
458458
result[WireKey.objectId.rawValue] = .string(objectId)
459459
}
460-
if let encoding {
461-
result[WireKey.encoding.rawValue] = .string(encoding)
462-
}
463460
if let boolean {
464461
result[WireKey.boolean.rawValue] = .bool(boolean)
465462
}
@@ -472,6 +469,9 @@ extension WireObjectData: WireObjectCodable {
472469
if let string {
473470
result[WireKey.string.rawValue] = .string(string)
474471
}
472+
if let json {
473+
result[WireKey.json.rawValue] = .string(json)
474+
}
475475

476476
return result
477477
}

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
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
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
417+
"jsonObject": TestFactories.internalMapEntry(data: ObjectData(json: .object(["foo": "bar"]))), // TODO: Needs specification
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)