Skip to content

Commit e355f2e

Browse files
Implement increment / decrement / set / delete
Based on [1] at cb11ba8. Code by me, tests by Cursor and tidied up by me. Channel mode checking and echoMessages check omitted as in e65643a. [1] ably/specification#353
1 parent f0541e2 commit e355f2e

6 files changed

Lines changed: 389 additions & 12 deletions

File tree

Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,43 @@ internal final class InternalDefaultLiveCounter: Sendable {
8989
}
9090
}
9191

92-
internal func increment(amount _: Double) async throws(ARTErrorInfo) {
93-
notYetImplemented()
92+
internal func increment(amount: Double, coreSDK: CoreSDK) async throws(ARTErrorInfo) {
93+
do throws(InternalError) {
94+
// RTLC12c
95+
do {
96+
try coreSDK.validateChannelState(notIn: [.detached, .failed, .suspended], operationDescription: "RealtimeObjects.createMap")
97+
} catch {
98+
throw error.toInternalError()
99+
}
100+
101+
// RTLC12e1
102+
if !amount.isFinite {
103+
throw LiveObjectsError.counterIncrementAmountInvalid(amount: amount).toARTErrorInfo().toInternalError()
104+
}
105+
106+
let objectMessage = OutboundObjectMessage(
107+
operation: .init(
108+
// RTLC12e2
109+
action: .known(.counterInc),
110+
// RTLC12e3
111+
objectId: objectID,
112+
counterOp: .init(
113+
// RTLC12e4
114+
amount: .init(value: amount),
115+
),
116+
),
117+
)
118+
119+
// RTLC12f
120+
try await coreSDK.publish(objectMessages: [objectMessage])
121+
} catch {
122+
throw error.toARTErrorInfo()
123+
}
94124
}
95125

96-
internal func decrement(amount _: Double) async throws(ARTErrorInfo) {
97-
notYetImplemented()
126+
internal func decrement(amount: Double, coreSDK: CoreSDK) async throws(ARTErrorInfo) {
127+
// RTLC13b
128+
try await increment(amount: -amount, coreSDK: coreSDK)
98129
}
99130

100131
@discardableResult

Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,63 @@ internal final class InternalDefaultLiveMap: Sendable {
173173
try entries(coreSDK: coreSDK, delegate: delegate).map(\.value)
174174
}
175175

176-
internal func set(key _: String, value _: LiveMapValue) async throws(ARTErrorInfo) {
177-
notYetImplemented()
176+
internal func set(key: String, value: InternalLiveMapValue, coreSDK: CoreSDK) async throws(ARTErrorInfo) {
177+
do throws(InternalError) {
178+
// RTLM20c
179+
do {
180+
try coreSDK.validateChannelState(notIn: [.detached, .failed, .suspended], operationDescription: "RealtimeObjects.createMap")
181+
} catch {
182+
throw error.toInternalError()
183+
}
184+
185+
let objectMessage = OutboundObjectMessage(
186+
operation: .init(
187+
// RTLM20e2
188+
action: .known(.mapSet),
189+
// RTLM20e3
190+
objectId: objectID,
191+
mapOp: .init(
192+
// RTLM20e4
193+
key: key,
194+
// RTLM20e5
195+
data: value.toObjectData,
196+
),
197+
),
198+
)
199+
200+
try await coreSDK.publish(objectMessages: [objectMessage])
201+
} catch {
202+
throw error.toARTErrorInfo()
203+
}
178204
}
179205

180-
internal func remove(key _: String) async throws(ARTErrorInfo) {
181-
notYetImplemented()
206+
internal func remove(key: String, coreSDK: CoreSDK) async throws(ARTErrorInfo) {
207+
do throws(InternalError) {
208+
// RTLM21c
209+
do {
210+
try coreSDK.validateChannelState(notIn: [.detached, .failed, .suspended], operationDescription: "RealtimeObjects.createMap")
211+
} catch {
212+
throw error.toInternalError()
213+
}
214+
215+
let objectMessage = OutboundObjectMessage(
216+
operation: .init(
217+
// RTLM21e2
218+
action: .known(.mapRemove),
219+
// RTLM21e3
220+
objectId: objectID,
221+
mapOp: .init(
222+
// RTLM21e4
223+
key: key,
224+
),
225+
),
226+
)
227+
228+
// RTLM21f
229+
try await coreSDK.publish(objectMessages: [objectMessage])
230+
} catch {
231+
throw error.toARTErrorInfo()
232+
}
182233
}
183234

184235
@discardableResult

Sources/AblyLiveObjects/Public/Public Proxy Objects/PublicDefaultLiveCounter.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ internal final class PublicDefaultLiveCounter: LiveCounter {
2727
}
2828

2929
internal func increment(amount: Double) async throws(ARTErrorInfo) {
30-
try await proxied.increment(amount: amount)
30+
try await proxied.increment(amount: amount, coreSDK: coreSDK)
3131
}
3232

3333
internal func decrement(amount: Double) async throws(ARTErrorInfo) {
34-
try await proxied.decrement(amount: amount)
34+
try await proxied.decrement(amount: amount, coreSDK: coreSDK)
3535
}
3636

3737
internal func subscribe(listener: @escaping LiveObjectUpdateCallback<LiveCounterUpdate>) throws(ARTErrorInfo) -> any SubscribeResponse {

Sources/AblyLiveObjects/Public/Public Proxy Objects/PublicDefaultLiveMap.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,13 @@ internal final class PublicDefaultLiveMap: LiveMap {
7676
}
7777

7878
internal func set(key: String, value: LiveMapValue) async throws(ARTErrorInfo) {
79-
try await proxied.set(key: key, value: value)
79+
let internalValue = InternalLiveMapValue(liveMapValue: value)
80+
81+
try await proxied.set(key: key, value: internalValue, coreSDK: coreSDK)
8082
}
8183

8284
internal func remove(key: String) async throws(ARTErrorInfo) {
83-
try await proxied.remove(key: key)
85+
try await proxied.remove(key: key, coreSDK: coreSDK)
8486
}
8587

8688
internal func subscribe(listener: @escaping LiveObjectUpdateCallback<LiveMapUpdate>) throws(ARTErrorInfo) -> any SubscribeResponse {

Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,120 @@ struct InternalDefaultLiveCounterTests {
422422
#expect(subscriberInvocations.isEmpty)
423423
}
424424
}
425+
426+
/// Tests for the `increment` method, covering RTLC12 specification points
427+
struct IncrementTests {
428+
// @spec RTLC12c
429+
@Test(arguments: [.detached, .failed, .suspended] as [ARTRealtimeChannelState])
430+
func throwsErrorForInvalidChannelState(channelState: ARTRealtimeChannelState) async throws {
431+
let logger = TestLogger()
432+
let counter = InternalDefaultLiveCounter.createZeroValued(objectID: "arbitrary", logger: logger, userCallbackQueue: .main, clock: MockSimpleClock())
433+
let coreSDK = MockCoreSDK(channelState: channelState)
434+
435+
await #expect {
436+
try await counter.increment(amount: 10, coreSDK: coreSDK)
437+
} throws: { error in
438+
guard let errorInfo = error as? ARTErrorInfo else {
439+
return false
440+
}
441+
442+
return errorInfo.code == 90001 && errorInfo.statusCode == 400
443+
}
444+
}
445+
446+
// @spec RTLC12e1 - The only part that is relevant in Swift's type system is the finiteness check
447+
@Test(arguments: [
448+
Double.nan,
449+
Double.infinity,
450+
-Double.infinity,
451+
] as [Double])
452+
func throwsErrorForInvalidAmount(amount: Double) async throws {
453+
let logger = TestLogger()
454+
let counter = InternalDefaultLiveCounter.createZeroValued(objectID: "arbitrary", logger: logger, userCallbackQueue: .main, clock: MockSimpleClock())
455+
let coreSDK = MockCoreSDK(channelState: .attached)
456+
457+
await #expect {
458+
try await counter.increment(amount: amount, coreSDK: coreSDK)
459+
} throws: { error in
460+
guard let errorInfo = error as? ARTErrorInfo else {
461+
return false
462+
}
463+
464+
return errorInfo.code == 40003 && errorInfo.statusCode == 400
465+
}
466+
}
467+
468+
// @spec RTLC12e2
469+
// @spec RTLC12e3
470+
// @spec RTLC12e4
471+
// @spec RTLC12f
472+
func publishesCorrectObjectMessage() async throws {
473+
let logger = TestLogger()
474+
let counter = InternalDefaultLiveCounter.createZeroValued(objectID: "counter:test@123", logger: logger, userCallbackQueue: .main, clock: MockSimpleClock())
475+
let coreSDK = MockCoreSDK(channelState: .attached)
476+
477+
var publishedMessages: [OutboundObjectMessage] = []
478+
coreSDK.setPublishHandler { messages in
479+
publishedMessages.append(contentsOf: messages)
480+
}
481+
482+
try await counter.increment(amount: 10.5, coreSDK: coreSDK)
483+
484+
let expectedMessage = OutboundObjectMessage(
485+
operation: ObjectOperation(
486+
// RTLC12e2
487+
action: .known(.counterInc),
488+
// RTLC12e3
489+
objectId: "counter:test@123",
490+
// RTLC12e4
491+
counterOp: WireObjectsCounterOp(amount: NSNumber(value: 10.5)),
492+
),
493+
)
494+
// RTLC12f
495+
#expect(publishedMessages.count == 1)
496+
#expect(publishedMessages[0] == expectedMessage)
497+
}
498+
499+
@Test
500+
func throwsErrorWhenPublishFails() async throws {
501+
let logger = TestLogger()
502+
let counter = InternalDefaultLiveCounter.createZeroValued(objectID: "counter:test@123", logger: logger, userCallbackQueue: .main, clock: MockSimpleClock())
503+
let coreSDK = MockCoreSDK(channelState: .attached)
504+
505+
coreSDK.setPublishHandler { _ throws(InternalError) in
506+
throw InternalError.other(.generic(NSError(domain: "test", code: 0, userInfo: [NSLocalizedDescriptionKey: "Publish failed"])))
507+
}
508+
509+
await #expect {
510+
try await counter.increment(amount: 10, coreSDK: coreSDK)
511+
} throws: { error in
512+
guard let errorInfo = error as? ARTErrorInfo else {
513+
return false
514+
}
515+
return errorInfo.message.contains("Publish failed")
516+
}
517+
}
518+
}
519+
520+
/// Tests for the `decrement` method, covering RTLC13 specification points
521+
struct DecrementTests {
522+
// @spec RTLC13b
523+
@Test
524+
func isOppositeOfIncrement() async throws {
525+
// This is just a smoke test; we assume that this just calls `increment`, which is tested elsewhere.
526+
let counter = InternalDefaultLiveCounter.createZeroValued(objectID: "counter:test@123", logger: TestLogger(), userCallbackQueue: .main, clock: MockSimpleClock())
527+
let coreSDK = MockCoreSDK(channelState: .attached)
528+
529+
var publishedMessages: [OutboundObjectMessage] = []
530+
coreSDK.setPublishHandler { messages in
531+
publishedMessages.append(contentsOf: messages)
532+
}
533+
534+
try await counter.decrement(amount: 10.5, coreSDK: coreSDK)
535+
536+
// RTLC12f
537+
#expect(publishedMessages.count == 1)
538+
#expect(publishedMessages[0].operation?.counterOp?.amount == -10.5 /* i.e. assert the amount gets negated */ )
539+
}
540+
}
425541
}

0 commit comments

Comments
 (0)