Skip to content

Commit 46de61a

Browse files
Add ExpressibleBy*Literal conformance to LiveMapValue.
The conversion to use literal syntax was done by Cursor; I've vaguely satisfied myself that it's caught most of the usages but there may be some it's missed. Resolves #65.
1 parent 9baf46f commit 46de61a

3 files changed

Lines changed: 78 additions & 19 deletions

File tree

Sources/AblyLiveObjects/Public/PublicTypes.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,27 @@ public protocol RealtimeObjects: Sendable {
8383

8484
/// Represents the type of data stored for a given key in a ``LiveMap``.
8585
/// It may be a primitive value (string, number, boolean, binary data, JSON array, or JSON object), or another ``LiveObject``.
86+
///
87+
/// `LiveMapValue` implements Swift's `ExpressibleBy*Literal` protocols. This, in combination with `JSONValue`'s conformance to these protocols, allows you to write type-safe map values using familiar syntax. For example:
88+
///
89+
/// ```swift
90+
/// let map = try await channel.objects.createMap(entries: [
91+
/// "someStringKey": "someString",
92+
/// "someIntegerKey": 123,
93+
/// "someFloatKey": 123.456,
94+
/// "someTrueKey": true,
95+
/// "someFalseKey": false,
96+
/// "someJSONObjectKey": [
97+
/// "someNestedJSONObjectKey": [
98+
/// "someOtherKey": "someOtherValue",
99+
/// ],
100+
/// ],
101+
/// "someJSONArrayKey": [
102+
/// "foo",
103+
/// 42,
104+
/// ],
105+
/// ])
106+
/// ```
86107
public enum LiveMapValue: Sendable, Equatable {
87108
case string(String)
88109
case number(Double)
@@ -185,6 +206,44 @@ public enum LiveMapValue: Sendable, Equatable {
185206
}
186207
}
187208

209+
// MARK: - ExpressibleBy*Literal conformances
210+
211+
extension LiveMapValue: ExpressibleByDictionaryLiteral {
212+
public init(dictionaryLiteral elements: (String, JSONValue)...) {
213+
self = .jsonObject(.init(uniqueKeysWithValues: elements))
214+
}
215+
}
216+
217+
extension LiveMapValue: ExpressibleByArrayLiteral {
218+
public init(arrayLiteral elements: JSONValue...) {
219+
self = .jsonArray(elements)
220+
}
221+
}
222+
223+
extension LiveMapValue: ExpressibleByStringLiteral {
224+
public init(stringLiteral value: String) {
225+
self = .string(value)
226+
}
227+
}
228+
229+
extension LiveMapValue: ExpressibleByIntegerLiteral {
230+
public init(integerLiteral value: Int) {
231+
self = .number(Double(value))
232+
}
233+
}
234+
235+
extension LiveMapValue: ExpressibleByFloatLiteral {
236+
public init(floatLiteral value: Double) {
237+
self = .number(value)
238+
}
239+
}
240+
241+
extension LiveMapValue: ExpressibleByBooleanLiteral {
242+
public init(booleanLiteral value: Bool) {
243+
self = .bool(value)
244+
}
245+
}
246+
188247
/// Object returned from an `on` call, allowing the listener provided in that call to be deregistered.
189248
public protocol OnObjectsEventResponse: Sendable {
190249
/// Deregisters the listener passed to the `on` call.

Tests/AblyLiveObjectsTests/AblyLiveObjectsTests.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,13 @@ struct AblyLiveObjectsTests {
139139

140140
// Create a map and check its initial entries
141141
let map = try await channel.objects.createMap(entries: [
142-
"boolKey": .bool(true),
143-
"numberKey": .number(10),
142+
"boolKey": true,
143+
"numberKey": 10,
144144
])
145145
#expect(
146146
try Dictionary(uniqueKeysWithValues: map.entries) == [
147-
"boolKey": .bool(true),
148-
"numberKey": .number(10),
147+
"boolKey": true,
148+
"numberKey": 10,
149149
],
150150
)
151151
let mapSubscription = try map.updates()
@@ -162,8 +162,8 @@ struct AblyLiveObjectsTests {
162162
#expect(mapUpdate.update == ["counterKey": .updated])
163163
#expect(
164164
try Dictionary(uniqueKeysWithValues: map.entries) == [
165-
"boolKey": .bool(true),
166-
"numberKey": .number(10),
165+
"boolKey": true,
166+
"numberKey": 10,
167167
"counterKey": .liveCounter(counter),
168168
],
169169
)
@@ -180,7 +180,7 @@ struct AblyLiveObjectsTests {
180180
#expect(mapRemoveUpdate.update == ["boolKey": .removed])
181181
#expect(
182182
try Dictionary(uniqueKeysWithValues: map.entries) == [
183-
"numberKey": .number(10),
183+
"numberKey": 10,
184184
"counterKey": .liveCounter(counter),
185185
],
186186
)

Tests/AblyLiveObjectsTests/JS Integration Tests/ObjectsIntegrationTests.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@ private let primitiveKeyData: [(key: String, data: [String: JSONValue], liveMapV
137137
(
138138
key: "stringKey",
139139
data: ["string": .string("stringValue")],
140-
liveMapValue: .string("stringValue")
140+
liveMapValue: "stringValue"
141141
),
142142
(
143143
key: "emptyStringKey",
144144
data: ["string": .string("")],
145-
liveMapValue: .string("")
145+
liveMapValue: ""
146146
),
147147
(
148148
key: "bytesKey",
@@ -167,22 +167,22 @@ private let primitiveKeyData: [(key: String, data: [String: JSONValue], liveMapV
167167
(
168168
key: "numberKey",
169169
data: ["number": .number(1)],
170-
liveMapValue: .number(1)
170+
liveMapValue: 1
171171
),
172172
(
173173
key: "zeroKey",
174174
data: ["number": .number(0)],
175-
liveMapValue: .number(0)
175+
liveMapValue: 0
176176
),
177177
(
178178
key: "trueKey",
179179
data: ["boolean": .bool(true)],
180-
liveMapValue: .bool(true)
180+
liveMapValue: true
181181
),
182182
(
183183
key: "falseKey",
184184
data: ["boolean": .bool(false)],
185-
liveMapValue: .bool(false)
185+
liveMapValue: false
186186
),
187187
]
188188

@@ -422,7 +422,7 @@ private struct ObjectsIntegrationTests {
422422
}
423423

424424
// MAP_CREATE
425-
let map = try await objects.createMap(entries: ["shouldStay": .string("foo"), "shouldDelete": .string("bar")])
425+
let map = try await objects.createMap(entries: ["shouldStay": "foo", "shouldDelete": "bar"])
426426
// COUNTER_CREATE
427427
let counter = try await objects.createCounter(count: 1)
428428

@@ -449,7 +449,7 @@ private struct ObjectsIntegrationTests {
449449
}
450450

451451
// Perform the operations and await the promise
452-
async let setAnotherKeyPromise: Void = map.set(key: "anotherKey", value: .string("baz"))
452+
async let setAnotherKeyPromise: Void = map.set(key: "anotherKey", value: "baz")
453453
async let removeKeyPromise: Void = map.remove(key: "shouldDelete")
454454
async let incrementPromise: Void = counter.increment(amount: 10)
455455
_ = try await (setAnotherKeyPromise, removeKeyPromise, incrementPromise, operationsAppliedPromise)
@@ -3032,7 +3032,7 @@ private struct ObjectsIntegrationTests {
30323032
async let mapCreatedPromise: Void = waitForMapKeyUpdate(mapCreatedPromiseUpdates, "map")
30333033

30343034
let counter = try await objects.createCounter()
3035-
let map = try await objects.createMap(entries: ["foo": .string("bar"), "baz": .liveCounter(counter)])
3035+
let map = try await objects.createMap(entries: ["foo": "bar", "baz": .liveCounter(counter)])
30363036
try await root.set(key: "map", value: .liveMap(map))
30373037
_ = await mapCreatedPromise
30383038

@@ -3058,7 +3058,7 @@ private struct ObjectsIntegrationTests {
30583058
internallyTypedObjects.testsOnly_overridePublish(with: { _ in })
30593059

30603060
// prevent publishing of ops to realtime so we guarantee that the initial value doesn't come from a CREATE op
3061-
let map = try await objects.createMap(entries: ["foo": .string("bar")])
3061+
let map = try await objects.createMap(entries: ["foo": "bar"])
30623062
#expect(try #require(map.get(key: "foo")?.stringValue) == "bar", "Check map has expected initial value")
30633063
},
30643064
),
@@ -3103,7 +3103,7 @@ private struct ObjectsIntegrationTests {
31033103
}
31043104
})
31053105

3106-
let map = try await objects.createMap(entries: ["foo": .string("bar")])
3106+
let map = try await objects.createMap(entries: ["foo": "bar"])
31073107

31083108
// Map should be created with forged initial value instead of the actual one
31093109
#expect(try map.get(key: "foo") == nil, "Check key \"foo\" was not set on a map client-side")
@@ -3127,7 +3127,7 @@ private struct ObjectsIntegrationTests {
31273127
})
31283128

31293129
// Create map locally, should have an initial value set
3130-
let map = try await objects.createMap(entries: ["foo": .string("bar")])
3130+
let map = try await objects.createMap(entries: ["foo": "bar"])
31313131
let internalMap = try #require(map as? PublicDefaultLiveMap)
31323132
let mapId = internalMap.proxied.objectID
31333133

0 commit comments

Comments
 (0)