Skip to content

Commit

Permalink
[WIP] Implement room lifecycle spec
Browse files Browse the repository at this point in the history
This is based on [1] at aa7455d. It’s generated some questions, which
I’ve asked on that PR.

I started implementing this as part of #19, before realising that
implementing the spec is not the aim of that task. So, putting this work
on hold until we pick it up again in #28.

So far, only the ATTACH operation is implemented.

[1] ably/specification#200

I have since updated with error names @ bcb7390

other stuff done since first version of this commit:

- start implementing the low-hanging fruit of DETACH

all the infrastructure in place for feature-specific errors

used generic type for contributor so that i can access the mock-specific
properties of the contributor's channel in the tests without having to
create channels and then create contributors from those

wip detachment errors

further with detachment errors

wip trying to see if there aren't 2 status updates

Revert "wip trying to see if there aren't 2 status updates"

This reverts commit 63dd9b9.

Doesn’t work.

implement CHA-RL2h2

wip channel detach retry

a few updates to older bits

implement more of RELEASE

comment

wip

done RELEASE

OK, I believe that this is now based on b4a495e of the spec

some further comments

spec points documentation

based on JS rules [1] @ 8c9ce8b, but decided that:

- if a test doesn't relate to a spec point, it doesn't need any markers
- there should be a way to know that a spec point is tested across
  multiple tests

[1] https://github.com/ably/ably-js/blob/main/CONTRIBUTING.md#tests-alignment-with-the-ably-features-specification
  • Loading branch information
lawrence-forooghian committed Sep 17, 2024
1 parent 9014b0c commit 997dbb8
Show file tree
Hide file tree
Showing 10 changed files with 1,215 additions and 4 deletions.
37 changes: 37 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,43 @@ To check formatting and code quality, run `swift run BuildTool lint`. Run with `
- We should aim to make it easy for consumers of the SDK to be able to mock out the SDK in the tests for their own code. A couple of things that will aid with this:
- Describe the SDK’s functionality via protocols (when doing so would still be sufficiently idiomatic to Swift).
- When defining a `struct` that is emitted by the public API of the library, make sure to define a public memberwise initializer so that users can create one to be emitted by their mocks. (There is no way to make Swift’s autogenerated memberwise initializer public, so you will need to write one yourself. In Xcode, you can do this by clicking at the start of the type declaration and doing Editor → Refactor → Generate Memberwise Initializer.)
- When writing code that implements behaviour specified by the Chat SDK features spec, add a comment that references the identifier of the relevant spec item.

### Testing guidelines

#### Attributing tests to a spec point

When writing a test that relates to a spec point from the Chat SDK features spec, add a comment that contains one of the following tags:

- `@spec <spec-item-id>` — The test case directly tests all the functionality documented in the spec item.
- `@specOneOf(m/n) <spec-item-id>` — The test case is the m<sup>th</sup> of n test cases which, together, test all of the functionality documented in the spec item.
- `@specPartial <spec-item-id>` — The test case tests some, but not all, of the functionality documented in the spec item. This is different to `@specOneOf` in that it implies that the test suite does not fully test this spec item.

`<spec-item-id>` should be a spec item identifier such as `CHA-RL3g`.

Each of the above tags can optionally be followed by a hyphen and a comment which explains how the test relates to the given spec item.

Examples:

```swift
// @spec CHA-EX3f
func test1 { }
```

```swift
// @specOneOf(1/2) CHA-EX2h — Tests the case where the room is FAILED
func test2 { }

