Skip to content

Commit 51e3f9f

Browse files
Switch to a new internal type for LiveMap data values
This is preparation for adding additional fields (e.g. tombstonedAt) per RTLM3a in [1]. [1] ably/specification#350
1 parent 392fae3 commit 51e3f9f

4 files changed

Lines changed: 87 additions & 39 deletions

File tree

Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal final class InternalDefaultLiveMap: Sendable {
1414

1515
private nonisolated(unsafe) var mutableState: MutableState
1616

17-
internal var testsOnly_data: [String: ObjectsMapEntry] {
17+
internal var testsOnly_data: [String: InternalObjectsMapEntry] {
1818
mutex.withLock {
1919
mutableState.data
2020
}
@@ -50,7 +50,7 @@ internal final class InternalDefaultLiveMap: Sendable {
5050
// MARK: - Initialization
5151

5252
internal convenience init(
53-
testsOnly_data data: [String: ObjectsMapEntry],
53+
testsOnly_data data: [String: InternalObjectsMapEntry],
5454
objectID: String,
5555
testsOnly_semantics semantics: WireEnum<ObjectsMapSemantics>? = nil,
5656
logger: AblyPlugin.Logger,
@@ -66,7 +66,7 @@ internal final class InternalDefaultLiveMap: Sendable {
6666
}
6767

6868
private init(
69-
data: [String: ObjectsMapEntry],
69+
data: [String: InternalObjectsMapEntry],
7070
objectID: String,
7171
semantics: WireEnum<ObjectsMapSemantics>?,
7272
logger: AblyPlugin.Logger,
@@ -339,7 +339,7 @@ internal final class InternalDefaultLiveMap: Sendable {
339339
internal var liveObject: LiveObjectMutableState<DefaultLiveMapUpdate>
340340

341341
/// The internal data that this map holds, per RTLM3.
342-
internal var data: [String: ObjectsMapEntry]
342+
internal var data: [String: InternalObjectsMapEntry]
343343

344344
/// The "private `semantics` field" of RTO5c1b1b.
345345
internal var semantics: WireEnum<ObjectsMapSemantics>?
@@ -361,7 +361,7 @@ internal final class InternalDefaultLiveMap: Sendable {
361361
liveObject.createOperationIsMerged = false
362362

363363
// RTLM6c: Set data to ObjectState.map.entries, or to an empty map if it does not exist
364-
data = state.map?.entries ?? [:]
364+
data = state.map?.entries?.mapValues { .init(objectsMapEntry: $0) } ?? [:]
365365

366366
// RTLM6d: If ObjectState.createOp is present, merge the initial value into the LiveMap as described in RTLM17
367367
return if let createOp = state.createOp {
@@ -527,7 +527,7 @@ internal final class InternalDefaultLiveMap: Sendable {
527527
// RTLM7b: If an entry does not exist in the private data for the specified key
528528
// RTLM7b1: Create a new entry in data for the specified key with the provided ObjectData and the operation's serial
529529
// RTLM7b2: Set ObjectsMapEntry.tombstone for the new entry to false
530-
data[key] = ObjectsMapEntry(tombstone: false, timeserial: operationTimeserial, data: operationData)
530+
data[key] = InternalObjectsMapEntry(tombstone: false, timeserial: operationTimeserial, data: operationData)
531531
}
532532

533533
// RTLM7c: If the operation has a non-empty ObjectData.objectId attribute
@@ -563,7 +563,7 @@ internal final class InternalDefaultLiveMap: Sendable {
563563
// RTLM8b: If an entry does not exist in the private data for the specified key
564564
// RTLM8b1: Create a new entry in data for the specified key, with ObjectsMapEntry.data set to undefined/null and the operation's serial
565565
// RTLM8b2: Set ObjectsMapEntry.tombstone for the new entry to true
566-
data[key] = ObjectsMapEntry(tombstone: true, timeserial: operationTimeserial, data: ObjectData())
566+
data[key] = InternalObjectsMapEntry(tombstone: true, timeserial: operationTimeserial, data: ObjectData())
567567
}
568568

569569
return .update(DefaultLiveMapUpdate(update: [key: .removed]))
@@ -647,9 +647,9 @@ internal final class InternalDefaultLiveMap: Sendable {
647647

648648
// MARK: - Helper Methods
649649

650-
/// Converts an ObjectsMapEntry to LiveMapValue using the same logic as get(key:)
650+
/// Converts an InternalObjectsMapEntry to LiveMapValue using the same logic as get(key:)
651651
/// This is used by entries to ensure consistent value conversion
652-
private func convertEntryToLiveMapValue(_ entry: ObjectsMapEntry, delegate: LiveMapObjectPoolDelegate) -> InternalLiveMapValue? {
652+
private func convertEntryToLiveMapValue(_ entry: InternalObjectsMapEntry, delegate: LiveMapObjectPoolDelegate) -> InternalLiveMapValue? {
653653
// RTLM5d2a: If ObjectsMapEntry.tombstone is true, return undefined/null
654654
// This is also equivalent to the RTLM14 check
655655
if entry.tombstone == true {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// The entries stored in a `LiveMap`'s data. Same as an `ObjectsMapEntry` but with an additional `tombstonedAt` property, per RTLM3a. (This property will be added in an upcoming commit.)
2+
internal struct InternalObjectsMapEntry {
3+
internal var tombstone: Bool? // OME2a
4+
internal var timeserial: String? // OME2b
5+
internal var data: ObjectData // OME2c
6+
}
7+
8+
internal extension InternalObjectsMapEntry {
9+
init(objectsMapEntry: ObjectsMapEntry) {
10+
tombstone = objectsMapEntry.tombstone
11+
timeserial = objectsMapEntry.timeserial
12+
data = objectsMapEntry.data
13+
}
14+
}

Tests/AblyLiveObjectsTests/Helpers/TestFactories.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,21 @@ struct TestFactories {
404404
)
405405
}
406406

407+
/// Creates an InternalObjectsMapEntry with sensible defaults
408+
///
409+
/// This should be kept in sync with ``mapEntry``.
410+
static func internalMapEntry(
411+
tombstone: Bool? = false,
412+
timeserial: String? = "ts1",
413+
data: ObjectData,
414+
) -> InternalObjectsMapEntry {
415+
InternalObjectsMapEntry(
416+
tombstone: tombstone,
417+
timeserial: timeserial,
418+
data: data,
419+
)
420+
}
421+
407422
/// Creates a map entry with string data
408423
static func stringMapEntry(
409424
key: String = "testKey",
@@ -421,6 +436,25 @@ struct TestFactories {
421436
)
422437
}
423438

439+
/// Creates an internal map entry with string data
440+
///
441+
/// This should be kept in sync with ``stringMapEntry``.
442+
static func internalStringMapEntry(
443+
key: String = "testKey",
444+
value: String = "testValue",
445+
tombstone: Bool? = false,
446+
timeserial: String? = "ts1",
447+
) -> (key: String, entry: InternalObjectsMapEntry) {
448+
(
449+
key: key,
450+
entry: internalMapEntry(
451+
tombstone: tombstone,
452+
timeserial: timeserial,
453+
data: ObjectData(string: .string(value)),
454+
),
455+
)
456+
}
457+
424458
/// Creates a map entry with number data
425459
static func numberMapEntry(
426460
key: String = "testKey",

Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct InternalDefaultLiveMapTests {
3838
@Test
3939
func returnsNilWhenEntryIsTombstoned() throws {
4040
let logger = TestLogger()
41-
let entry = TestFactories.mapEntry(
41+
let entry = TestFactories.internalMapEntry(
4242
tombstone: true,
4343
data: ObjectData(boolean: true), // Value doesn't matter as it's tombstoned
4444
)
@@ -51,7 +51,7 @@ struct InternalDefaultLiveMapTests {
5151
@Test
5252
func returnsBooleanValue() throws {
5353
let logger = TestLogger()
54-
let entry = TestFactories.mapEntry(data: ObjectData(boolean: true))
54+
let entry = TestFactories.internalMapEntry(data: ObjectData(boolean: true))
5555
let coreSDK = MockCoreSDK(channelState: .attaching)
5656
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main)
5757
let result = try map.get(key: "key", coreSDK: coreSDK, delegate: MockLiveMapObjectPoolDelegate())
@@ -63,7 +63,7 @@ struct InternalDefaultLiveMapTests {
6363
func returnsBytesValue() throws {
6464
let logger = TestLogger()
6565
let bytes = Data([0x01, 0x02, 0x03])
66-
let entry = TestFactories.mapEntry(data: ObjectData(bytes: bytes))
66+
let entry = TestFactories.internalMapEntry(data: ObjectData(bytes: bytes))
6767
let coreSDK = MockCoreSDK(channelState: .attaching)
6868
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main)
6969
let result = try map.get(key: "key", coreSDK: coreSDK, delegate: MockLiveMapObjectPoolDelegate())
@@ -74,7 +74,7 @@ struct InternalDefaultLiveMapTests {
7474
@Test
7575
func returnsNumberValue() throws {
7676
let logger = TestLogger()
77-
let entry = TestFactories.mapEntry(data: ObjectData(number: NSNumber(value: 123.456)))
77+
let entry = TestFactories.internalMapEntry(data: ObjectData(number: NSNumber(value: 123.456)))
7878
let coreSDK = MockCoreSDK(channelState: .attaching)
7979
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main)
8080
let result = try map.get(key: "key", coreSDK: coreSDK, delegate: MockLiveMapObjectPoolDelegate())
@@ -85,7 +85,7 @@ struct InternalDefaultLiveMapTests {
8585
@Test
8686
func returnsStringValue() throws {
8787
let logger = TestLogger()
88-
let entry = TestFactories.mapEntry(data: ObjectData(string: .string("test")))
88+
let entry = TestFactories.internalMapEntry(data: ObjectData(string: .string("test")))
8989
let coreSDK = MockCoreSDK(channelState: .attaching)
9090
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main)
9191
let result = try map.get(key: "key", coreSDK: coreSDK, delegate: MockLiveMapObjectPoolDelegate())
@@ -96,7 +96,7 @@ struct InternalDefaultLiveMapTests {
9696
@Test
9797
func returnsNilWhenReferencedObjectDoesNotExist() throws {
9898
let logger = TestLogger()
99-
let entry = TestFactories.mapEntry(data: ObjectData(objectId: "missing"))
99+
let entry = TestFactories.internalMapEntry(data: ObjectData(objectId: "missing"))
100100
let delegate = MockLiveMapObjectPoolDelegate()
101101
let coreSDK = MockCoreSDK(channelState: .attaching)
102102
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main)
@@ -108,7 +108,7 @@ struct InternalDefaultLiveMapTests {
108108
func returnsReferencedMap() throws {
109109
let logger = TestLogger()
110110
let objectId = "map1"
111-
let entry = TestFactories.mapEntry(data: ObjectData(objectId: objectId))
111+
let entry = TestFactories.internalMapEntry(data: ObjectData(objectId: objectId))
112112
let delegate = MockLiveMapObjectPoolDelegate()
113113
let coreSDK = MockCoreSDK(channelState: .attaching)
114114
let referencedMap = InternalDefaultLiveMap.createZeroValued(objectID: "arbitrary", logger: logger, userCallbackQueue: .main)
@@ -124,7 +124,7 @@ struct InternalDefaultLiveMapTests {
124124
func returnsReferencedCounter() throws {
125125
let logger = TestLogger()
126126
let objectId = "counter1"
127-
let entry = TestFactories.mapEntry(data: ObjectData(objectId: objectId))
127+
let entry = TestFactories.internalMapEntry(data: ObjectData(objectId: objectId))
128128
let delegate = MockLiveMapObjectPoolDelegate()
129129
let coreSDK = MockCoreSDK(channelState: .attaching)
130130
let referencedCounter = InternalDefaultLiveCounter.createZeroValued(objectID: "arbitrary", logger: logger, userCallbackQueue: .main)
@@ -139,7 +139,7 @@ struct InternalDefaultLiveMapTests {
139139
@Test
140140
func returnsNullOtherwise() throws {
141141
let logger = TestLogger()
142-
let entry = TestFactories.mapEntry(data: ObjectData())
142+
let entry = TestFactories.internalMapEntry(data: ObjectData())
143143
let delegate = MockLiveMapObjectPoolDelegate()
144144
let coreSDK = MockCoreSDK(channelState: .attaching)
145145
let map = InternalDefaultLiveMap(testsOnly_data: ["key": entry], objectID: "arbitrary", logger: logger, userCallbackQueue: .main)
@@ -294,11 +294,11 @@ struct InternalDefaultLiveMapTests {
294294
let map = InternalDefaultLiveMap(
295295
testsOnly_data: [
296296
// tombstone is nil, so not considered tombstoned
297-
"active1": TestFactories.mapEntry(data: ObjectData(string: .string("value1"))),
297+
"active1": TestFactories.internalMapEntry(data: ObjectData(string: .string("value1"))),
298298
// tombstone is false, so not considered tombstoned[
299-
"active2": TestFactories.mapEntry(tombstone: false, data: ObjectData(string: .string("value2"))),
300-
"tombstoned": TestFactories.mapEntry(tombstone: true, data: ObjectData(string: .string("tombstoned"))),
301-
"tombstoned2": TestFactories.mapEntry(tombstone: true, data: ObjectData(string: .string("tombstoned2"))),
299+
"active2": TestFactories.internalMapEntry(tombstone: false, data: ObjectData(string: .string("value2"))),
300+
"tombstoned": TestFactories.internalMapEntry(tombstone: true, data: ObjectData(string: .string("tombstoned"))),
301+
"tombstoned2": TestFactories.internalMapEntry(tombstone: true, data: ObjectData(string: .string("tombstoned2"))),
302302
],
303303
objectID: "arbitrary",
304304
logger: logger,
@@ -339,9 +339,9 @@ struct InternalDefaultLiveMapTests {
339339
let delegate = MockLiveMapObjectPoolDelegate()
340340
let map = InternalDefaultLiveMap(
341341
testsOnly_data: [
342-
"key1": TestFactories.mapEntry(data: ObjectData(string: .string("value1"))),
343-
"key2": TestFactories.mapEntry(data: ObjectData(string: .string("value2"))),
344-
"key3": TestFactories.mapEntry(data: ObjectData(string: .string("value3"))),
342+
"key1": TestFactories.internalMapEntry(data: ObjectData(string: .string("value1"))),
343+
"key2": TestFactories.internalMapEntry(data: ObjectData(string: .string("value2"))),
344+
"key3": TestFactories.internalMapEntry(data: ObjectData(string: .string("value3"))),
345345
],
346346
objectID: "arbitrary",
347347
logger: logger,
@@ -383,12 +383,12 @@ struct InternalDefaultLiveMapTests {
383383

384384
let map = InternalDefaultLiveMap(
385385
testsOnly_data: [
386-
"boolean": TestFactories.mapEntry(data: ObjectData(boolean: true)), // RTLM5d2b
387-
"bytes": TestFactories.mapEntry(data: ObjectData(bytes: Data([0x01, 0x02, 0x03]))), // RTLM5d2c
388-
"number": TestFactories.mapEntry(data: ObjectData(number: NSNumber(value: 42))), // RTLM5d2d
389-
"string": TestFactories.mapEntry(data: ObjectData(string: .string("hello"))), // RTLM5d2e
390-
"mapRef": TestFactories.mapEntry(data: ObjectData(objectId: "map:ref@123")), // RTLM5d2f2
391-
"counterRef": TestFactories.mapEntry(data: ObjectData(objectId: "counter:ref@456")), // RTLM5d2f2
386+
"boolean": TestFactories.internalMapEntry(data: ObjectData(boolean: true)), // RTLM5d2b
387+
"bytes": TestFactories.internalMapEntry(data: ObjectData(bytes: Data([0x01, 0x02, 0x03]))), // RTLM5d2c
388+
"number": TestFactories.internalMapEntry(data: ObjectData(number: NSNumber(value: 42))), // RTLM5d2d
389+
"string": TestFactories.internalMapEntry(data: ObjectData(string: .string("hello"))), // RTLM5d2e
390+
"mapRef": TestFactories.internalMapEntry(data: ObjectData(objectId: "map:ref@123")), // RTLM5d2f2
391+
"counterRef": TestFactories.internalMapEntry(data: ObjectData(objectId: "counter:ref@456")), // RTLM5d2f2
392392
],
393393
objectID: "arbitrary",
394394
logger: logger,
@@ -434,7 +434,7 @@ struct InternalDefaultLiveMapTests {
434434
let delegate = MockLiveMapObjectPoolDelegate()
435435
let coreSDK = MockCoreSDK(channelState: .attaching)
436436
let map = InternalDefaultLiveMap(
437-
testsOnly_data: ["key1": TestFactories.mapEntry(timeserial: "ts2", data: ObjectData(string: .string("existing")))],
437+
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: "ts2", data: ObjectData(string: .string("existing")))],
438438
objectID: "arbitrary",
439439
logger: logger,
440440
userCallbackQueue: .main,
@@ -473,7 +473,7 @@ struct InternalDefaultLiveMapTests {
473473
let delegate = MockLiveMapObjectPoolDelegate()
474474
let coreSDK = MockCoreSDK(channelState: .attaching)
475475
let map = InternalDefaultLiveMap(
476-
testsOnly_data: ["key1": TestFactories.mapEntry(tombstone: true, timeserial: "ts1", data: ObjectData(string: .string("existing")))],
476+
testsOnly_data: ["key1": TestFactories.internalMapEntry(tombstone: true, timeserial: "ts1", data: ObjectData(string: .string("existing")))],
477477
objectID: "arbitrary",
478478
logger: logger,
479479
userCallbackQueue: .main,
@@ -642,7 +642,7 @@ struct InternalDefaultLiveMapTests {
642642
let delegate = MockLiveMapObjectPoolDelegate()
643643
let coreSDK = MockCoreSDK(channelState: .attaching)
644644
let map = InternalDefaultLiveMap(
645-
testsOnly_data: ["key1": TestFactories.mapEntry(timeserial: "ts2", data: ObjectData(string: .string("existing")))],
645+
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: "ts2", data: ObjectData(string: .string("existing")))],
646646
objectID: "arbitrary",
647647
logger: logger,
648648
userCallbackQueue: .main,
@@ -667,7 +667,7 @@ struct InternalDefaultLiveMapTests {
667667
let delegate = MockLiveMapObjectPoolDelegate()
668668
let coreSDK = MockCoreSDK(channelState: .attaching)
669669
let map = InternalDefaultLiveMap(
670-
testsOnly_data: ["key1": TestFactories.mapEntry(tombstone: false, timeserial: "ts1", data: ObjectData(string: .string("existing")))],
670+
testsOnly_data: ["key1": TestFactories.internalMapEntry(tombstone: false, timeserial: "ts1", data: ObjectData(string: .string("existing")))],
671671
objectID: "arbitrary",
672672
logger: logger,
673673
userCallbackQueue: .main,
@@ -791,7 +791,7 @@ struct InternalDefaultLiveMapTests {
791791
let delegate = MockLiveMapObjectPoolDelegate()
792792
let coreSDK = MockCoreSDK(channelState: .attaching)
793793
let map = InternalDefaultLiveMap(
794-
testsOnly_data: ["key1": TestFactories.mapEntry(timeserial: entrySerial, data: ObjectData(string: .string("existing")))],
794+
testsOnly_data: ["key1": TestFactories.internalMapEntry(timeserial: entrySerial, data: ObjectData(string: .string("existing")))],
795795
objectID: "arbitrary",
796796
logger: logger,
797797
userCallbackQueue: .main,
@@ -849,7 +849,7 @@ struct InternalDefaultLiveMapTests {
849849
let delegate = MockLiveMapObjectPoolDelegate()
850850
let coreSDK = MockCoreSDK(channelState: .attaching)
851851
let map = InternalDefaultLiveMap(
852-
testsOnly_data: ["key1": TestFactories.stringMapEntry().entry],
852+
testsOnly_data: ["key1": TestFactories.internalStringMapEntry().entry],
853853
objectID: "arbitrary",
854854
logger: logger,
855855
userCallbackQueue: .main,
@@ -881,8 +881,8 @@ struct InternalDefaultLiveMapTests {
881881
let logger = TestLogger()
882882
let map = InternalDefaultLiveMap(
883883
testsOnly_data: [
884-
"keyThatWillBeRemoved": TestFactories.stringMapEntry(timeserial: "ts1").entry,
885-
"keyThatWillNotBeRemoved": TestFactories.stringMapEntry(timeserial: "ts1").entry,
884+
"keyThatWillBeRemoved": TestFactories.internalStringMapEntry(timeserial: "ts1").entry,
885+
"keyThatWillNotBeRemoved": TestFactories.internalStringMapEntry(timeserial: "ts1").entry,
886886
],
887887
objectID: "arbitrary",
888888
logger: logger,

0 commit comments

Comments
 (0)