Skip to content

Commit 1072f43

Browse files
wip claude's first stab
TODO as a separate piece of work we should stop using ARTErrorInfo in the mock channel; that should have been stopped a long time ago! TODO look at the changes it's made to test helpers TODO use the same `toErrorInfo` helper as in LiveObjects after giving it feedback
1 parent d9b38f6 commit 1072f43

31 files changed

+449
-547
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,14 @@ To check formatting and code quality, run `swift run BuildTool lint`. Run with `
4646
### Throwing errors
4747

4848
- The public API of the SDK should use typed throws, and the thrown errors should be of type `ErrorInfo`.
49-
- Currently, we throw the `InternalError` type everywhere internally, and then convert it to `ErrorInfo` at the public API.
5049

5150
If you haven't worked with typed throws before, be aware of a few sharp edges:
5251

5352
- Some of the Swift standard library does not (yet?) interact as nicely with typed throws as you might hope.
5453
- It is not currently possible to create a `Task`, `CheckedContinuation`, or `AsyncThrowingStream` with a specific error type. You will need to instead return a `Result` and then call its `.get()` method.
5554
- `Dictionary.mapValues` does not support typed throws. We have our own extension `ablyChat_mapValuesWithTypedThrow` which does; use this.
56-
- There are times when the compiler struggles to infer the type of the error thrown within a `do` block. In these cases, you can disable type inference for a `do` block and explicitly specify the type of the thrown error, like: `do throws(InternalError) { … }`.
57-
- The compiler will never infer the type of the error thrown by a closure; you will need to specify this yourself; e.g. `let items = try jsonValues.map { jsonValue throws(InternalError) in … }`.
55+
- There are times when the compiler struggles to infer the type of the error thrown within a `do` block. In these cases, you can disable type inference for a `do` block and explicitly specify the type of the thrown error, like: `do throws(ErrorInfo) { … }`.
56+
- The compiler will never infer the type of the error thrown by a closure; you will need to specify this yourself; e.g. `let items = try jsonValues.map { jsonValue throws(ErrorInfo) in … }`.
5857

5958
### Swift concurrency rough edges
6059

Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal protocol InternalRealtimeClientProtocol: AnyObject, Sendable {
2121
associatedtype Connection: InternalConnectionProtocol
2222

2323
var clientId: String? { get }
24-
func request(_ method: String, path: String, params: [String: String]?, body: Any?, headers: [String: String]?) async throws(InternalError) -> ARTHTTPPaginatedResponse
24+
func request(_ method: String, path: String, params: [String: String]?, body: Any?, headers: [String: String]?) async throws(ErrorInfo) -> ARTHTTPPaginatedResponse
2525

2626
var channels: Channels { get }
2727
var connection: Connection { get }
@@ -55,8 +55,8 @@ internal protocol InternalRealtimeChannelProtocol: AnyObject, Sendable {
5555

5656
var annotations: Annotations { get }
5757

58-
func attach() async throws(InternalError)
59-
func detach() async throws(InternalError)
58+
func attach() async throws(ErrorInfo)
59+
func detach() async throws(ErrorInfo)
6060
var name: String { get }
6161
var state: ARTRealtimeChannelState { get }
6262
var errorReason: ARTErrorInfo? { get }
@@ -65,7 +65,7 @@ internal protocol InternalRealtimeChannelProtocol: AnyObject, Sendable {
6565
func once(_ cb: @escaping @MainActor (ARTChannelStateChange) -> Void) -> ARTEventListener
6666
func once(_ event: ARTChannelEvent, callback cb: @escaping @MainActor (ARTChannelStateChange) -> Void) -> ARTEventListener
6767
func unsubscribe(_: ARTEventListener?)
68-
func publish(_ name: String?, data: JSONValue?, extras: [String: JSONValue]?) async throws(InternalError)
68+
func publish(_ name: String?, data: JSONValue?, extras: [String: JSONValue]?) async throws(ErrorInfo)
6969
func subscribe(_ callback: @escaping @MainActor (ARTMessage) -> Void) -> ARTEventListener?
7070
func subscribe(_ name: String, callback: @escaping @MainActor (ARTMessage) -> Void) -> ARTEventListener?
7171
var properties: ARTChannelProperties { get }
@@ -75,11 +75,11 @@ internal protocol InternalRealtimeChannelProtocol: AnyObject, Sendable {
7575
/// Expresses the requirements of the object returned by ``InternalRealtimeChannelProtocol/presence``.
7676
@MainActor
7777
internal protocol InternalRealtimePresenceProtocol: AnyObject, Sendable {
78-
func get() async throws(InternalError) -> [PresenceMessage]
79-
func get(_ query: ARTRealtimePresenceQuery) async throws(InternalError) -> [PresenceMessage]
80-
func enter(_ data: JSONObject?) async throws(InternalError)
81-
func leave(_ data: JSONObject?) async throws(InternalError)
82-
func update(_ data: JSONObject?) async throws(InternalError)
78+
func get() async throws(ErrorInfo) -> [PresenceMessage]
79+
func get(_ query: ARTRealtimePresenceQuery) async throws(ErrorInfo) -> [PresenceMessage]
80+
func enter(_ data: JSONObject?) async throws(ErrorInfo)
81+
func leave(_ data: JSONObject?) async throws(ErrorInfo)
82+
func update(_ data: JSONObject?) async throws(ErrorInfo)
8383
func subscribe(_ callback: @escaping @MainActor (ARTPresenceMessage) -> Void) -> ARTEventListener?
8484
func subscribe(_ action: ARTPresenceAction, callback: @escaping @MainActor (ARTPresenceMessage) -> Void) -> ARTEventListener?
8585
func unsubscribe(_ listener: ARTEventListener)
@@ -145,7 +145,7 @@ internal final class InternalRealtimeClientAdapter<Underlying: ProxyRealtimeClie
145145
underlying.clientId
146146
}
147147

148-
internal func request(_ method: String, path: String, params: [String: String]?, body: Any?, headers: [String: String]?) async throws(InternalError) -> ARTHTTPPaginatedResponse {
148+
internal func request(_ method: String, path: String, params: [String: String]?, body: Any?, headers: [String: String]?) async throws(ErrorInfo) -> ARTHTTPPaginatedResponse {
149149
do {
150150
return try await withCheckedContinuation { (continuation: CheckedContinuation<Result<ARTHTTPPaginatedResponse, ARTErrorInfo>, _>) in
151151
do {
@@ -164,7 +164,7 @@ internal final class InternalRealtimeClientAdapter<Underlying: ProxyRealtimeClie
164164
}
165165
}.get()
166166
} catch {
167-
throw InternalError.fromAblyCocoa(error)
167+
throw .init(ablyCocoaError: error)
168168
}
169169
}
170170
}
@@ -220,7 +220,7 @@ internal final class InternalRealtimePresenceAdapter<Underlying: RealtimePresenc
220220
self.underlying = underlying
221221
}
222222

223-
internal func get() async throws(InternalError) -> [PresenceMessage] {
223+
internal func get() async throws(ErrorInfo) -> [PresenceMessage] {
224224
do {
225225
return try await withCheckedContinuation { (continuation: CheckedContinuation<Result<[PresenceMessage], ARTErrorInfo>, _>) in
226226
underlying.get { members, error in
@@ -234,11 +234,11 @@ internal final class InternalRealtimePresenceAdapter<Underlying: RealtimePresenc
234234
}
235235
}.get()
236236
} catch {
237-
throw InternalError.fromAblyCocoa(error)
237+
throw .init(ablyCocoaError: error)
238238
}
239239
}
240240

241-
internal func get(_ query: ARTRealtimePresenceQuery) async throws(InternalError) -> [PresenceMessage] {
241+
internal func get(_ query: ARTRealtimePresenceQuery) async throws(ErrorInfo) -> [PresenceMessage] {
242242
do {
243243
return try await withCheckedContinuation { (continuation: CheckedContinuation<Result<[PresenceMessage], ARTErrorInfo>, _>) in
244244
underlying.get(query) { members, error in
@@ -252,11 +252,11 @@ internal final class InternalRealtimePresenceAdapter<Underlying: RealtimePresenc
252252
}
253253
}.get()
254254
} catch {
255-
throw InternalError.fromAblyCocoa(error)
255+
throw .init(ablyCocoaError: error)
256256
}
257257
}
258258

259-
internal func leave(_ data: JSONObject?) async throws(InternalError) {
259+
internal func leave(_ data: JSONObject?) async throws(ErrorInfo) {
260260
do {
261261
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, ARTErrorInfo>, _>) in
262262
underlying.leave(data?.toAblyCocoaData) { error in
@@ -268,11 +268,11 @@ internal final class InternalRealtimePresenceAdapter<Underlying: RealtimePresenc
268268
}
269269
}.get()
270270
} catch {
271-
throw InternalError.fromAblyCocoa(error)
271+
throw .init(ablyCocoaError: error)
272272
}
273273
}
274274

275-
internal func enter(_ data: JSONObject?) async throws(InternalError) {
275+
internal func enter(_ data: JSONObject?) async throws(ErrorInfo) {
276276
do {
277277
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, ARTErrorInfo>, _>) in
278278
underlying.enter(data?.toAblyCocoaData) { error in
@@ -284,11 +284,11 @@ internal final class InternalRealtimePresenceAdapter<Underlying: RealtimePresenc
284284
}
285285
}.get()
286286
} catch {
287-
throw InternalError.fromAblyCocoa(error)
287+
throw .init(ablyCocoaError: error)
288288
}
289289
}
290290

291-
internal func update(_ data: JSONObject?) async throws(InternalError) {
291+
internal func update(_ data: JSONObject?) async throws(ErrorInfo) {
292292
do {
293293
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, ARTErrorInfo>, _>) in
294294
underlying.update(data?.toAblyCocoaData) { error in
@@ -300,7 +300,7 @@ internal final class InternalRealtimePresenceAdapter<Underlying: RealtimePresenc
300300
}
301301
}.get()
302302
} catch {
303-
throw InternalError.fromAblyCocoa(error)
303+
throw .init(ablyCocoaError: error)
304304
}
305305
}
306306

@@ -346,7 +346,7 @@ internal final class InternalRealtimeChannelAdapter<Underlying: ProxyRealtimeCha
346346
underlying.properties
347347
}
348348

349-
internal func attach() async throws(InternalError) {
349+
internal func attach() async throws(ErrorInfo) {
350350
do {
351351
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, ARTErrorInfo>, _>) in
352352
underlying.attach { error in
@@ -358,11 +358,11 @@ internal final class InternalRealtimeChannelAdapter<Underlying: ProxyRealtimeCha
358358
}
359359
}.get()
360360
} catch {
361-
throw InternalError.fromAblyCocoa(error)
361+
throw .init(ablyCocoaError: error)
362362
}
363363
}
364364

365-
internal func detach() async throws(InternalError) {
365+
internal func detach() async throws(ErrorInfo) {
366366
do {
367367
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, ARTErrorInfo>, _>) in
368368
underlying.detach { error in
@@ -374,11 +374,11 @@ internal final class InternalRealtimeChannelAdapter<Underlying: ProxyRealtimeCha
374374
}
375375
}.get()
376376
} catch {
377-
throw InternalError.fromAblyCocoa(error)
377+
throw .init(ablyCocoaError: error)
378378
}
379379
}
380380

381-
internal func publish(_ name: String?, data: JSONValue?, extras: [String: JSONValue]?) async throws(InternalError) {
381+
internal func publish(_ name: String?, data: JSONValue?, extras: [String: JSONValue]?) async throws(ErrorInfo) {
382382
do {
383383
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, ARTErrorInfo>, _>) in
384384
underlying.publish(name, data: data?.toAblyCocoaData, extras: extras?.toARTJsonCompatible) { error in
@@ -390,7 +390,7 @@ internal final class InternalRealtimeChannelAdapter<Underlying: ProxyRealtimeCha
390390
}
391391
}.get()
392392
} catch {
393-
throw InternalError.fromAblyCocoa(error)
393+
throw .init(ablyCocoaError: error)
394394
}
395395
}
396396

Sources/AblyChat/ChatAPI.swift

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ internal final class ChatAPI {
2828
}
2929

3030
// (CHA-M6) Messages should be queryable from a paginated REST API.
31-
internal func getMessages(roomName: String, params: HistoryParams) async throws(InternalError) -> some PaginatedResult<Message> {
31+
internal func getMessages(roomName: String, params: HistoryParams) async throws(ErrorInfo) -> some PaginatedResult<Message> {
3232
let endpoint = roomUrl(roomName: roomName, suffix: "/messages")
3333
return try await makePaginatedRequest(endpoint, params: params.asQueryItems())
3434
}
3535

3636
// (CHA-M13) Get a single message by its serial
37-
internal func getMessage(roomName: String, serial: String) async throws(InternalError) -> Message {
37+
internal func getMessage(roomName: String, serial: String) async throws(ErrorInfo) -> Message {
3838
let endpoint = messageUrl(roomName: roomName, serial: serial)
3939
return try await makeRequest(endpoint, method: "GET")
4040
}
@@ -53,14 +53,14 @@ internal final class ChatAPI {
5353
internal struct MessageReactionResponse: JSONObjectDecodable {
5454
internal let serial: String
5555

56-
internal init(jsonObject: [String: JSONValue]) throws(InternalError) {
56+
internal init(jsonObject: [String: JSONValue]) throws(ErrorInfo) {
5757
serial = try jsonObject.stringValueForKey("serial")
5858
}
5959
}
6060

6161
// (CHA-M3) Messages are sent to Ably via the Chat REST API, using the send method.
6262
// (CHA-M3a) When a message is sent successfully, the caller shall receive a struct representing the Message in response (as if it were received via Realtime event).
63-
internal func sendMessage(roomName: String, params: SendMessageParams) async throws(InternalError) -> Message {
63+
internal func sendMessage(roomName: String, params: SendMessageParams) async throws(ErrorInfo) -> Message {
6464
let endpoint = roomUrl(roomName: roomName, suffix: "/messages")
6565
var body: [String: JSONValue] = ["text": .string(params.text)]
6666

@@ -79,7 +79,7 @@ internal final class ChatAPI {
7979

8080
// (CHA-M8) A client must be able to update a message in a room.
8181
// (CHA-M8a) A client may update a message via the Chat REST API by calling the update method.
82-
internal func updateMessage(roomName: String, serial: String, updateParams: UpdateMessageParams, details: OperationDetails?) async throws(InternalError) -> Message {
82+
internal func updateMessage(roomName: String, serial: String, updateParams: UpdateMessageParams, details: OperationDetails?) async throws(ErrorInfo) -> Message {
8383
let endpoint = messageUrl(roomName: roomName, serial: serial)
8484
var body: [String: JSONValue] = [:]
8585

@@ -114,7 +114,7 @@ internal final class ChatAPI {
114114

115115
// (CHA-M9) A client must be able to delete a message in a room.
116116
// (CHA-M9a) A client may delete a message via the Chat REST API by calling the delete method.
117-
internal func deleteMessage(roomName: String, serial: String, details: OperationDetails?) async throws(InternalError) -> Message {
117+
internal func deleteMessage(roomName: String, serial: String, details: OperationDetails?) async throws(ErrorInfo) -> Message {
118118
let endpoint = messageUrl(roomName: roomName, serial: serial, suffix: "/delete")
119119
var body: [String: JSONValue] = [:]
120120

@@ -131,16 +131,16 @@ internal final class ChatAPI {
131131
return try await makeRequest(endpoint, method: "POST", body: .jsonObject(body))
132132
}
133133

134-
internal func getOccupancy(roomName: String) async throws(InternalError) -> OccupancyData {
134+
internal func getOccupancy(roomName: String) async throws(ErrorInfo) -> OccupancyData {
135135
let endpoint = roomUrl(roomName: roomName, suffix: "/occupancy")
136136
return try await makeRequest(endpoint, method: "GET")
137137
}
138138

139139
// (CHA-MR4) Users should be able to send a reaction to a message via the `send` method of the `MessagesReactions` object
140-
internal func sendReactionToMessage(_ messageSerial: String, roomName: String, params: SendMessageReactionParams) async throws(InternalError) -> MessageReactionResponse {
140+
internal func sendReactionToMessage(_ messageSerial: String, roomName: String, params: SendMessageReactionParams) async throws(ErrorInfo) -> MessageReactionResponse {
141141
// (CHA-MR4a1) If the serial passed to this method is invalid: undefined, null, empty string, an error with code 40000 must be thrown.
142142
guard !messageSerial.isEmpty else {
143-
throw ChatError.messageReactionInvalidMessageSerial.toInternalError()
143+
throw ChatError.messageReactionInvalidMessageSerial.toInternalError().toErrorInfo()
144144
}
145145

146146
let endpoint = messageUrl(roomName: roomName, serial: messageSerial, suffix: "/reactions")
@@ -155,10 +155,10 @@ internal final class ChatAPI {
155155
}
156156

157157
// (CHA-MR11) Users should be able to delete a reaction from a message via the `delete` method of the `MessagesReactions` object
158-
internal func deleteReactionFromMessage(_ messageSerial: String, roomName: String, params: DeleteMessageReactionParams) async throws(InternalError) -> MessageReactionResponse {
158+
internal func deleteReactionFromMessage(_ messageSerial: String, roomName: String, params: DeleteMessageReactionParams) async throws(ErrorInfo) -> MessageReactionResponse {
159159
// (CHA-MR11a1) If the serial passed to this method is invalid: undefined, null, empty string, an error with code 40000 must be thrown.
160160
guard !messageSerial.isEmpty else {
161-
throw ChatError.messageReactionInvalidMessageSerial.toInternalError()
161+
throw ChatError.messageReactionInvalidMessageSerial.toInternalError().toErrorInfo()
162162
}
163163

164164
let endpoint = messageUrl(roomName: roomName, serial: messageSerial, suffix: "/reactions")
@@ -172,7 +172,7 @@ internal final class ChatAPI {
172172
}
173173

174174
// CHA-MR13
175-
internal func getClientReactions(forMessageWithSerial messageSerial: String, roomName: String, clientID: String?) async throws(InternalError) -> MessageReactionSummary {
175+
internal func getClientReactions(forMessageWithSerial messageSerial: String, roomName: String, clientID: String?) async throws(ErrorInfo) -> MessageReactionSummary {
176176
// CHA-MR13b
177177
let endpoint = messageUrl(roomName: roomName, serial: messageSerial, suffix: "/client-reactions")
178178

@@ -188,12 +188,12 @@ internal final class ChatAPI {
188188
internal struct MessageReactionSummaryResponse: JSONObjectDecodable {
189189
internal let reactions: MessageReactionSummary
190190

191-
internal init(jsonObject: [String: JSONValue]) throws(InternalError) {
191+
internal init(jsonObject: [String: JSONValue]) throws(ErrorInfo) {
192192
reactions = MessageReactionSummary(values: jsonObject)
193193
}
194194
}
195195

196-
private func makeRequest<Response: JSONDecodable>(_ url: String, method: String, params: [String: String]? = nil, body: RequestBody? = nil) async throws(InternalError) -> Response {
196+
private func makeRequest<Response: JSONDecodable>(_ url: String, method: String, params: [String: String]? = nil, body: RequestBody? = nil) async throws(ErrorInfo) -> Response {
197197
let ablyCocoaBody: Any? = if let body {
198198
switch body {
199199
case let .jsonObject(jsonObject):
@@ -209,7 +209,7 @@ internal final class ChatAPI {
209209
let paginatedResponse = try await realtime.request(method, path: url, params: params, body: ablyCocoaBody, headers: [:])
210210

211211
guard let firstItem = paginatedResponse.items.first else {
212-
throw ChatError.noItemInResponse.toInternalError()
212+
throw ChatError.noItemInResponse.toInternalError().toErrorInfo()
213213
}
214214

215215
let jsonValue = JSONValue(ablyCocoaData: firstItem)
@@ -219,10 +219,10 @@ internal final class ChatAPI {
219219
private func makePaginatedRequest<Response: JSONDecodable & Sendable & Equatable>(
220220
_ url: String,
221221
params: [String: String]? = nil,
222-
) async throws(InternalError) -> some PaginatedResult<Response> {
222+
) async throws(ErrorInfo) -> some PaginatedResult<Response> {
223223
let paginatedResponse = try await realtime.request("GET", path: url, params: params, body: nil, headers: [:])
224224
let jsonValues = paginatedResponse.items.map { JSONValue(ablyCocoaData: $0) }
225-
let items = try jsonValues.map { jsonValue throws(InternalError) in
225+
let items = try jsonValues.map { jsonValue throws(ErrorInfo) in
226226
try Response(jsonValue: jsonValue)
227227
}
228228
return paginatedResponse.toPaginatedResult(items: items)

0 commit comments

Comments
 (0)