Skip to content

Commit ba4658d

Browse files
Implement CHA-PR3h and similar
Resolves #151.
1 parent 89677f6 commit ba4658d

File tree

4 files changed

+34
-72
lines changed

4 files changed

+34
-72
lines changed

Sources/AblyChat/Errors.swift

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ public let errorDomain = "AblyChatErrorDomain"
1111
The error codes for errors in the ``errorDomain`` error domain.
1212
*/
1313
public enum ErrorCode: Int {
14-
case nonspecific = 40000
15-
1614
/// ``Rooms.get(roomID:options:)`` was called with a different set of room options than was used on a previous call. You must first release the existing room instance using ``Rooms.release(roomID:)``.
1715
///
1816
/// TODO this code is a guess, revisit in https://github.com/ably-labs/ably-chat-swift/issues/32
@@ -38,7 +36,6 @@ public enum ErrorCode: Int {
3836

3937
/// Has a case for each of the ``ErrorCode`` cases that imply a fixed status code.
4038
internal enum CaseThatImpliesFixedStatusCode {
41-
case nonspecific
4239
case inconsistentRoomOptions
4340
case messagesAttachmentFailed
4441
case presenceAttachmentFailed
@@ -53,12 +50,9 @@ public enum ErrorCode: Int {
5350
case roomInFailedState
5451
case roomIsReleasing
5552
case roomIsReleased
56-
case roomInInvalidState
5753

5854
internal var toNumericErrorCode: ErrorCode {
5955
switch self {
60-
case .nonspecific:
61-
.nonspecific
6256
case .inconsistentRoomOptions:
6357
.inconsistentRoomOptions
6458
case .messagesAttachmentFailed:
@@ -87,17 +81,14 @@ public enum ErrorCode: Int {
8781
.roomIsReleasing
8882
case .roomIsReleased:
8983
.roomIsReleased
90-
case .roomInInvalidState:
91-
.roomInInvalidState
9284
}
9385
}
9486

9587
/// The ``ARTErrorInfo.statusCode`` that should be returned for this error.
9688
internal var statusCode: Int {
9789
// These status codes are taken from the "Chat-specific Error Codes" section of the spec.
9890
switch self {
99-
case .nonspecific,
100-
.inconsistentRoomOptions,
91+
case .inconsistentRoomOptions,
10192
.roomInFailedState,
10293
.roomIsReleasing,
10394
.roomIsReleased:
@@ -112,18 +103,21 @@ public enum ErrorCode: Int {
112103
.presenceDetachmentFailed,
113104
.reactionsDetachmentFailed,
114105
.occupancyDetachmentFailed,
115-
.typingDetachmentFailed,
116-
// CHA-RL9c
117-
.roomInInvalidState:
106+
.typingDetachmentFailed:
118107
500
119108
}
120109
}
121110
}
122111

123112
/// Has a case for each of the ``ErrorCode`` cases that do not imply a fixed status code.
124113
internal enum CaseThatImpliesVariableStatusCode {
114+
case roomInInvalidState
115+
125116
internal var toNumericErrorCode: ErrorCode {
126-
switch self {}
117+
switch self {
118+
case .roomInInvalidState:
119+
.roomInInvalidState
120+
}
127121
}
128122
}
129123
}
@@ -169,8 +163,7 @@ internal enum ChatError {
169163
case roomIsReleasing
170164
case roomIsReleased
171165
case presenceOperationRequiresRoomAttach(feature: RoomFeature)
172-
case presenceOperationDisallowedForCurrentRoomStatus(feature: RoomFeature)
173-
case roomInInvalidState(cause: ARTErrorInfo?)
166+
case roomTransitionedToInvalidStateForPresenceOperation(cause: ARTErrorInfo?)
174167

175168
internal var codeAndStatusCode: ErrorCodeAndStatusCode {
176169
switch self {
@@ -208,11 +201,12 @@ internal enum ChatError {
208201
.fixedStatusCode(.roomIsReleasing)
209202
case .roomIsReleased:
210203
.fixedStatusCode(.roomIsReleased)
211-
case .roomInInvalidState:
212-
.fixedStatusCode(.roomInInvalidState)
213-
case .presenceOperationRequiresRoomAttach,
214-
.presenceOperationDisallowedForCurrentRoomStatus:
215-
.fixedStatusCode(.nonspecific)
204+
case .roomTransitionedToInvalidStateForPresenceOperation:
205+
// CHA-RL9c
206+
.variableStatusCode(.roomInInvalidState, statusCode: 500)
207+
case .presenceOperationRequiresRoomAttach:
208+
// CHA-PR3h, CHA-PR10h, CHA-PR6h, CHA-T2g
209+
.variableStatusCode(.roomInInvalidState, statusCode: 400)
216210
}
217211
}
218212

@@ -268,9 +262,7 @@ internal enum ChatError {
268262
"Cannot perform operation because the room is in a released state."
269263
case let .presenceOperationRequiresRoomAttach(feature):
270264
"To perform this \(Self.descriptionOfFeature(feature)) operation, you must first attach the room."
271-
case let .presenceOperationDisallowedForCurrentRoomStatus(feature):
272-
"This \(Self.descriptionOfFeature(feature)) operation can not be performed given the current room status."
273-
case .roomInInvalidState:
265+
case .roomTransitionedToInvalidStateForPresenceOperation:
274266
"The room operation failed because the room was in an invalid state."
275267
}
276268
}
@@ -282,14 +274,13 @@ internal enum ChatError {
282274
underlyingError
283275
case let .detachmentFailed(_, underlyingError):
284276
underlyingError
285-
case let .roomInInvalidState(cause):
277+
case let .roomTransitionedToInvalidStateForPresenceOperation(cause):
286278
cause
287279
case .inconsistentRoomOptions,
288280
.roomInFailedState,
289281
.roomIsReleasing,
290282
.roomIsReleased,
291-
.presenceOperationRequiresRoomAttach,
292-
.presenceOperationDisallowedForCurrentRoomStatus:
283+
.presenceOperationRequiresRoomAttach:
293284
nil
294285
}
295286
}

Sources/AblyChat/RoomFeature.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,11 @@ internal protocol FeatureChannel: Sendable, EmitsDiscontinuities {
4141

4242
/// Waits until we can perform presence operations on the contributors of this room without triggering an implicit attach.
4343
///
44-
/// Implements the checks described by CHA-PR3d, CHA-PR3e, CHA-PR3f, and CHA-PR3g (and similar ones described by other functionality that performs contributor presence operations). Namely:
44+
/// Implements the checks described by CHA-PR3d, CHA-PR3e, and CHA-PR3h (and similar ones described by other functionality that performs contributor presence operations). Namely:
4545
///
46-
/// - CHA-RL9, which is invoked by CHA-PR3d, CHA-PR10d, CHA-PR6c, CHA-T2c: If the room is in the ATTACHING status, it waits for the next room status change. If the new status is ATTACHED, it returns. Else, it throws an `ARTErrorInfo` derived from ``ChatError.roomInInvalidState(cause:)``.
46+
/// - CHA-RL9, which is invoked by CHA-PR3d, CHA-PR10d, CHA-PR6c, CHA-T2c: If the room is in the ATTACHING status, it waits for the next room status change. If the new status is ATTACHED, it returns. Else, it throws an `ARTErrorInfo` derived from ``ChatError.roomTransitionedToInvalidStateForPresenceOperation(cause:)``.
4747
/// - CHA-PR3e, CHA-PR10e, CHA-PR6d, CHA-T2d: If the room is in the ATTACHED status, it returns immediately.
48-
/// - CHA-PR3f, CHA-PR10f, CHA-PR6e, CHA-T2e: If the room is in the DETACHED status, it throws an `ARTErrorInfo` derived from ``ChatError.presenceOperationRequiresRoomAttach(feature:)``.
49-
/// - // CHA-PR3g, CHA-PR10g, CHA-PR6f, CHA-T2f: If the room is in any other status, it throws an `ARTErrorInfo` derived from ``ChatError.presenceOperationDisallowedForCurrentRoomStatus(feature:)``.
48+
/// - CHA-PR3h, CHA-PR10h, CHA-PR6h, CHA-T2g: If the room is in any other status, it throws an `ARTErrorInfo` derived from ``ChatError.presenceOperationRequiresRoomAttach(feature:)``.
5049
///
5150
/// - Parameters:
5251
/// - requester: The room feature that wishes to perform a presence operation. This is only used for customising the message of the thrown error.

Sources/AblyChat/RoomLifecycleManager.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,17 +1225,14 @@ internal actor DefaultRoomLifecycleManager<Contributor: RoomLifecycleContributor
12251225
// TODO: decide what to do if nextRoomStatusChange is nil; I believe that this will happen if the current Task is cancelled. For now, will just treat it as an invalid status change. Handle it properly in https://github.com/ably-labs/ably-chat-swift/issues/29
12261226
if nextRoomStatusChange?.current != .attached {
12271227
// CHA-RL9c
1228-
throw .init(chatError: .roomInInvalidState(cause: nextRoomStatusChange?.current.error))
1228+
throw .init(chatError: .roomTransitionedToInvalidStateForPresenceOperation(cause: nextRoomStatusChange?.current.error))
12291229
}
12301230
case .attached:
12311231
// CHA-PR3e, CHA-PR10e, CHA-PR6d, CHA-T2d
12321232
break
1233-
case .detached:
1234-
// CHA-PR3f, CHA-PR10f, CHA-PR6e, CHA-T2e
1235-
throw .init(chatError: .presenceOperationRequiresRoomAttach(feature: requester))
12361233
default:
1237-
// CHA-PR3g, CHA-PR10g, CHA-PR6f, CHA-T2f
1238-
throw .init(chatError: .presenceOperationDisallowedForCurrentRoomStatus(feature: requester))
1234+
// CHA-PR3h, CHA-PR10h, CHA-PR6h, CHA-T2g
1235+
throw .init(chatError: .presenceOperationRequiresRoomAttach(feature: requester))
12391236
}
12401237
}
12411238

Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2087,7 +2087,7 @@ struct DefaultRoomLifecycleManagerTests {
20872087
let contributorAttachError = ARTErrorInfo.createUnknownError() // arbitrary
20882088
contributorAttachOperation.complete(behavior: .completeAndChangeState(.failure(contributorAttachError), newState: .failed))
20892089

2090-
// Then: The call to `waitToBeAbleToPerformPresenceOperations(requestedByFeature:)` fails with a `roomInInvalidState` error, whose cause is the error associated with the room status change
2090+
// Then: The call to `waitToBeAbleToPerformPresenceOperations(requestedByFeature:)` fails with a `roomInInvalidState` error with status code 500, whose cause is the error associated with the room status change
20912091
var caughtError: Error?
20922092
do {
20932093
try await waitToBeAbleToPerformPresenceOperationsResult
@@ -2096,7 +2096,7 @@ struct DefaultRoomLifecycleManagerTests {
20962096
}
20972097

20982098
let expectedCause = ARTErrorInfo(chatError: .attachmentFailed(feature: .messages, underlyingError: contributorAttachError)) // using our knowledge of CHA-RL1h4
2099-
#expect(isChatError(caughtError, withCodeAndStatusCode: .fixedStatusCode(.roomInInvalidState), cause: expectedCause))
2099+
#expect(isChatError(caughtError, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 500), cause: expectedCause))
21002100
}
21012101

21022102
// @specPartial CHA-PR3e - Tests the wait described in the spec point, but not that the feature actually performs this wait nor the side effect. TODO change this to a specOneOf once the feature is implemented
@@ -2115,40 +2115,15 @@ struct DefaultRoomLifecycleManagerTests {
21152115
try await manager.waitToBeAbleToPerformPresenceOperations(requestedByFeature: .messages /* arbitrary */ )
21162116
}
21172117

2118-
// @specPartial CHA-PR3f - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2119-
// @specPartial CHA-PR10f - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2120-
// @specPartial CHA-PR6e - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2121-
// @specPartial CHA-T2e - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2122-
@Test
2123-
func waitToBeAbleToPerformPresenceOperations_whenDetached() async throws {
2124-
// Given: A DefaultRoomLifecycleManager in the DETACHED status
2125-
let manager = await createManager(
2126-
forTestingWhatHappensWhenCurrentlyIn: .detached
2127-
)
2128-
2129-
// (Note: I wanted to use #expect(…, throws:) below, but for some reason it made the compiler _crash_! No idea why. So, gave up on that.)
2130-
2131-
// When: `waitToBeAbleToPerformPresenceOperations(requestedByFeature:)` is called on the lifecycle manager
2132-
var caughtError: ARTErrorInfo?
2133-
do {
2134-
try await manager.waitToBeAbleToPerformPresenceOperations(requestedByFeature: .messages /* arbitrary */ )
2135-
} catch {
2136-
caughtError = error
2137-
}
2138-
2139-
// Then: It throws a presenceOperationRequiresRoomAttach error for that feature (which we just check via its error code, i.e. `.nonspecific`, and its message)
2140-
#expect(isChatError(caughtError, withCodeAndStatusCode: .fixedStatusCode(.nonspecific), message: "To perform this messages operation, you must first attach the room."))
2141-
}
2142-
2143-
// @specPartial CHA-PR3f - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2144-
// @specPartial CHA-PR10f - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2145-
// @specPartial CHA-PR6e - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2146-
// @specPartial CHA-T2e - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2118+
// @specPartial CHA-PR3h - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2119+
// @specPartial CHA-PR10h - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2120+
// @specPartial CHA-PR6h - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
2121+
// @specPartial CHA-T2g - Tests the wait described in the spec point, but not that the feature actually performs this wait. TODO change this to a specOneOf once the feature is implemented
21472122
@Test
21482123
func waitToBeAbleToPerformPresenceOperations_whenAnyOtherStatus() async throws {
2149-
// Given: A DefaultRoomLifecycleManager in a status other than ATTACHING, ATTACHED or DETACHED
2124+
// Given: A DefaultRoomLifecycleManager in a status other than ATTACHING or ATTACHED
21502125
let manager = await createManager(
2151-
forTestingWhatHappensWhenCurrentlyIn: .detaching(detachOperationID: .init()) // arbitrary given the above constraints
2126+
forTestingWhatHappensWhenCurrentlyIn: .detached // arbitrary given the above constraints
21522127
)
21532128

21542129
// (Note: I wanted to use #expect(…, throws:) below, but for some reason it made the compiler _crash_! No idea why. So, gave up on that.)
@@ -2161,7 +2136,7 @@ struct DefaultRoomLifecycleManagerTests {
21612136
caughtError = error
21622137
}
21632138

2164-
// Then: It throws a presenceOperationDisallowedForCurrentRoomStatus error for that feature (which we just check via its error code, i.e. `.nonspecific`, and its message)
2165-
#expect(isChatError(caughtError, withCodeAndStatusCode: .fixedStatusCode(.nonspecific), message: "This messages operation can not be performed given the current room status."))
2139+
// Then: It throws a roomInInvalidState error for that feature, with status code 400, and a message explaining that the room must first be attached
2140+
#expect(isChatError(caughtError, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 400), message: "To perform this messages operation, you must first attach the room."))
21662141
}
21672142
}

0 commit comments

Comments
 (0)