Skip to content

Commit

Permalink
Merge pull request #163 from ably/CHA-PR3h-etc
Browse files Browse the repository at this point in the history
[ECO-5144] Implement CHA-PR3h etc
  • Loading branch information
lawrence-forooghian authored Dec 4, 2024
2 parents 6a2b03f + ba4658d commit ed4c028
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 116 deletions.
5 changes: 3 additions & 2 deletions Sources/AblyChat/DefaultMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,11 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities {
case .failed, .suspended:
// TODO: Revisit as part of https://github.com/ably-labs/ably-chat-swift/issues/32
logger.log(message: "Channel failed to attach", level: .error)
let errorCodeCase = ErrorCode.CaseThatImpliesFixedStatusCode.messagesAttachmentFailed
nillableContinuation?.resume(
throwing: ARTErrorInfo.create(
withCode: ErrorCode.messagesAttachmentFailed.rawValue,
status: ErrorCode.messagesAttachmentFailed.statusCode,
withCode: errorCodeCase.toNumericErrorCode.rawValue,
status: errorCodeCase.statusCode,
message: "Channel failed to attach"
)
)
Expand Down
190 changes: 136 additions & 54 deletions Sources/AblyChat/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ public let errorDomain = "AblyChatErrorDomain"
The error codes for errors in the ``errorDomain`` error domain.
*/
public enum ErrorCode: Int {
case nonspecific = 40000

/// ``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:)``.
///
/// TODO this code is a guess, revisit in https://github.com/ably-labs/ably-chat-swift/issues/32
Expand All @@ -36,30 +34,118 @@ public enum ErrorCode: Int {

case roomInInvalidState = 102_107

/// Has a case for each of the ``ErrorCode`` cases that imply a fixed status code.
internal enum CaseThatImpliesFixedStatusCode {
case inconsistentRoomOptions
case messagesAttachmentFailed
case presenceAttachmentFailed
case reactionsAttachmentFailed
case occupancyAttachmentFailed
case typingAttachmentFailed
case messagesDetachmentFailed
case presenceDetachmentFailed
case reactionsDetachmentFailed
case occupancyDetachmentFailed
case typingDetachmentFailed
case roomInFailedState
case roomIsReleasing
case roomIsReleased

internal var toNumericErrorCode: ErrorCode {
switch self {
case .inconsistentRoomOptions:
.inconsistentRoomOptions
case .messagesAttachmentFailed:
.messagesAttachmentFailed
case .presenceAttachmentFailed:
.presenceAttachmentFailed
case .reactionsAttachmentFailed:
.reactionsAttachmentFailed
case .occupancyAttachmentFailed:
.occupancyAttachmentFailed
case .typingAttachmentFailed:
.typingAttachmentFailed
case .messagesDetachmentFailed:
.messagesDetachmentFailed
case .presenceDetachmentFailed:
.presenceDetachmentFailed
case .reactionsDetachmentFailed:
.reactionsDetachmentFailed
case .occupancyDetachmentFailed:
.occupancyDetachmentFailed
case .typingDetachmentFailed:
.typingDetachmentFailed
case .roomInFailedState:
.roomInFailedState
case .roomIsReleasing:
.roomIsReleasing
case .roomIsReleased:
.roomIsReleased
}
}

/// The ``ARTErrorInfo.statusCode`` that should be returned for this error.
internal var statusCode: Int {
// These status codes are taken from the "Chat-specific Error Codes" section of the spec.
switch self {
case .inconsistentRoomOptions,
.roomInFailedState,
.roomIsReleasing,
.roomIsReleased:
400
case
.messagesAttachmentFailed,
.presenceAttachmentFailed,
.reactionsAttachmentFailed,
.occupancyAttachmentFailed,
.typingAttachmentFailed,
.messagesDetachmentFailed,
.presenceDetachmentFailed,
.reactionsDetachmentFailed,
.occupancyDetachmentFailed,
.typingDetachmentFailed:
500
}
}
}

/// Has a case for each of the ``ErrorCode`` cases that do not imply a fixed status code.
internal enum CaseThatImpliesVariableStatusCode {
case roomInInvalidState

internal var toNumericErrorCode: ErrorCode {
switch self {
case .roomInInvalidState:
.roomInInvalidState
}
}
}
}

/**
* Represents a case of ``ErrorCode`` plus a status code.
*/
internal enum ErrorCodeAndStatusCode {
case fixedStatusCode(ErrorCode.CaseThatImpliesFixedStatusCode)
case variableStatusCode(ErrorCode.CaseThatImpliesVariableStatusCode, statusCode: Int)

/// The ``ARTErrorInfo.code`` that should be returned for this error.
internal var code: ErrorCode {
switch self {
case let .fixedStatusCode(code):
code.toNumericErrorCode
case let .variableStatusCode(code, _):
code.toNumericErrorCode
}
}

/// The ``ARTErrorInfo.statusCode`` that should be returned for this error.
internal var statusCode: Int {
// These status codes are taken from the "Chat-specific Error Codes" section of the spec.
switch self {
case .nonspecific,
.inconsistentRoomOptions,
.roomInFailedState,
.roomIsReleasing,
.roomIsReleased:
400
case
.messagesAttachmentFailed,
.presenceAttachmentFailed,
.reactionsAttachmentFailed,
.occupancyAttachmentFailed,
.typingAttachmentFailed,
.messagesDetachmentFailed,
.presenceDetachmentFailed,
.reactionsDetachmentFailed,
.occupancyDetachmentFailed,
.typingDetachmentFailed,
// CHA-RL9c
.roomInInvalidState:
500
case let .fixedStatusCode(code):
code.statusCode
case let .variableStatusCode(_, statusCode):
statusCode
}
}
}
Expand All @@ -77,51 +163,50 @@ internal enum ChatError {
case roomIsReleasing
case roomIsReleased
case presenceOperationRequiresRoomAttach(feature: RoomFeature)
case presenceOperationDisallowedForCurrentRoomStatus(feature: RoomFeature)
case roomInInvalidState(cause: ARTErrorInfo?)
case roomTransitionedToInvalidStateForPresenceOperation(cause: ARTErrorInfo?)

/// The ``ARTErrorInfo.code`` that should be returned for this error.
internal var code: ErrorCode {
internal var codeAndStatusCode: ErrorCodeAndStatusCode {
switch self {
case .inconsistentRoomOptions:
.inconsistentRoomOptions
.fixedStatusCode(.inconsistentRoomOptions)
case let .attachmentFailed(feature, _):
switch feature {
case .messages:
.messagesAttachmentFailed
.fixedStatusCode(.messagesAttachmentFailed)
case .occupancy:
.occupancyAttachmentFailed
.fixedStatusCode(.occupancyAttachmentFailed)
case .presence:
.presenceAttachmentFailed
.fixedStatusCode(.presenceAttachmentFailed)
case .reactions:
.reactionsAttachmentFailed
.fixedStatusCode(.reactionsAttachmentFailed)
case .typing:
.typingAttachmentFailed
.fixedStatusCode(.typingAttachmentFailed)
}
case let .detachmentFailed(feature, _):
switch feature {
case .messages:
.messagesDetachmentFailed
.fixedStatusCode(.messagesDetachmentFailed)
case .occupancy:
.occupancyDetachmentFailed
.fixedStatusCode(.occupancyDetachmentFailed)
case .presence:
.presenceDetachmentFailed
.fixedStatusCode(.presenceDetachmentFailed)
case .reactions:
.reactionsDetachmentFailed
.fixedStatusCode(.reactionsDetachmentFailed)
case .typing:
.typingDetachmentFailed
.fixedStatusCode(.typingDetachmentFailed)
}
case .roomInFailedState:
.roomInFailedState
.fixedStatusCode(.roomInFailedState)
case .roomIsReleasing:
.roomIsReleasing
.fixedStatusCode(.roomIsReleasing)
case .roomIsReleased:
.roomIsReleased
case .roomInInvalidState:
.roomInInvalidState
case .presenceOperationRequiresRoomAttach,
.presenceOperationDisallowedForCurrentRoomStatus:
.nonspecific
.fixedStatusCode(.roomIsReleased)
case .roomTransitionedToInvalidStateForPresenceOperation:
// CHA-RL9c
.variableStatusCode(.roomInInvalidState, statusCode: 500)
case .presenceOperationRequiresRoomAttach:
// CHA-PR3h, CHA-PR10h, CHA-PR6h, CHA-T2g
.variableStatusCode(.roomInInvalidState, statusCode: 400)
}
}

Expand Down Expand Up @@ -177,9 +262,7 @@ internal enum ChatError {
"Cannot perform operation because the room is in a released state."
case let .presenceOperationRequiresRoomAttach(feature):
"To perform this \(Self.descriptionOfFeature(feature)) operation, you must first attach the room."
case let .presenceOperationDisallowedForCurrentRoomStatus(feature):
"This \(Self.descriptionOfFeature(feature)) operation can not be performed given the current room status."
case .roomInInvalidState:
case .roomTransitionedToInvalidStateForPresenceOperation:
"The room operation failed because the room was in an invalid state."
}
}
Expand All @@ -191,14 +274,13 @@ internal enum ChatError {
underlyingError
case let .detachmentFailed(_, underlyingError):
underlyingError
case let .roomInInvalidState(cause):
case let .roomTransitionedToInvalidStateForPresenceOperation(cause):
cause
case .inconsistentRoomOptions,
.roomInFailedState,
.roomIsReleasing,
.roomIsReleased,
.presenceOperationRequiresRoomAttach,
.presenceOperationDisallowedForCurrentRoomStatus:
.presenceOperationRequiresRoomAttach:
nil
}
}
Expand All @@ -208,7 +290,7 @@ internal extension ARTErrorInfo {
convenience init(chatError: ChatError) {
var userInfo: [String: Any] = [:]
// TODO: copied and pasted from implementation of -[ARTErrorInfo createWithCode:status:message:requestId:] because there’s no way to pass domain; revisit in https://github.com/ably-labs/ably-chat-swift/issues/32. Also the ARTErrorInfoStatusCode variable in ably-cocoa is not public.
userInfo["ARTErrorInfoStatusCode"] = chatError.code.statusCode
userInfo["ARTErrorInfoStatusCode"] = chatError.codeAndStatusCode.statusCode
userInfo[NSLocalizedDescriptionKey] = chatError.localizedDescription

// TODO: This is kind of an implementation detail (that NSUnderlyingErrorKey is what populates `cause`); consider documenting in ably-cocoa as part of https://github.com/ably-labs/ably-chat-swift/issues/32.
Expand All @@ -218,7 +300,7 @@ internal extension ARTErrorInfo {

self.init(
domain: errorDomain,
code: chatError.code.rawValue,
code: chatError.codeAndStatusCode.code.rawValue,
userInfo: userInfo
)
}
Expand Down
9 changes: 4 additions & 5 deletions Sources/AblyChat/RoomFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,11 @@ internal protocol FeatureChannel: Sendable, EmitsDiscontinuities {

/// Waits until we can perform presence operations on the contributors of this room without triggering an implicit attach.
///
/// 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:
/// 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:
///
/// - 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:)``.
/// - CHA-PR3e, CHA-PR11e, CHA-PR6d, CHA-T2d: If the room is in the ATTACHED status, it returns immediately.
/// - CHA-PR3f, CHA-PR11f, CHA-PR6e, CHA-T2e: If the room is in the DETACHED status, it throws an `ARTErrorInfo` derived from ``ChatError.presenceOperationRequiresRoomAttach(feature:)``.
/// - // CHA-PR3g, CHA-PR11g, CHA-PR6f, CHA-T2f: If the room is in any other status, it throws an `ARTErrorInfo` derived from ``ChatError.presenceOperationDisallowedForCurrentRoomStatus(feature:)``.
/// - 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:)``.
/// - CHA-PR3e, CHA-PR10e, CHA-PR6d, CHA-T2d: If the room is in the ATTACHED status, it returns immediately.
/// - 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:)``.
///
/// - Parameters:
/// - requester: The room feature that wishes to perform a presence operation. This is only used for customising the message of the thrown error.
Expand Down
11 changes: 4 additions & 7 deletions Sources/AblyChat/RoomLifecycleManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1225,17 +1225,14 @@ internal actor DefaultRoomLifecycleManager<Contributor: RoomLifecycleContributor
// 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
if nextRoomStatusChange?.current != .attached {
// CHA-RL9c
throw .init(chatError: .roomInInvalidState(cause: nextRoomStatusChange?.current.error))
throw .init(chatError: .roomTransitionedToInvalidStateForPresenceOperation(cause: nextRoomStatusChange?.current.error))
}
case .attached:
// CHA-PR3e, CHA-PR11e, CHA-PR6d, CHA-T2d
// CHA-PR3e, CHA-PR10e, CHA-PR6d, CHA-T2d
break
case .detached:
// CHA-PR3f, CHA-PR11f, CHA-PR6e, CHA-T2e
throw .init(chatError: .presenceOperationRequiresRoomAttach(feature: requester))
default:
// CHA-PR3g, CHA-PR11g, CHA-PR6f, CHA-T2f
throw .init(chatError: .presenceOperationDisallowedForCurrentRoomStatus(feature: requester))
// CHA-PR3h, CHA-PR10h, CHA-PR6h, CHA-T2g
throw .init(chatError: .presenceOperationRequiresRoomAttach(feature: requester))
}
}

Expand Down
Loading

0 comments on commit ed4c028

Please sign in to comment.