// @specOneOf(2/2) CHA-EX2h — Tests the case where the room is SUSPENDED
func test3 { }
```

```swift
// @specIncomplete CHA-EX1h4 - Tests that we retry, but not the retry attempt limit because we’ve not implemented it yet
func test4 { }
```

In [#46](https://github.com/ably-labs/ably-chat-swift/issues/46), we’ll write a script that uses these tags to generate a report about how much of the feature spec we’ve implemented.

## Building for Swift 6

Expand Down
95 changes: 93 additions & 2 deletions Sources/AblyChat/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,25 @@ public enum ErrorCode: Int {
/// TODO this code is a guess, revisit in https://github.com/ably-labs/ably-chat-swift/issues/32
case inconsistentRoomOptions = 1

// TODO: describe, and code is a guess
case channelAttachResultedInSuspended = 2
case channelAttachResultedInFailed = 3

case roomInFailedState = 102_101 // CHA-RL2d
case roomIsReleasing = 102_102 // CHA-RL1b, CHA-RL2b
case roomIsReleased = 102_103 // CHA-RL1c, CHA-RL2c

case messagesDetachmentFailed = 102_050
case presenceDetachmentFailed = 102_051
case reactionsDetachmentFailed = 102_052
case occupancyDetachmentFailed = 102_053
case typingDetachmentFailed = 102_054

/// The ``ARTErrorInfo.statusCode`` that should be returned for this error.
internal var statusCode: Int {
// TODO: These are currently a guess, revisit in https://github.com/ably-labs/ably-chat-swift/issues/32
switch self {
case .inconsistentRoomOptions:
case .inconsistentRoomOptions, .channelAttachResultedInSuspended, .channelAttachResultedInFailed, .roomInFailedState, .roomIsReleasing, .roomIsReleased, .messagesDetachmentFailed, .presenceDetachmentFailed, .reactionsDetachmentFailed, .occupancyDetachmentFailed, .typingDetachmentFailed:
400
}
}
Expand All @@ -29,16 +43,45 @@ public enum ErrorCode: Int {
/**
The errors thrown by the Chat SDK.

This type exists in addition to ``ErrorCode`` to allow us to attach metadata which can be incorporated into the error’s `localizedDescription`.
This type exists in addition to ``ErrorCode`` to allow us to attach metadata which can be incorporated into the error’s `localizedDescription` and `cause`.
*/
internal enum ChatError {
case inconsistentRoomOptions(requested: RoomOptions, existing: RoomOptions)
case channelAttachResultedInSuspended(underlyingError: ARTErrorInfo)
case channelAttachResultedInFailed(underlyingError: ARTErrorInfo)
case roomInFailedState
case roomIsReleasing
case roomIsReleased
case detachmentFailed(feature: RoomFeature, underlyingError: ARTErrorInfo)

/// The ``ARTErrorInfo.code`` that should be returned for this error.
internal var code: ErrorCode {
switch self {
case .inconsistentRoomOptions:
.inconsistentRoomOptions
case .channelAttachResultedInSuspended:
.channelAttachResultedInSuspended
case .channelAttachResultedInFailed:
.channelAttachResultedInFailed
case .roomInFailedState:
.roomInFailedState
case .roomIsReleasing:
.roomIsReleasing
case .roomIsReleased:
.roomIsReleased
case let .detachmentFailed(feature, _):
switch feature {
case .messages:
.messagesDetachmentFailed
case .occupancy:
.occupancyDetachmentFailed
case .presence:
.presenceDetachmentFailed
case .reactions:
.reactionsDetachmentFailed
case .typing:
.typingDetachmentFailed
}
}
}

Expand All @@ -47,6 +90,49 @@ internal enum ChatError {
switch self {
case let .inconsistentRoomOptions(requested, existing):
"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:). Requested options: \(requested), existing options: \(existing)"
case .channelAttachResultedInSuspended:
"TODO"
case .channelAttachResultedInFailed:
"TODO"
case .roomInFailedState:
"Cannot perform operation because the room is in a failed state."
case .roomIsReleasing:
"Cannot perform operation because the room is in a releasing state."
case .roomIsReleased:
"Cannot perform operation because the room is in a released state."
case let .detachmentFailed(feature, _):
{
let description = switch feature {
case .messages:
"messages"
case .occupancy:
"occupancy"
case .presence:
"presence"
case .reactions:
"reactions"
case .typing:
"typing"
}
return "The \(description) feature failed to detach."
}()
}
}

/// The ``ARTErrorInfo.cause`` that should be returned for this error.
internal var cause: ARTErrorInfo? {
switch self {
case let .channelAttachResultedInSuspended(underlyingError):
underlyingError
case let .channelAttachResultedInFailed(underlyingError):
underlyingError
case let .detachmentFailed(_, underlyingError):
underlyingError
case .inconsistentRoomOptions,
.roomInFailedState,
.roomIsReleasing,
.roomIsReleased:
nil
}
}
}
Expand All @@ -58,6 +144,11 @@ internal extension ARTErrorInfo {
userInfo["ARTErrorInfoStatusCode"] = chatError.code.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.
if let cause = chatError.cause {
userInfo[NSUnderlyingErrorKey] = cause
}

self.init(
domain: errorDomain,
code: chatError.code.rawValue,
Expand Down
8 changes: 8 additions & 0 deletions Sources/AblyChat/RoomFeature.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/// The features offered by a chat room.
internal enum RoomFeature {
case messages
case presence
case reactions
case occupancy
case typing
}
Loading

0 comments on commit 997dbb8

Please sign in to comment.