Skip to content

[ECO-5608, ECO-5609] Align error codes and messages with spec#437

Merged
lawrence-forooghian merged 5 commits intomainfrom
426-427-align-codes-and-messages
Oct 21, 2025
Merged

[ECO-5608, ECO-5609] Align error codes and messages with spec#437
lawrence-forooghian merged 5 commits intomainfrom
426-427-align-codes-and-messages

Conversation

@lawrence-forooghian
Copy link
Copy Markdown
Collaborator

@lawrence-forooghian lawrence-forooghian commented Oct 20, 2025

Aligns error codes and messages with ably/specification#394. See commit messages for more details.

We still have some usage of the 40000 BadRequest code for errors not specified by the spec, violating CHA-GP5; will revisit these errors separately in #438.

Resolves #426, resolves #427.

Summary by CodeRabbit

  • Bug Fixes

    • More consistent, spec-aligned error codes and messages across messaging, reactions, presence, subscription resolution, and paginated responses (including invalid message serials and non-200 paginated results).
    • Clearer failure messages for room attach/detach, presence readiness, and header/body decoding.
  • Chores

    • Streamlined internal request/error plumbing and simplified presence-wait API for clearer, more predictable behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Oct 20, 2025

Walkthrough

Refactors internal error handling into a parameterized InternalError, removes ChatError, renames ChatAPI request param urlpath and propagates it, simplifies presence API by removing the feature argument from waitToBeAbleToPerformPresenceOperations(), and updates decoding, pagination, rooms, and tests to match the new errors and signatures.

Changes

Cohort / File(s) Summary
Core error surface
Sources/AblyChat/InternalError.swift, Sources/AblyChat/ErrorInfo.swift
Introduce a spec-aligned, parameterized InternalError (many new cases, ErrorCode mapping, .toErrorInfo()), adjust ErrorInfo.Source to derive code/status from internalError.code.
Chat API & request plumbing
Sources/AblyChat/ChatAPI.swift
Rename request parameter urlpath, propagate path through request and paginated flows, remove ChatError enum, and map Chat API error paths to InternalError (e.g., noItemInResponse(path:), reaction-empty serial cases).
Message, reactions & decoding
Sources/AblyChat/Message.swift, Sources/AblyChat/DefaultMessageReactions.swift, Sources/AblyChat/JSONCodable.swift
Align message/reaction error throws to new InternalError cases (e.g., cannotApplyMessageEventForDifferentMessage, cannotApplyReactionSummaryEventForDifferentMessage, unableDeleteReactionWithoutName). JSONValueDecodingError.failedToDecodeFromRawValue now includes type: Any.Type.
Pagination & response handling
Sources/AblyChat/PaginatedResult.swift
Decouple nil-check and status-code check, remove PaginatedResultError, return InternalError.paginatedResultStatusCode(Int) for non-200 responses, add preconditionFailure for missing paginated response.
Subscription / messages resolution
Sources/AblyChat/DefaultMessages.swift
Replace legacy errors with InternalError variants for subscription-point resolution failures (messages instance gone, attachSerial undefined, channel failed to attach) and update propagation to .toErrorInfo().
Presence / lifecycle API
Sources/AblyChat/DefaultPresence.swift, Sources/AblyChat/RoomLifecycleManager.swift, Sources/AblyChat/DefaultRoomLifecycleManager.swift
Remove requestedByFeature / RoomFeature from waitToBeAbleToPerformPresenceOperations() (now parameterless). Update error construction to operation/state-aware InternalError cases (e.g., roomIsReleasing(operation:), roomInInvalidStateForAttach/Detach, roomTransitionedToInvalidStateForPresenceOperation(newState:cause:), presenceOperationRequiresRoomAttach).
Room management
Sources/AblyChat/Rooms.swift
When existing room options differ, throw InternalError.roomExistsWithDifferentOptions(requested:existing:) converted to ErrorInfo at the throw site.
Feature cleanup
Sources/AblyChat/RoomFeature.swift
Remove RoomFeature enum and drop the Ably import.
Tests & helpers / mocks
Tests/AblyChatTests/*, Tests/AblyChatTests/Helpers/Helpers.swift, Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
Update tests and mocks to match new InternalError cases and signatures (replace ChatError/noItemInResponse, hasCodeAndStatusCodehasCode, remove requestedByFeature argument, update spec tags and expected error codes/messages).

Sequence Diagram(s)

sequenceDiagram
  participant API as ChatAPI
  participant REST as REST layer
  participant Parser as JSONDecoding

  rect rgb(247,250,255)
  note right of API: makeRequest(path: String, ...)
  API->>REST: request(method, path: path, params, body)
  REST-->>API: HTTP response
  API->>Parser: decode response
  alt decode succeeds
    Parser-->>API: Model
  else decode fails
    Parser-->>API: throw JSONValueDecodingError(type:, rawValue:)
  end
  alt missing expected item
    API-->>API: throw InternalError.noItemInResponse(path: path)
  end
  end
Loading
sequenceDiagram
  participant Caller as Feature
  participant Lifecycle as RoomLifecycleManager

  Caller->>Lifecycle: waitToBeAbleToPerformPresenceOperations()
  alt attached or attaching
    Lifecycle-->>Caller: return (ready)
  else not attached
    Lifecycle-->>Caller: throw InternalError.presenceOperationRequiresRoomAttach
  else invalid/releasing/failed
    Lifecycle-->>Caller: throw InternalError.roomTransitionedToInvalidStateForPresenceOperation(newState, cause)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • umair-ably
  • maratal

Poem

🐰
I hopped through enums and nudged a path anew,
Where errors found their spec and the tests hopped too.
I trimmed a flag, made wait calls small,
Decoded types now speak — I cheered at each merge call! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.02% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "[ECO-5608, ECO-5609] Align error codes and messages with spec" accurately and clearly describes the main objective of the changeset. The title directly reflects the core purpose of the PR, which is to align error codes and error messages with specification requirements. A team member scanning the history would immediately understand this is about spec alignment for error handling, making it both specific and informative without being overly verbose.
Linked Issues Check ✅ Passed The code changes comprehensively address the objectives from both linked issues. For issue #426 (ECO-5608), the PR aligns error codes with the specification by refactoring error types in InternalError.swift, updating error constructors throughout the codebase to use spec-aligned variants (e.g., roomInInvalidStateForAttach, sendMessageReactionEmptyMessageSerial, failedToResolveSubscriptionPointBecauseMessagesInstanceGone), and consolidating error code mappings. For issue #427 (ECO-5609), error messages are reorganized to follow a consistent "unable to ; " pattern, with updated descriptive helper machinery to align messages with the JavaScript implementation. Both objectives are fully realized in the implementation.
Out of Scope Changes Check ✅ Passed All changes in the PR are in-scope and necessary for the error code and message alignment objectives. The removal of the RoomFeature enum and the simplification of waitToBeAbleToPerformPresenceOperations (removing the requestedByFeature parameter) are integral to the error type refactoring, as these changes eliminate unnecessary context parameters that were previously used in error construction but are no longer needed under the spec-aligned error handling. The updates to InternalError variants, error message formatting, and all call sites are cohesive and directly support achieving the stated goals of resolving issues #426 and #427.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 426-427-align-codes-and-messages

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot temporarily deployed to staging/pull/437/AblyChat October 20, 2025 17:07 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the 426-427-align-codes-and-messages branch from 73feb83 to f8a014d Compare October 20, 2025 17:46
@github-actions github-actions bot temporarily deployed to staging/pull/437/AblyChat October 20, 2025 17:47 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the 426-427-align-codes-and-messages branch from f8a014d to 3a2ee83 Compare October 20, 2025 18:44
@lawrence-forooghian lawrence-forooghian marked this pull request as ready for review October 20, 2025 18:45
@github-actions github-actions bot temporarily deployed to staging/pull/437/AblyChat October 20, 2025 18:46 Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/AblyChat/RoomLifecycleManager.swift (1)

591-606: Potential double-resume of continuation in waitToBeAbleToPerformPresenceOperations

If multiple status changes arrive before nextRoomStatusSubscription.off() runs, the closure can resume the same continuation more than once. Move .off() into the callback and guard with a local flag to ensure single resume.

-            var nextRoomStatusSubscription: StatusSubscription!
-            var nextRoomStatusChange: RoomStatusChange!
+            var nextRoomStatusSubscription: StatusSubscription!
+            var nextRoomStatusChange: RoomStatusChange!
+            var didResume = false
             await withCheckedContinuation { (continuation: CheckedContinuation<Void, _>) in
                 self.logger.log(message: "waitToBeAbleToPerformPresenceOperations waiting for status change", level: .debug)
                 #if DEBUG
                     self.statusChangeWaitEventSubscriptions.emit(.init())
                 #endif
                 nextRoomStatusSubscription = self.onRoomStatusChange { [weak self] statusChange in
-                    nextRoomStatusChange = statusChange
-                    self?.logger.log(message: "waitToBeAbleToPerformPresenceOperations got status change \(String(describing: nextRoomStatusChange))", level: .debug)
-                    continuation.resume()
+                    guard !didResume else { return }
+                    didResume = true
+                    nextRoomStatusChange = statusChange
+                    self?.logger.log(message: "waitToBeAbleToPerformPresenceOperations got status change \(String(describing: nextRoomStatusChange))", level: .debug)
+                    nextRoomStatusSubscription.off()
+                    continuation.resume()
                 }
             }
-            nextRoomStatusSubscription.off()

Also applies to: 607-614

🧹 Nitpick comments (2)
Sources/AblyChat/ChatAPI.swift (1)

196-217: Unify naming: prefer “path” consistently

makeRequest now uses _ path: String, but makePaginatedRequest still uses _ url: String. For consistency and clearer semantics with realtime.request(path:), rename the param and call sites.

-    private func makePaginatedRequest<Response: JSONDecodable & Sendable & Equatable>(
-        _ url: String,
+    private func makePaginatedRequest<Response: JSONDecodable & Sendable & Equatable>(
+        _ path: String,
         params: [String: String]? = nil,
     ) async throws(ErrorInfo) -> some PaginatedResult<Response> {
-        let paginatedResponse = try await realtime.request("GET", path: url, params: params, body: nil, headers: [:])
+        let paginatedResponse = try await realtime.request("GET", path: path, params: params, body: nil, headers: [:])
Sources/AblyChat/InternalError.swift (1)

203-224: Minor consistency: use uppercased status in presence-transition message

Other cases use descriptionOfRoomStatus(…) for “ATTACHED/…”. Consider reusing it in roomTransitionedToInvalidStateForPresenceOperation to keep output uniform.

-        case let .roomTransitionedToInvalidStateForPresenceOperation(newState: newState, cause: cause):
+        case let .roomTransitionedToInvalidStateForPresenceOperation(newState: newState, cause: cause):
             failedActionBareInfinitive = "perform presence operation"
-            reason = "room transitioned to invalid state \(newState): \(cause, default: "(nil cause)")"
+            reason = "room transitioned to invalid state \(Self.descriptionOfRoomStatus(newState)): \(cause, default: "(nil cause)")"

Also applies to: 241-329

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c96988e and 3a2ee83.

📒 Files selected for processing (21)
  • Sources/AblyChat/ChatAPI.swift (4 hunks)
  • Sources/AblyChat/DefaultMessageReactions.swift (2 hunks)
  • Sources/AblyChat/DefaultMessages.swift (2 hunks)
  • Sources/AblyChat/DefaultPresence.swift (6 hunks)
  • Sources/AblyChat/ErrorInfo.swift (2 hunks)
  • Sources/AblyChat/InternalError.swift (2 hunks)
  • Sources/AblyChat/JSONCodable.swift (2 hunks)
  • Sources/AblyChat/Message.swift (3 hunks)
  • Sources/AblyChat/PaginatedResult.swift (1 hunks)
  • Sources/AblyChat/RoomFeature.swift (0 hunks)
  • Sources/AblyChat/RoomLifecycleManager.swift (5 hunks)
  • Sources/AblyChat/Rooms.swift (1 hunks)
  • Tests/AblyChatTests/ChatAPITests.swift (1 hunks)
  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift (3 hunks)
  • Tests/AblyChatTests/DefaultPresenceTests.swift (13 hunks)
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (10 hunks)
  • Tests/AblyChatTests/DefaultRoomsTests.swift (3 hunks)
  • Tests/AblyChatTests/ErrorInfoTests.swift (1 hunks)
  • Tests/AblyChatTests/Helpers/Helpers.swift (2 hunks)
  • Tests/AblyChatTests/MessageTests.swift (6 hunks)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1 hunks)
💤 Files with no reviewable changes (1)
  • Sources/AblyChat/RoomFeature.swift
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: Use protocol-based design; expose SDK functionality via protocols and prefer associated types with opaque return types (some Protocol) instead of existentials (any Protocol)
Isolate all mutable state to the main actor; mark stateful objects with @mainactor
Public API must use typed throws with ErrorInfo; use InternalError internally and convert at the public API boundary
For public structs emitted by the API, provide an explicit public memberwise initializer
When using AsyncSequence operators in @mainactor contexts, mark operator closures as @sendable
Task, CheckedContinuation, and AsyncThrowingStream do not support typed errors; use Result and call .get() to surface typed errors
Do not use Dictionary.mapValues for typed throws; use ablyChat_mapValuesWithTypedThrow instead
When the compiler struggles with typed throws, explicitly declare the error type on do blocks (e.g., do throws(InternalError))
Specify error types in closures using the throws(ErrorType) syntax (e.g., try items.map { jsonValue throws(InternalError) in ... })
Mark any test-only APIs with testsOnly_ prefix and wrap them in #if DEBUG

Files:

  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift
  • Sources/AblyChat/DefaultMessageReactions.swift
  • Sources/AblyChat/DefaultPresence.swift
  • Sources/AblyChat/PaginatedResult.swift
  • Sources/AblyChat/ErrorInfo.swift
  • Sources/AblyChat/Rooms.swift
  • Tests/AblyChatTests/ErrorInfoTests.swift
  • Sources/AblyChat/Message.swift
  • Sources/AblyChat/JSONCodable.swift
  • Tests/AblyChatTests/MessageTests.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Sources/AblyChat/ChatAPI.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Sources/AblyChat/RoomLifecycleManager.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
  • Sources/AblyChat/DefaultMessages.swift
  • Sources/AblyChat/InternalError.swift
Tests/AblyChatTests/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Tests/AblyChatTests/**/*.swift: Use test attribution tags to reference spec coverage: @SPEC, @specOneOf(m/n), @specPartial, @specUntested, @specNotApplicable
For Swift Testing #expect(throws:) with typed errors, move typed-throw code into a separate non-typed-throw function until Xcode 16.3+

Files:

  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift
  • Tests/AblyChatTests/ErrorInfoTests.swift
  • Tests/AblyChatTests/MessageTests.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
Tests/AblyChatTests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place tests under Tests/AblyChatTests/ as the root for test targets

Files:

  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift
  • Tests/AblyChatTests/ErrorInfoTests.swift
  • Tests/AblyChatTests/MessageTests.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
Tests/AblyChatTests/Mocks/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Put mock implementations under Tests/AblyChatTests/Mocks/

Files:

  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
🧬 Code graph analysis (14)
Tests/AblyChatTests/DefaultMessageReactionsTests.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Sources/AblyChat/DefaultPresence.swift (2)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Sources/AblyChat/PaginatedResult.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/ErrorInfoTests.swift (2)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Sources/AblyChat/Message.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Sources/AblyChat/JSONCodable.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (2)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • addRecord (114-118)
Tests/AblyChatTests/DefaultRoomsTests.swift (2)
Sources/AblyChat/Rooms.swift (2)
  • get (60-63)
  • get (164-269)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Sources/AblyChat/ChatAPI.swift (3)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Mocks/MockRealtime.swift (1)
  • request (29-45)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (1)
  • request (148-169)
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (4)
Sources/AblyChat/RoomLifecycleManager.swift (6)
  • performAttachOperation (370-372)
  • performAttachOperation (374-376)
  • performDetachOperation (433-435)
  • performDetachOperation (437-439)
  • testsOnly_subscribeToStatusChangeWaitEvents (634-636)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (3)
  • performAttachOperation (25-31)
  • performDetachOperation (33-39)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Tests/AblyChatTests/Helpers/RoomLifecycleManager+AsyncSequence.swift (1)
  • testsOnly_subscribeToStatusChangeWaitEvents (60-74)
Sources/AblyChat/RoomLifecycleManager.swift (2)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/DefaultPresenceTests.swift (2)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Helpers/Helpers.swift (2)
  • hasCode (10-26)
  • hasRecord (120-131)
Sources/AblyChat/DefaultMessages.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Sources/AblyChat/InternalError.swift (3)
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (2)
  • attach (94-102)
  • detach (108-116)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (2)
  • attach (351-365)
  • detach (367-381)
Sources/AblyChat/Room.swift (2)
  • attach (377-379)
  • detach (382-384)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Example app, iOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 26.0)
  • GitHub Check: Example app, tvOS (Xcode 26.0)
  • GitHub Check: Example app, macOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 26.0)
  • GitHub Check: Xcode, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 26.0)
  • GitHub Check: Xcode, macOS (Xcode 26.0)
  • GitHub Check: Xcode, iOS (Xcode 26.0)
  • GitHub Check: SPM, release configuration (Xcode 26.0)
🔇 Additional comments (58)
Sources/AblyChat/ErrorInfo.swift (1)

26-35: LGTM! Clean simplification of internal error property access.

The updated property paths (internalError.code.rawValue and internalError.code.statusCode) align with the new internal error model and improve code clarity while maintaining the public API surface.

Also applies to: 70-79

Sources/AblyChat/DefaultMessageReactions.swift (2)

40-41: Good addition of spec reference.

The CHA-MR11b1 comment improves traceability to the specification for the non-unique reaction validation.


98-100: Good addition of spec reference.

The CHA-MR7c comment documents the validation requirement, though the implementation choice of fatalError for programmer errors is appropriate for Swift.

Sources/AblyChat/Rooms.swift (1)

169-175: Improved error context for room options mismatch.

The updated error roomExistsWithDifferentOptions(requested:existing:) provides much better debugging information by including both the requested and existing options, compared to the previous generic inconsistentRoomOptions.

Tests/AblyChatTests/ChatAPITests.swift (1)

24-29: Test correctly updated for simplified error hierarchy.

The pattern match now directly checks for .noItemInResponse without nested wrappers, aligning with the flattened internal error model.

Tests/AblyChatTests/Helpers/Helpers.swift (2)

4-26: Test helper correctly updated for new error model.

The renamed hasCode(_:cause:message:) function (previously hasCodeAndStatusCode) simplifies the test API while maintaining the implicit statusCode validation mentioned in the documentation.


29-44: LGTM! Enum case helper updated appropriately.

The enumCase property updated to handle the new jsonValueDecodingError case pattern.

Sources/AblyChat/DefaultPresence.swift (1)

23-23: Consistent removal of requestedByFeature parameter.

All six presence operation call sites correctly updated to use the parameterless waitToBeAbleToPerformPresenceOperations() API, aligning with the protocol signature change shown in the relevant code snippets.

Also applies to: 44-44, 66-66, 97-97, 125-125, 153-153

Tests/AblyChatTests/MessageTests.swift (2)

9-9: Test annotations correctly updated to align with spec changes.

The spec reference updates (CHA-M11a/b/e → CHA-M11h/i/j) reflect the specification restructuring mentioned in the PR objectives.

Also applies to: 63-63, 353-353, 393-393


59-59: Error codes correctly updated from generic to specific.

All three test assertions consistently updated from error code 40000 (generic BadRequest) to 40003, aligning with the specification's more specific error categorization for invalid arguments.

Also applies to: 112-112, 434-434

Tests/AblyChatTests/ErrorInfoTests.swift (1)

51-62: Test correctly updated for new internal error structure.

The test now exercises the updated error conversion pathway using headersValueJSONDecodingError instead of the removed ChatError wrapper, and validates the standardized error message format. The spec annotation (CHA-GP6) is appropriately added.

Tests/AblyChatTests/DefaultMessageReactionsTests.swift (2)

61-78: LGTM: Spec reference and error assertion updated correctly.

The spec reference update from CHA-MR4a1 to CHA-MR4a2 and the new specific error case InternalError.sendMessageReactionEmptyMessageSerial improve clarity and align with the specification changes.


81-98: LGTM: Spec reference and error assertion updated correctly.

The spec reference update from CHA-MR11a1 to CHA-MR11a2 and the new specific error case InternalError.deleteMessageReactionEmptyMessageSerial maintain consistency with the send operation changes.

Sources/AblyChat/JSONCodable.swift (2)

33-33: LGTM: Enhanced error with type information.

Adding the type parameter to the error case improves error diagnostics by including the expected type in the error message.


296-296: LGTM: Error throw updated to include type information.

The updated throw site now provides both the expected type and the raw value, which will significantly improve error messages when decoding fails.

Sources/AblyChat/PaginatedResult.swift (1)

42-49: LGTM: Improved error handling with clearer separation of concerns.

The split validation (nil check with preconditionFailure, then status code check) is clearer than the previous approach. Using preconditionFailure for nil response is appropriate since it indicates a programming error in the SDK (ARTHTTPPaginatedResponse should always provide either an error or a response).

The new InternalError.paginatedResultStatusCode error is more specific and aligns with the standardized error model.

Tests/AblyChatTests/DefaultRoomsTests.swift (3)

174-180: LGTM: Updated to use more specific error code.

The change from a generic badRequest to the specific roomExistsWithDifferentOptions error code improves error clarity and aligns with the specification.


215-221: LGTM: Consistent error code update.

This test correctly uses the same roomExistsWithDifferentOptions error code, maintaining consistency with the first test case.


373-373: LGTM: Simplified error assertion.

The change from hasCodeAndStatusCode to hasCode reflects the updated error checking API while maintaining the same validation logic.

Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)

60-68: LGTM: API simplified by removing feature parameter.

Removing the requestedByFeature parameter simplifies the presence operation API. The mock implementation correctly records the call with no arguments.

Sources/AblyChat/DefaultMessages.swift (2)

100-100: LGTM: More descriptive error for subscription point resolution.

The new InternalError.failedToResolveSubscriptionPointBecauseMessagesInstanceGone is more descriptive than the generic MessagesError.noReferenceToSelf and follows a consistent naming pattern.


162-167: LGTM: Consistent error naming for subscription point failures.

Both errors (failedToResolveSubscriptionPointBecauseAttachSerialNotDefined and failedToResolveSubscriptionPointBecauseChannelFailedToAttach) follow the established naming pattern and clearly describe the failure reason.

Sources/AblyChat/Message.swift (3)

181-181: LGTM: Enhanced error diagnostics with type information.

Adding the type: ChatMessageAction.self parameter provides better error messages when decoding fails, consistent with the changes in JSONCodable.swift.


227-227: LGTM: More specific error for reaction summary events.

The new cannotApplyReactionSummaryEventForDifferentMessage error is more specific than the previous generic error, making it easier to distinguish between different event application failures.


248-253: LGTM: Distinct errors for different message event scenarios.

Using separate error cases (cannotApplyCreatedMessageEvent and cannotApplyMessageEventForDifferentMessage) improves error clarity and makes debugging easier by immediately identifying which validation failed.

Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (4)

100-118: LGTM: Consolidated error code with updated spec references.

The consolidation to roomInInvalidState (from the now-obsolete roomIsReleasing) simplifies the error model. The spec reference update to CHA-RL1l reflects the latest specification.


283-317: LGTM: Consistent error code updates for detach operations.

All detach operation tests correctly use roomInInvalidState with updated spec references (CHA-RL2l, CHA-RL2m), maintaining consistency across the test suite.


937-940: LGTM: Simplified discontinuity error assertion.

The updated hasCode method with roomDiscontinuity and cause parameter correctly validates the discontinuity error structure.


956-1069: LGTM: Presence operation tests updated to parameterless API.

All four presence operation tests correctly updated to:

  1. Use waitToBeAbleToPerformPresenceOperations() without the requestedByFeature parameter
  2. Use the simplified roomInInvalidState error code
  3. Include clear error messages explaining required room status

The changes are thorough and consistent across all test scenarios (attaching→attached, attaching→failed, attached, and other statuses).

Tests/AblyChatTests/DefaultPresenceTests.swift (15)

77-79: Updated wait helper invocation looks correct

Signature and empty-args expectation align with the parameterless API.


87-88: Good: presence ATTACHING → invalid transition mapped via InternalError

Constructing roomTransitionedToInvalidStateForPresenceOperation(...) then toErrorInfo() matches the new flow.


105-105: Asserting code and cause is spot on

Validates .roomInInvalidState and preserves the underlying attachError.


109-111: Wait helper recorded as expected

Mock call signature matches "waitToBeAbleToPerformPresenceOperations()".


118-118: Correct error for not-attached presence op

presenceOperationRequiresRoomAttach is the intended precondition failure.


134-134: Right code asserted

.roomInInvalidState matches mapping for the precondition failure case.


184-186: Update presence while attaching: wait call verified

Matches new API and test intent.


194-195: Invalid transition error wiring LGTM

Mirrors the enter() failure path.


212-212: Cause propagation asserted correctly

Keeps attachError as cause.


216-218: Wait helper invocation recorded

Consistent with other tests.


225-225: Precondition error on update presence

Correct use of presenceOperationRequiresRoomAttach.


241-241: Right code for invalid state

Matches mapping.


330-330: Precondition error for presence get()

Correct error source.


346-346: Code assertion LGTM

.roomInInvalidState is expected.


369-371: Wait helper check again OK

Signature and args match mocks.

Sources/AblyChat/ChatAPI.swift (3)

141-145: Correct: empty serial → InvalidArgument via InternalError

Guard + sendMessageReactionEmptyMessageSerial.toErrorInfo() aligns with spec text.


159-163: Delete reaction empty serial validation is correct

Maps to deleteMessageReactionEmptyMessageSerial.toErrorInfo().


209-213: Nice: empty paginated response → InternalError.noItemInResponse

Clearer diagnostics than a generic decode failure.

Sources/AblyChat/RoomLifecycleManager.swift (4)

395-399: Attach invalid-state mapping looks correct

.roomIsReleasing(.attach) and .roomInInvalidStateForAttach(roomStatus) align with the new error taxonomy.


458-462: Detach invalid-state mapping looks correct

.roomIsReleasing(.detach) and .roomInInvalidStateForDetach(roomStatus) as per spec points.


609-614: Error construction and mapping LGTM

Throws roomTransitionedToInvalidStateForPresenceOperation(...).toErrorInfo() and presenceOperationRequiresRoomAttach.toErrorInfo() per CHA-RL9c/PR3h.

Also applies to: 620-621


625-636: DEBUG test hooks are clear

testsOnly_subscribeToStatusChangeWaitEvents and event emission are consistent with other DEBUG hooks.

Also applies to: 633-636

Sources/AblyChat/InternalError.swift (7)

9-20: Attach/Detach invalid-state cases are a good split

roomInInvalidStateForAttach/Detach improves diagnostics and mapping.


36-45: Reactions: empty serial mapped to InvalidArgument

send/delete variants separate the contexts cleanly.

Also applies to: 41-45


51-55: roomIsReleasing folded into roomInInvalidState code — OK

Comment explains spec change; mapping kept consistent via code property.


66-75: Presence errors and messages consistent

Code maps to .roomInInvalidState; message follows “unable to …; …” format.

Also applies to: 170-176, 266-272


154-201: code mapping switch reads well

Each InternalError maps deterministically to an ErrorCode.


376-386: Convenience conversions for decoding errors LGTM

headersValueJSONDecodingError/jsonValueDecodingError mapping is straightforward.

Also applies to: 382-385


127-152: I'll search GitHub for the Ably specification repository to find the authoritative error code mappings.

I need to search more specifically for the Chat specification and PR 394:

I found Chat error codes in the 102000 range in ably-common's errors.json, but the specific codes referenced (102100, 102106, 102107, 102112) and PR 394 are not appearing in search results. Let me perform one final search to verify these specific codes:

Based on my web search attempts, I've gathered the following information:

Findings:

Chat error codes are documented in ably-common's errors.json, with codes in the 102000 range including codes like 102000-102005 and 102050-102054. However, the specific error codes in your review (102100, 102106, 102107, 102112) do not appear in publicly available search results.

Additionally, I was unable to locate any public reference to "PR 394" in the Ably Chat or specification repositories.

Verification Status:

  • ✅ ably-common repository exists and contains shared error codes
  • ✅ ably/specification repository is the authoritative source for SDK development guidance
  • ❌ The specific Chat error codes (102100, 102106, 102107, 102112) cannot be verified through public sources
  • ❌ PR 394 reference cannot be located publicly

Conclusion:

The error code values and their HTTP status mappings appear to be from an internal or non-public Chat specification. Without access to the authoritative Ably Chat specification or PR 394, I cannot definitively confirm whether these mappings are correct or outdated.


Verify error code constants and HTTP status mappings against the Chat specification

The specific error codes (102100, 102106, 102107, 102112) and PR 394 referenced in this review cannot be confirmed through public Ably repositories or documentation. Please verify the mappings directly against the internal Ably Chat specification to ensure they remain current.

Comment thread Sources/AblyChat/InternalError.swift Outdated
Comment thread Sources/AblyChat/InternalError.swift
Comment thread Sources/AblyChat/RoomLifecycleManager.swift Outdated
@lawrence-forooghian lawrence-forooghian force-pushed the 426-427-align-codes-and-messages branch from 3a2ee83 to a8d06d6 Compare October 20, 2025 20:12
@github-actions github-actions bot temporarily deployed to staging/pull/437/AblyChat October 20, 2025 20:13 Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/AblyChat/RoomLifecycleManager.swift (1)

591-606: Avoid potential double-resume in withCheckedContinuation by unsubscribing inside the callback before resuming.

Unsubscribe inside the handler, then remove the post‑continuation off().

-            await withCheckedContinuation { (continuation: CheckedContinuation<Void, _>) in
+            await withCheckedContinuation { (continuation: CheckedContinuation<Void, _>) in
                 self.logger.log(message: "waitToBeAbleToPerformPresenceOperations waiting for status change", level: .debug)
                 #if DEBUG
                     self.statusChangeWaitEventSubscriptions.emit(.init())
                 #endif
                 nextRoomStatusSubscription = self.onRoomStatusChange { [weak self] statusChange in
                     nextRoomStatusChange = statusChange
                     self?.logger.log(message: "waitToBeAbleToPerformPresenceOperations got status change \(String(describing: nextRoomStatusChange))", level: .debug)
+                    nextRoomStatusSubscription.off()
                     continuation.resume()
                 }
             }
-            nextRoomStatusSubscription.off()
♻️ Duplicate comments (1)
Sources/AblyChat/InternalError.swift (1)

271-275: Non-standard string interpolation will not compile without a custom helper. Replace with standard Optional formatting.

Use cause.map(String.init(describing:)) ?? "(nil cause)".

-            reason = "room transitioned to invalid state \(newState): \(cause, default: "(nil cause)")"
+            reason = "room transitioned to invalid state \(newState): \(cause.map(String.init(describing:)) ?? "(nil cause)")"
@@
-            reason = "room experienced a discontinuity: \(cause, default: "(nil cause)")"
+            reason = "room experienced a discontinuity: \(cause.map(String.init(describing:)) ?? "(nil cause)")"
@@
-            reason = "channel failed to attach: \(cause, default: "(nil cause)")"
+            reason = "channel failed to attach: \(cause.map(String.init(describing:)) ?? "(nil cause)")"

Also applies to: 295-295

🧹 Nitpick comments (4)
Sources/AblyChat/ChatAPI.swift (2)

159-162: Delete reaction: empty-serial check OK; add pre-validation for missing name when required.

Keep the empty-serial error. Additionally, enforce name presence for reaction types that require it so callers get a deterministic SDK error before the request.

 internal func deleteReactionFromMessage(_ messageSerial: String, roomName: String, params: DeleteMessageReactionParams) async throws(ErrorInfo) -> MessageReactionResponse {
   // ...
-  var httpParams: [String: String] = [
+  // If the reaction type requires a name, validate early.
+  if params.type != .unique, params.name == nil {
+      throw InternalError.unableDeleteReactionWithoutName(reactionType: params.type.rawValue).toErrorInfo()
+  }
+
+  var httpParams: [String: String] = [
       "type": params.type.rawValue,
   ]
   httpParams["name"] = params.name
   return try await makeRequest(endpoint, method: "DELETE", params: httpParams)
 }

Also applies to: 166-171


219-229: Rename _ url_ path for consistency with makeRequest and realtime.request(path:).

Purely internal but avoids confusion.

-    private func makePaginatedRequest<Response: JSONDecodable & Sendable & Equatable>(
-        _ url: String,
+    private func makePaginatedRequest<Response: JSONDecodable & Sendable & Equatable>(
+        _ path: String,
         params: [String: String]? = nil,
     ) async throws(ErrorInfo) -> some PaginatedResult<Response> {
-        let paginatedResponse = try await realtime.request("GET", path: url, params: params, body: nil, headers: [:])
+        let paginatedResponse = try await realtime.request("GET", path: path, params: params, body: nil, headers: [:])
         let jsonValues = paginatedResponse.items.map { JSONValue(ablyCocoaData: $0) }
         let items = try jsonValues.map { jsonValue throws(ErrorInfo) in
             try Response(jsonValue: jsonValue)
         }
         return paginatedResponse.toPaginatedResult(items: items)
     }
Sources/AblyChat/InternalError.swift (1)

80-84: Optional: improve case name grammar.

Consider unableToDeleteReactionWithoutName for readability. Public API unaffected if internal-only.

Sources/AblyChat/RoomLifecycleManager.swift (1)

76-83: Mark the stateful manager type as @mainactor to enforce isolation of mutable state.

Matches the guideline “Isolate all mutable state to the main actor; mark stateful objects with @mainactor.”

-internal class DefaultRoomLifecycleManager: RoomLifecycleManager {
+@MainActor
+internal class DefaultRoomLifecycleManager: RoomLifecycleManager {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3a2ee83 and a8d06d6.

📒 Files selected for processing (21)
  • Sources/AblyChat/ChatAPI.swift (4 hunks)
  • Sources/AblyChat/DefaultMessageReactions.swift (2 hunks)
  • Sources/AblyChat/DefaultMessages.swift (2 hunks)
  • Sources/AblyChat/DefaultPresence.swift (6 hunks)
  • Sources/AblyChat/ErrorInfo.swift (2 hunks)
  • Sources/AblyChat/InternalError.swift (2 hunks)
  • Sources/AblyChat/JSONCodable.swift (2 hunks)
  • Sources/AblyChat/Message.swift (3 hunks)
  • Sources/AblyChat/PaginatedResult.swift (1 hunks)
  • Sources/AblyChat/RoomFeature.swift (0 hunks)
  • Sources/AblyChat/RoomLifecycleManager.swift (5 hunks)
  • Sources/AblyChat/Rooms.swift (1 hunks)
  • Tests/AblyChatTests/ChatAPITests.swift (1 hunks)
  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift (3 hunks)
  • Tests/AblyChatTests/DefaultPresenceTests.swift (13 hunks)
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (10 hunks)
  • Tests/AblyChatTests/DefaultRoomsTests.swift (3 hunks)
  • Tests/AblyChatTests/ErrorInfoTests.swift (1 hunks)
  • Tests/AblyChatTests/Helpers/Helpers.swift (2 hunks)
  • Tests/AblyChatTests/MessageTests.swift (6 hunks)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1 hunks)
💤 Files with no reviewable changes (1)
  • Sources/AblyChat/RoomFeature.swift
🚧 Files skipped from review as they are similar to previous changes (9)
  • Sources/AblyChat/DefaultMessageReactions.swift
  • Sources/AblyChat/Rooms.swift
  • Sources/AblyChat/DefaultPresence.swift
  • Tests/AblyChatTests/ErrorInfoTests.swift
  • Sources/AblyChat/ErrorInfo.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
  • Sources/AblyChat/DefaultMessages.swift
  • Sources/AblyChat/PaginatedResult.swift
  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: Use protocol-based design; expose SDK functionality via protocols and prefer associated types with opaque return types (some Protocol) instead of existentials (any Protocol)
Isolate all mutable state to the main actor; mark stateful objects with @mainactor
Public API must use typed throws with ErrorInfo; use InternalError internally and convert at the public API boundary
For public structs emitted by the API, provide an explicit public memberwise initializer
When using AsyncSequence operators in @mainactor contexts, mark operator closures as @sendable
Task, CheckedContinuation, and AsyncThrowingStream do not support typed errors; use Result and call .get() to surface typed errors
Do not use Dictionary.mapValues for typed throws; use ablyChat_mapValuesWithTypedThrow instead
When the compiler struggles with typed throws, explicitly declare the error type on do blocks (e.g., do throws(InternalError))
Specify error types in closures using the throws(ErrorType) syntax (e.g., try items.map { jsonValue throws(InternalError) in ... })
Mark any test-only APIs with testsOnly_ prefix and wrap them in #if DEBUG

Files:

  • Tests/AblyChatTests/MessageTests.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Sources/AblyChat/Message.swift
  • Sources/AblyChat/JSONCodable.swift
  • Sources/AblyChat/RoomLifecycleManager.swift
  • Sources/AblyChat/InternalError.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Sources/AblyChat/ChatAPI.swift
Tests/AblyChatTests/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Tests/AblyChatTests/**/*.swift: Use test attribution tags to reference spec coverage: @SPEC, @specOneOf(m/n), @specPartial, @specUntested, @specNotApplicable
For Swift Testing #expect(throws:) with typed errors, move typed-throw code into a separate non-typed-throw function until Xcode 16.3+

Files:

  • Tests/AblyChatTests/MessageTests.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
Tests/AblyChatTests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place tests under Tests/AblyChatTests/ as the root for test targets

Files:

  • Tests/AblyChatTests/MessageTests.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
Tests/AblyChatTests/Mocks/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Put mock implementations under Tests/AblyChatTests/Mocks/

Files:

  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
🧬 Code graph analysis (7)
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (4)
Sources/AblyChat/RoomLifecycleManager.swift (6)
  • performAttachOperation (370-372)
  • performAttachOperation (374-376)
  • performDetachOperation (433-435)
  • performDetachOperation (437-439)
  • testsOnly_subscribeToStatusChangeWaitEvents (634-636)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (3)
  • performAttachOperation (25-31)
  • performDetachOperation (33-39)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Tests/AblyChatTests/Helpers/RoomLifecycleManager+AsyncSequence.swift (1)
  • testsOnly_subscribeToStatusChangeWaitEvents (60-74)
Tests/AblyChatTests/DefaultRoomsTests.swift (2)
Sources/AblyChat/Rooms.swift (2)
  • get (60-63)
  • get (164-269)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Sources/AblyChat/Message.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Sources/AblyChat/JSONCodable.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Sources/AblyChat/RoomLifecycleManager.swift (1)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (2)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • addRecord (114-118)
Sources/AblyChat/ChatAPI.swift (4)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Mocks/MockRealtime.swift (1)
  • request (29-45)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (1)
  • request (148-169)
Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (1)
  • first (50-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 26.0)
  • GitHub Check: Example app, iOS (Xcode 26.0)
  • GitHub Check: Xcode, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 26.0)
  • GitHub Check: Example app, macOS (Xcode 26.0)
  • GitHub Check: Example app, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, iOS (Xcode 26.0)
  • GitHub Check: Xcode, macOS (Xcode 26.0)
  • GitHub Check: SPM, release configuration (Xcode 26.0)
  • GitHub Check: SPM (Xcode 26.0)
🔇 Additional comments (25)
Tests/AblyChatTests/Helpers/Helpers.swift (2)

5-26: LGTM! Helper function simplified appropriately.

The refactoring from hasCodeAndStatusCode to hasCode aligns well with the new error model. The note on line 8 is helpful for encouraging proper error validation.


36-43: LGTM! Error case handling updated correctly.

The switch case properly reflects the new flat error structure for jsonValueDecodingError.

Tests/AblyChatTests/ChatAPITests.swift (1)

23-29: LGTM! Error assertion updated correctly.

The error matching has been properly updated to reflect the new flattened error structure, checking for .internalError(.noItemInResponse) directly instead of the nested variant.

Tests/AblyChatTests/DefaultRoomsTests.swift (3)

158-181: LGTM! Error expectations correctly updated.

The test properly validates the roomExistsWithDifferentOptions error using the simplified hasCode helper.


183-225: LGTM! Consistent error validation.

The second test for roomExistsWithDifferentOptions follows the same pattern and correctly validates the error case.


329-383: LGTM! Release operation error handling validated.

The test correctly checks for roomReleasedBeforeOperationCompleted error using the hasCode helper.

Sources/AblyChat/JSONCodable.swift (2)

29-34: LGTM! Enhanced error information.

Adding the type parameter to failedToDecodeFromRawValue provides better context for debugging decoding failures.


275-300: LGTM! Call site updated correctly.

The function properly passes both the type information and raw value to the enhanced error case.

Tests/AblyChatTests/MessageTests.swift (4)

9-61: LGTM! Spec annotations and error code updated.

The spec annotation has been correctly updated to CHA-M11h, and the error code changed to 40003, aligning with the new error model.


63-114: LGTM! Consistent updates.

The spec annotation (CHA-M11i) and error code (40003) are correctly updated.


352-391: LGTM! Summary event spec annotation updated.

The spec annotation has been correctly updated to CHA-M11j for the reaction summary event test.


393-436: LGTM! Consistent error code and spec annotation.

The error code (40003) and spec annotation (CHA-M11j) are correctly aligned.

Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)

60-69: LGTM! Mock updated for parameterless presence API.

The mock correctly reflects the simplified presence wait API that no longer requires a requestedByFeature parameter. The call recording with empty arguments is appropriate.

Sources/AblyChat/Message.swift (3)

169-198: LGTM! JSON decoding error enhanced with type information.

The call to failedToDecodeFromRawValue correctly passes both ChatMessageAction.self and the raw value, improving error context.


213-233: LGTM! Error case updated for reaction summary events.

The error has been appropriately renamed to cannotApplyReactionSummaryEventForDifferentMessage, providing clearer semantics. The comment correctly references the InvalidArgument error code from the spec.


235-266: LGTM! Message event error cases refined.

The error cases have been properly updated:

  • cannotApplyCreatedMessageEvent for created events
  • cannotApplyMessageEventForDifferentMessage for serial mismatches

Both align with spec terminology referencing InvalidArgument errors.

Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (4)

91-119: LGTM! Error consolidation for invalid room states.

The tests correctly use roomInInvalidState error for both releasing and released states. The comment on line 100 helpfully explains that the old roomIsReleasing error code no longer exists in the spec.


274-318: LGTM! Consistent error handling for detach operations.

The detach operation tests properly validate roomInInvalidState errors for releasing, released, and failed states. Spec annotations have been updated appropriately (CHA-RL2l, CHA-RL2m).


900-945: LGTM! Discontinuity error validation updated.

The test correctly validates the roomDiscontinuity error code with its cause, using the hasCode helper method properly.


948-1070: LGTM! Presence wait API simplified correctly.

All calls to waitToBeAbleToPerformPresenceOperations() have been updated to remove the requestedByFeature parameter. The error expectations correctly validate roomInInvalidState with appropriate messages. The test at line 1069 includes a helpful message explaining the room must be in ATTACHED or ATTACHING status.

Sources/AblyChat/ChatAPI.swift (2)

141-144: Spec-aligned empty-serial check — good.

Throwing InternalError.sendMessageReactionEmptyMessageSerial.toErrorInfo() matches CHA‑MR4a2; typed throws preserved.


196-207: Path propagation and typed throws — good.

Switch to path and throwing InternalError.noItemInResponse(path:) is clean and aligns with the new error surface. Conversion to JSONValue and Response(jsonValue:) remains correct.

Also applies to: 209-217

Sources/AblyChat/RoomLifecycleManager.swift (2)

19-23: Docstring fix — good.

Updated to the new roomTransitionedToInvalidStateForPresenceOperation(newState:cause:) and presenceOperationRequiresRoomAttach.


609-614: Presence-flow errors use new InternalError variants — good.

Error propagation via toErrorInfo() is typed and spec-aligned.

Also applies to: 620-621

Sources/AblyChat/InternalError.swift (1)

131-151: Unable to verify error mappings against spec PR 394—recommend manual review.

The error codes and status mappings in InternalError.swift (lines 131–151) are consistent and well-established throughout the codebase:

  • Numeric values: badRequest (40000), invalidArgument (40003), roomDiscontinuity (102_100), roomReleasedBeforeOperationCompleted (102_106), roomExistsWithDifferentOptions (102_107), roomInInvalidState (102_112)
  • Status mappings: 400 for badRequest, invalidArgument, roomReleasedBeforeOperationCompleted, roomInInvalidState, roomExistsWithDifferentOptions; 500 for roomDiscontinuity

The code references "Common Error Codes used by Chat" and "Chat-specific Error Codes" sections of the chat spec (comment at line 141). These codes appear across 50+ test assertions with consistent spec point references (CHA-RL1l, CHA-RL2l, CHA-RL2m, etc.), but GitHub PR 394 is not accessible to confirm the exact mappings.

Verify these values against the merged specification to confirm alignment with PR 394.

Copy link
Copy Markdown
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few questions

Comment thread Sources/AblyChat/DefaultMessages.swift
Comment thread Sources/AblyChat/InternalError.swift
Comment thread Sources/AblyChat/InternalError.swift
Comment thread Sources/AblyChat/InternalError.swift Outdated
@lawrence-forooghian lawrence-forooghian force-pushed the 426-427-align-codes-and-messages branch from a8d06d6 to 2b00409 Compare October 21, 2025 12:37
@lawrence-forooghian lawrence-forooghian changed the base branch from main to 2025-10-21-undo-spec-pinning October 21, 2025 12:37
@github-actions github-actions bot temporarily deployed to staging/pull/437/AblyChat October 21, 2025 12:40 Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
Sources/AblyChat/InternalError.swift (1)

271-271: Verify custom string interpolation helper exists.

The syntax \(cause, default: "(nil cause)") requires a custom StringInterpolation extension with an appendInterpolation(_:default:) method. A previous review verified this helper doesn't exist in the codebase, which would cause compilation errors.

Verify whether the code compiles successfully:

#!/bin/bash
# Description: Check if the project builds successfully and search for StringInterpolation extensions

# Check for any StringInterpolation extensions that might support this syntax
rg -n "appendInterpolation" --type swift -A3 -B1

# Also check if there's a custom String extension
rg -n "extension.*String.*Interpolation" --type swift -A5

If the interpolation helper is missing, replace with standard Swift:

-reason = "room transitioned to invalid state \(newState): \(cause, default: "(nil cause)")"
+reason = "room transitioned to invalid state \(newState): \(cause.map { String(describing: $0) } ?? "(nil cause)")"

Also applies to: 274-274, 295-295

🧹 Nitpick comments (3)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)

60-67: Signature/recording alignment looks good; two small nits.

  • Typo in fatalError message (“resultOfWaitToBeAblePerformPresenceOperations”): missing “To”.
  • Consider marking this mock @mainactor to match state isolation in tests and production interfaces. This avoids accidental cross-actor mutation of counters. As per coding guidelines.
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (1)

1063-1070: Message assertion adds confidence but is brittle.
Hard-coding the message ties tests to wording tweaks. Consider asserting code+cause only, or centralize the expected message via a helper to reduce churn. Based on coding guidelines.

Tests/AblyChatTests/DefaultPresenceTests.swift (1)

109-111: Tiny test ergonomics nit.
You repeat the “waitToBeAble…” record assertion across tests; consider a small helper like expectPresenceWait(roomLifecycleManager) to DRY it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a8d06d6 and 2b00409.

📒 Files selected for processing (21)
  • Sources/AblyChat/ChatAPI.swift (4 hunks)
  • Sources/AblyChat/DefaultMessageReactions.swift (2 hunks)
  • Sources/AblyChat/DefaultMessages.swift (2 hunks)
  • Sources/AblyChat/DefaultPresence.swift (6 hunks)
  • Sources/AblyChat/ErrorInfo.swift (2 hunks)
  • Sources/AblyChat/InternalError.swift (2 hunks)
  • Sources/AblyChat/JSONCodable.swift (2 hunks)
  • Sources/AblyChat/Message.swift (3 hunks)
  • Sources/AblyChat/PaginatedResult.swift (1 hunks)
  • Sources/AblyChat/RoomFeature.swift (0 hunks)
  • Sources/AblyChat/RoomLifecycleManager.swift (5 hunks)
  • Sources/AblyChat/Rooms.swift (1 hunks)
  • Tests/AblyChatTests/ChatAPITests.swift (1 hunks)
  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift (3 hunks)
  • Tests/AblyChatTests/DefaultPresenceTests.swift (13 hunks)
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (10 hunks)
  • Tests/AblyChatTests/DefaultRoomsTests.swift (3 hunks)
  • Tests/AblyChatTests/ErrorInfoTests.swift (1 hunks)
  • Tests/AblyChatTests/Helpers/Helpers.swift (2 hunks)
  • Tests/AblyChatTests/MessageTests.swift (6 hunks)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1 hunks)
💤 Files with no reviewable changes (1)
  • Sources/AblyChat/RoomFeature.swift
🚧 Files skipped from review as they are similar to previous changes (6)
  • Sources/AblyChat/ErrorInfo.swift
  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift
  • Sources/AblyChat/Rooms.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Sources/AblyChat/DefaultMessageReactions.swift
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: Use protocol-based design; expose SDK functionality via protocols and prefer associated types with opaque return types (some Protocol) instead of existentials (any Protocol)
Isolate all mutable state to the main actor; mark stateful objects with @mainactor
Public API must use typed throws with ErrorInfo; use InternalError internally and convert at the public API boundary
For public structs emitted by the API, provide an explicit public memberwise initializer
When using AsyncSequence operators in @mainactor contexts, mark operator closures as @sendable
Task, CheckedContinuation, and AsyncThrowingStream do not support typed errors; use Result and call .get() to surface typed errors
Do not use Dictionary.mapValues for typed throws; use ablyChat_mapValuesWithTypedThrow instead
When the compiler struggles with typed throws, explicitly declare the error type on do blocks (e.g., do throws(InternalError))
Specify error types in closures using the throws(ErrorType) syntax (e.g., try items.map { jsonValue throws(InternalError) in ... })
Mark any test-only APIs with testsOnly_ prefix and wrap them in #if DEBUG

Files:

  • Sources/AblyChat/DefaultPresence.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ErrorInfoTests.swift
  • Sources/AblyChat/PaginatedResult.swift
  • Sources/AblyChat/JSONCodable.swift
  • Tests/AblyChatTests/MessageTests.swift
  • Sources/AblyChat/ChatAPI.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Sources/AblyChat/DefaultMessages.swift
  • Sources/AblyChat/InternalError.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Sources/AblyChat/Message.swift
  • Sources/AblyChat/RoomLifecycleManager.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
Tests/AblyChatTests/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Tests/AblyChatTests/**/*.swift: Use test attribution tags to reference spec coverage: @SPEC, @specOneOf(m/n), @specPartial, @specUntested, @specNotApplicable
For Swift Testing #expect(throws:) with typed errors, move typed-throw code into a separate non-typed-throw function until Xcode 16.3+

Files:

  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ErrorInfoTests.swift
  • Tests/AblyChatTests/MessageTests.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
Tests/AblyChatTests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place tests under Tests/AblyChatTests/ as the root for test targets

Files:

  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ErrorInfoTests.swift
  • Tests/AblyChatTests/MessageTests.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
Tests/AblyChatTests/Mocks/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Put mock implementations under Tests/AblyChatTests/Mocks/

Files:

  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
🧬 Code graph analysis (12)
Sources/AblyChat/DefaultPresence.swift (2)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Tests/AblyChatTests/DefaultRoomsTests.swift (2)
Sources/AblyChat/Rooms.swift (2)
  • get (60-63)
  • get (164-269)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Tests/AblyChatTests/ErrorInfoTests.swift (2)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Sources/AblyChat/PaginatedResult.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Sources/AblyChat/JSONCodable.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Sources/AblyChat/ChatAPI.swift (4)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Mocks/MockRealtime.swift (1)
  • request (29-45)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (1)
  • request (148-169)
Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (1)
  • first (50-52)
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (4)
Sources/AblyChat/RoomLifecycleManager.swift (6)
  • performAttachOperation (370-372)
  • performAttachOperation (374-376)
  • performDetachOperation (433-435)
  • performDetachOperation (437-439)
  • testsOnly_subscribeToStatusChangeWaitEvents (634-636)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (3)
  • performAttachOperation (25-31)
  • performDetachOperation (33-39)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Tests/AblyChatTests/Helpers/RoomLifecycleManager+AsyncSequence.swift (1)
  • testsOnly_subscribeToStatusChangeWaitEvents (60-74)
Sources/AblyChat/DefaultMessages.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (2)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • addRecord (114-118)
Sources/AblyChat/Message.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Sources/AblyChat/RoomLifecycleManager.swift (2)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/DefaultPresenceTests.swift (2)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (371-373)
Tests/AblyChatTests/Helpers/Helpers.swift (2)
  • hasCode (10-26)
  • hasRecord (120-131)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Example app, macOS (Xcode 26.0)
  • GitHub Check: Example app, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 26.0)
  • GitHub Check: Example app, iOS (Xcode 26.0)
  • GitHub Check: Xcode, iOS (Xcode 26.0)
  • GitHub Check: Xcode, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, macOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 26.0)
  • GitHub Check: SPM, release configuration (Xcode 26.0)
  • GitHub Check: SPM (Xcode 26.0)
  • GitHub Check: check-documentation
🔇 Additional comments (25)
Sources/AblyChat/DefaultMessages.swift (1)

100-100: Consider whether throwing is appropriate when instance is deallocated.

A past reviewer suggested a silent exit here since "it's gone now, who cares?". The current approach throws an error, which provides diagnostic information. Consider whether:

  • The error helps diagnose issues in production/testing
  • Or if silent failure is more appropriate for a deallocated instance

Both approaches are valid; the choice depends on whether you want visibility into this edge case.

Based on learnings

Sources/AblyChat/RoomLifecycleManager.swift (1)

19-22: LGTM! Signature and documentation updated consistently.

The removal of the requestedByFeature parameter simplifies the API, and the documentation correctly references the updated error variant with newState:cause: parameters. All call sites have been updated accordingly.

Sources/AblyChat/DefaultPresence.swift (1)

23-23: LGTM! All call sites updated consistently.

The removal of the requestedByFeature parameter has been applied consistently across all presence operations.

Also applies to: 44-44, 66-66, 97-97, 125-125, 153-153

Tests/AblyChatTests/ErrorInfoTests.swift (1)

51-61: LGTM! Test updated to reflect new internal error model.

The test now exercises the headersValueJSONDecodingError variant and verifies the correct error code and message format.

Tests/AblyChatTests/DefaultRoomsTests.swift (1)

180-180: LGTM! Test assertions updated for new error codes.

The tests now correctly verify the new roomExistsWithDifferentOptions and roomReleasedBeforeOperationCompleted error codes.

Also applies to: 221-221, 373-373

Sources/AblyChat/JSONCodable.swift (1)

33-33: LGTM! Type information enriches error diagnostics.

Adding the type parameter to failedToDecodeFromRawValue will help with debugging by making it clear which type failed to decode from its raw value.

Also applies to: 296-296

Sources/AblyChat/ChatAPI.swift (2)

196-196: LGTM! Parameter rename improves clarity.

Renaming url to path is more accurate since the parameter is a path segment, not a complete URL. The change has been propagated correctly through the call chain.

Also applies to: 209-209


212-212: LGTM! Error includes path for better diagnostics.

Including the path parameter in the noItemInResponse error will help with debugging by showing which endpoint failed to return an item.

Tests/AblyChatTests/MessageTests.swift (1)

59-59: Error code 40003 is correctly mapped but external spec verification needed.

The codebase shows error code 40003 is defined as invalidArgument in Sources/AblyChat/InternalError.swift:132 and all three test assertions (lines 59, 112, 434) consistently check for this code. The test scenarios—mismatched message serial, invalid timestamp, and wrong message type—logically represent invalid arguments. The InternalError enum documentation states these error codes derive from the Ably Chat spec, but the spec document itself cannot be verified within the codebase.

Sources/AblyChat/Message.swift (1)

181-182: Good alignment to spec-driven errors and richer decode failures.

  • Using JSONValueDecodingError.failedToDecodeFromRawValue(type:…, rawValue:…) improves diagnostics.
  • Specific InternalError variants for reaction/message mismatch and created-event are clearer and map well to InvalidArgument.

Please confirm these variants map to the intended public code (InvalidArgument) in ErrorInfo formatting. Based on coding guidelines.

Also applies to: 225-228, 246-254

Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (5)

100-105: Spec-aligned error assertions (roomInInvalidState) look correct.
These updates reflect the consolidated error code and read cleanly. LGTM.

Also applies to: 107-119, 283-288, 290-302, 304-318


937-941: Discontinuity cause assertion is precise and valuable.
Verifying the propagated cause strengthens failure diagnosis. LGTM.


974-985: Presence wait: parameterless API usage verified end-to-end.
The subscription/wait flow and success path assertions look solid. LGTM.


1013-1016: Presence wait: failure path assertions are correct.
Asserting roomInInvalidState with the attach error as cause matches the new InternalError shape. LGTM.

Also applies to: 1024-1034


1046-1049: Attached fast-path test reads clean and minimal.
Good direct success assertion on parameterless wait. LGTM.

Tests/AblyChatTests/DefaultPresenceTests.swift (3)

77-79: Updated recorder expectations for parameterless presence-wait are correct.
Matches the new mock signature and ensures the wait happens. LGTM.

Also applies to: 105-105, 184-186, 212-212, 216-218, 369-371, 401-403


87-87: Failure-path construction aligns with new InternalError variants.
Using roomTransitionedToInvalidStateForPresenceOperation and mapping to roomInInvalidState with cause is spot on. LGTM.

Also applies to: 194-194, 379-379


118-118: Room-in-invalid-state expectations read clearly across operations.
Consistent spec coverage across enter/update/get flows. LGTM.

Also applies to: 134-135, 225-225, 241-242, 330-330, 346-347, 397-397

Sources/AblyChat/InternalError.swift (7)

9-75: LGTM! Spec-aligned error cases are well-documented.

The error cases are clearly documented with spec references, and the associated values provide appropriate context for error reporting.


78-78: TODO is appropriately scoped for issue #438.

The non-spec errors are marked for future review per the linked issue. This is a reasonable interim approach.


127-152: LGTM! Error code enum correctly implements spec values.

The error codes and status code mappings align with the spec requirements. The previously flagged typo has been corrected.


154-201: LGTM! Error code mappings are comprehensive and correct.

All error cases are properly mapped to their corresponding error codes, with appropriate spec references.


203-239: LGTM! Helper methods provide clear error context.

The descriptionOfRoomStatus and AttachOrDetach helpers effectively support error message generation. While a past review suggested "action" instead of "bareInfinitive", the current naming is linguistically accurate and documents the property's grammatical purpose.


331-361: LGTM! Cause extraction correctly handles all error cases.

The implementation properly extracts cause information from error cases that have it, and returns nil for others.


363-386: LGTM! Error conversion protocol is well-designed.

The ConvertibleToInternalError protocol provides a clean abstraction for converting domain-specific errors to InternalError, with appropriate conformances for JSON decoding errors.

Comment thread Sources/AblyChat/PaginatedResult.swift
Base automatically changed from 2025-10-21-undo-spec-pinning to main October 21, 2025 14:15
Based on spec PR [1] at 23ca1db.

I've also added documentation for all of the InternalError cases to
describe what they represent. We can also use this documentation to
explain the associated data.

Note that this highlights that there are some removed spec points that
we're still implementing; things that we missed in e.g. the single
channel migration. We still need to address those but it's not in the
scope of the current task.

We still have some usage of the 40000 BadRequest code for errors not
specified by the spec, violating CHA-GP5; will revisit these errors
separately in #438.

Note that we no longer have any error codes with a variable status code;
will remove this mechanism in a separate commit in case we need to
restore it some time.

Resolves #426.

[1] ably/specification#394
Want to treat them like our other errors and give them proper messages
and allow each to have its own code if necessary.
Got Claude to rewrite these and made some tweaks. There's still room for
improvement here (especially with the decoding error ones, where there's
not really enough context about the operation that was being performed).

Resolves #427.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (2)

81-89: Test asserts against a channel that isn’t used by the manager

You create channel but don’t pass it to createManager(...), so the assertion checks the wrong instance. Inject the channel.

-        let channel = createChannel()
-        let manager = createManager(forTestingWhatHappensWhenCurrentlyIn: .attached)
+        let channel = createChannel()
+        let manager = createManager(forTestingWhatHappensWhenCurrentlyIn: .attached, channel: channel)

142-145: Fix invalid async let usage and ensure tasks are awaited

Patterns like async let _ = try await ... are invalid and also leave child tasks un-awaited, risking test flakiness and compilation errors. Capture the task without await on the RHS and await it later.

Apply these localized fixes:

  1. attach_ifOtherOperationInProgress_waitsForItToComplete (Lines 142-165)
-        async let _ = manager.performDetachOperation(testsOnly_forcingOperationID: detachOperationID)
+        async let detaching: Void = manager.performDetachOperation(testsOnly_forcingOperationID: detachOperationID)
@@
-        try await attachResult
+        try await attachResult
+        try await detaching
  1. attach_transitionsToAttaching (Lines 179-190)
-        async let _ = try await manager.performAttachOperation()
+        async let attaching: Void = try manager.performAttachOperation()
@@
         channelAttachOperation.complete(behavior: .success)
+        try await attaching
  1. detach_transitionsToDetaching (Lines 378-388)
-        async let _ = try await manager.performDetachOperation()
+        async let detaching: Void = try manager.performDetachOperation()
@@
         channelDetachOperation.complete(behavior: .success)
+        try await detaching
  1. release_transitionsToReleasing (Lines 559-569)
-        async let _ = await manager.performReleaseOperation()
+        async let releasing: Void = manager.performReleaseOperation()
@@
         channelDetachOperation.complete(behavior: .success)
+        await releasing
  1. channelStateChange_withOperationInProgress (Lines 716-740)
-        async let _ = manager.performAttachOperation()
+        async let attaching: Void = manager.performAttachOperation()
@@
         channelAttachBehavior.complete(behavior: .success /* arbitrary */ )
+        try await attaching
  1. waitToBeAbleToPerformPresenceOperations_whenAttaching_whenTransitionsToAttached (Lines 969-986)
-        async let _ = manager.performAttachOperation(testsOnly_forcingOperationID: attachOperationID)
+        async let attaching: Void = manager.performAttachOperation(testsOnly_forcingOperationID: attachOperationID)
@@
         channelAttachOperation.complete(behavior: .success)
@@
-        try await waitToBeAbleToPerformPresenceOperationsResult
+        try await waitToBeAbleToPerformPresenceOperationsResult
+        try await attaching
  1. waitToBeAbleToPerformPresenceOperations_whenAttaching_whenTransitionsToNonAttachedStatus (Lines 1008-1034)
-        async let _ = manager.performAttachOperation(testsOnly_forcingOperationID: attachOperationID)
+        async let attaching: Void = manager.performAttachOperation(testsOnly_forcingOperationID: attachOperationID)
@@
         channelAttachOperation.complete(behavior: .completeAndChangeState(.failure(channelAttachError), newState: .failed))
@@
-        #expect(try #require(caughtError).hasCode(.roomInInvalidState, cause: expectedCause))
+        #expect(try #require(caughtError).hasCode(.roomInInvalidState, cause: expectedCause))
+        _ = try? await attaching

Also applies to: 179-181, 342-343, 379-380, 560-561, 716-717, 969-973, 1008-1012

♻️ Duplicate comments (4)
Sources/AblyChat/PaginatedResult.swift (1)

42-48: Replace preconditionFailure with typed error handling and accept 2xx status codes.

Two issues remain unaddressed from the previous review:

  1. PreconditionFailure crashes the app: Line 43's preconditionFailure will terminate the process. Replace with a typed internal error (e.g., InternalError.unexpectedNilPaginatedResponse) and resume the continuation with failure.

  2. Status code check is overly restrictive: Line 46 checks for exact equality with 200, but Ably's REST API treats all 2xx codes as success. Change to (200...299).contains(paginatedResponse.statusCode).

Sources/AblyChat/InternalError.swift (2)

270-271: Custom string interpolation syntax will not compile.

Lines 270, 273, and 294 use the syntax \(cause, default: "(nil cause)"), which is not standard Swift and requires a custom StringInterpolation extension that does not exist in the codebase (verified by searching the entire project).

Replace with standard Swift syntax:

cause.map { String(describing: $0) } ?? "(nil cause)"

or simply:

"\(cause?.description ?? "(nil cause)")"

Also applies to: 273-274, 294-295


129-129: Fix typo: "Commmon" → "Common".

Both lines contain a typo in the comment text.

Also applies to: 140-140

Sources/AblyChat/RoomLifecycleManager.swift (1)

19-23: Docstring now references the correct error symbol (newState:cause:)

The documentation reflects the updated roomTransitionedToInvalidStateForPresenceOperation(newState:cause:). Thanks for addressing the earlier note.

🧹 Nitpick comments (1)
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (1)

1068-1070: Assert error code only, not hardcoded message

The hardcoded message string is dynamically constructed in InternalError.swift (line 326) from separate op and reason values. If either value changes, the test fails despite correct behavior.

Instead of asserting the full message, assert only the error code:

#expect(caughtError.hasCode(.roomInInvalidState))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2b00409 and dfd423e.

📒 Files selected for processing (21)
  • Sources/AblyChat/ChatAPI.swift (4 hunks)
  • Sources/AblyChat/DefaultMessageReactions.swift (2 hunks)
  • Sources/AblyChat/DefaultMessages.swift (2 hunks)
  • Sources/AblyChat/DefaultPresence.swift (6 hunks)
  • Sources/AblyChat/ErrorInfo.swift (2 hunks)
  • Sources/AblyChat/InternalError.swift (2 hunks)
  • Sources/AblyChat/JSONCodable.swift (2 hunks)
  • Sources/AblyChat/Message.swift (3 hunks)
  • Sources/AblyChat/PaginatedResult.swift (1 hunks)
  • Sources/AblyChat/RoomFeature.swift (0 hunks)
  • Sources/AblyChat/RoomLifecycleManager.swift (5 hunks)
  • Sources/AblyChat/Rooms.swift (1 hunks)
  • Tests/AblyChatTests/ChatAPITests.swift (1 hunks)
  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift (3 hunks)
  • Tests/AblyChatTests/DefaultPresenceTests.swift (13 hunks)
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (10 hunks)
  • Tests/AblyChatTests/DefaultRoomsTests.swift (3 hunks)
  • Tests/AblyChatTests/ErrorInfoTests.swift (1 hunks)
  • Tests/AblyChatTests/Helpers/Helpers.swift (2 hunks)
  • Tests/AblyChatTests/MessageTests.swift (6 hunks)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1 hunks)
💤 Files with no reviewable changes (1)
  • Sources/AblyChat/RoomFeature.swift
✅ Files skipped from review due to trivial changes (1)
  • Sources/AblyChat/DefaultMessageReactions.swift
🚧 Files skipped from review as they are similar to previous changes (8)
  • Tests/AblyChatTests/DefaultMessageReactionsTests.swift
  • Sources/AblyChat/DefaultPresence.swift
  • Sources/AblyChat/DefaultMessages.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Sources/AblyChat/Message.swift
  • Sources/AblyChat/ErrorInfo.swift
  • Tests/AblyChatTests/ErrorInfoTests.swift
🧰 Additional context used
📓 Path-based instructions (3)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: Use protocol-based design; expose SDK functionality via protocols and prefer associated types with opaque return types (some Protocol) instead of existentials (any Protocol)
Isolate all mutable state to the main actor; mark stateful objects with @mainactor
Public API must use typed throws with ErrorInfo; use InternalError internally and convert at the public API boundary
For public structs emitted by the API, provide an explicit public memberwise initializer
When using AsyncSequence operators in @mainactor contexts, mark operator closures as @sendable
Task, CheckedContinuation, and AsyncThrowingStream do not support typed errors; use Result and call .get() to surface typed errors
Do not use Dictionary.mapValues for typed throws; use ablyChat_mapValuesWithTypedThrow instead
When the compiler struggles with typed throws, explicitly declare the error type on do blocks (e.g., do throws(InternalError))
Specify error types in closures using the throws(ErrorType) syntax (e.g., try items.map { jsonValue throws(InternalError) in ... })
Mark any test-only APIs with testsOnly_ prefix and wrap them in #if DEBUG

Files:

  • Sources/AblyChat/Rooms.swift
  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Sources/AblyChat/JSONCodable.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
  • Sources/AblyChat/PaginatedResult.swift
  • Sources/AblyChat/InternalError.swift
  • Sources/AblyChat/ChatAPI.swift
  • Tests/AblyChatTests/MessageTests.swift
  • Sources/AblyChat/RoomLifecycleManager.swift
Tests/AblyChatTests/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Tests/AblyChatTests/**/*.swift: Use test attribution tags to reference spec coverage: @SPEC, @specOneOf(m/n), @specPartial, @specUntested, @specNotApplicable
For Swift Testing #expect(throws:) with typed errors, move typed-throw code into a separate non-typed-throw function until Xcode 16.3+

Files:

  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
  • Tests/AblyChatTests/MessageTests.swift
Tests/AblyChatTests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place tests under Tests/AblyChatTests/ as the root for test targets

Files:

  • Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/ChatAPITests.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
  • Tests/AblyChatTests/MessageTests.swift
🧬 Code graph analysis (8)
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (4)
Sources/AblyChat/RoomLifecycleManager.swift (6)
  • performAttachOperation (370-372)
  • performAttachOperation (374-376)
  • performDetachOperation (433-435)
  • performDetachOperation (437-439)
  • testsOnly_subscribeToStatusChangeWaitEvents (634-636)
  • waitToBeAbleToPerformPresenceOperations (580-622)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (3)
  • performAttachOperation (25-31)
  • performDetachOperation (33-39)
  • waitToBeAbleToPerformPresenceOperations (60-69)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Tests/AblyChatTests/Helpers/RoomLifecycleManager+AsyncSequence.swift (1)
  • testsOnly_subscribeToStatusChangeWaitEvents (60-74)
Sources/AblyChat/JSONCodable.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (370-372)
Tests/AblyChatTests/DefaultRoomsTests.swift (2)
Sources/AblyChat/Rooms.swift (2)
  • get (60-63)
  • get (164-269)
Tests/AblyChatTests/Helpers/Helpers.swift (1)
  • hasCode (10-26)
Tests/AblyChatTests/DefaultPresenceTests.swift (2)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (370-372)
Tests/AblyChatTests/Helpers/Helpers.swift (2)
  • hasCode (10-26)
  • hasRecord (120-131)
Sources/AblyChat/PaginatedResult.swift (1)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (370-372)
Sources/AblyChat/InternalError.swift (3)
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (2)
  • attach (94-102)
  • detach (108-116)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (2)
  • attach (351-365)
  • detach (367-381)
Sources/AblyChat/Room.swift (2)
  • attach (377-379)
  • detach (382-384)
Sources/AblyChat/ChatAPI.swift (3)
Sources/AblyChat/InternalError.swift (2)
  • toErrorInfo (123-125)
  • toErrorInfo (370-372)
Tests/AblyChatTests/Mocks/MockRealtime.swift (1)
  • request (29-45)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (1)
  • request (148-169)
Sources/AblyChat/RoomLifecycleManager.swift (1)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • waitToBeAbleToPerformPresenceOperations (60-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Example app, macOS (Xcode 26.0)
  • GitHub Check: Example app, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 26.0)
  • GitHub Check: Xcode, macOS (Xcode 26.0)
  • GitHub Check: Xcode, tvOS (Xcode 26.0)
  • GitHub Check: Example app, iOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 26.0)
  • GitHub Check: SPM, release configuration (Xcode 26.0)
  • GitHub Check: Xcode, iOS (Xcode 26.0)
  • GitHub Check: SPM (Xcode 26.0)
🔇 Additional comments (13)
Tests/AblyChatTests/ChatAPITests.swift (1)

24-29: LGTM!

The error matching pattern correctly uses the new InternalError.noItemInResponse variant, aligning with the broader error refactoring in this PR.

Sources/AblyChat/Rooms.swift (1)

170-174: LGTM!

The new roomExistsWithDifferentOptions error variant provides better debugging information by including both the requested and existing options, and correctly converts to ErrorInfo via toErrorInfo().

Tests/AblyChatTests/DefaultRoomsTests.swift (2)

180-180: LGTM!

Test assertions correctly verify the new roomExistsWithDifferentOptions error code, aligning with the spec changes.

Also applies to: 221-221


373-373: LGTM!

Test assertion correctly verifies the roomReleasedBeforeOperationCompleted error code.

Sources/AblyChat/JSONCodable.swift (1)

33-33: LGTM!

Adding the type parameter to failedToDecodeFromRawValue provides better error diagnostics by identifying which type failed to decode. The throw site at line 296 correctly passes T.self.

Also applies to: 296-296

Tests/AblyChatTests/MessageTests.swift (2)

9-9: LGTM!

Spec tag updates (CHA-M11a→CHA-M11h, CHA-M11b→CHA-M11i) and error code changes (40000→40003) correctly align with the specification changes. The new invalidArgument code (40003) more accurately represents validation errors than the previous badRequest code (40000).

Also applies to: 59-59, 63-63, 112-112


353-353: LGTM!

Spec tag updates to CHA-M11j and error code change to 40003 (invalidArgument) align with the specification refactoring.

Also applies to: 393-393, 434-434

Sources/AblyChat/ChatAPI.swift (2)

141-143: LGTM!

The updated error handling correctly uses the new InternalError variants (sendMessageReactionEmptyMessageSerial and deleteMessageReactionEmptyMessageSerial) and aligns with the spec's requirement to throw InvalidArgument errors for empty message serials.

Also applies to: 159-161


196-196: LGTM!

The parameter rename from url to path more accurately reflects REST path semantics. Including the path in the noItemInResponse error (line 212) provides better debugging context.

Also applies to: 209-209, 212-212

Tests/AblyChatTests/DefaultPresenceTests.swift (1)

77-79: LGTM on presence wait and error alignment

Calls switched to the parameterless waitToBeAbleToPerformPresenceOperations() and assertions now target .roomInInvalidState with appropriate causes. Matches the new InternalError variants and mock recording signature.

Also applies to: 88-88, 105-111, 118-118, 134-135, 185-186, 194-194, 212-218, 225-225, 241-242, 330-331, 346-347, 369-371, 379-379, 397-403

Sources/AblyChat/RoomLifecycleManager.swift (3)

395-399: Attach invalid-state mapping aligned with spec

Using roomIsReleasing(operation: .attach) and roomInInvalidStateForAttach(roomStatus) is clearer and spec-aligned.


458-462: Detach invalid-state mapping aligned with spec

Symmetric handling with roomIsReleasing(operation: .detach) and roomInInvalidStateForDetach(roomStatus) looks good.


609-621: Presence wait: correct error propagation on non-attached transition

Throwing roomTransitionedToInvalidStateForPresenceOperation(newState:cause:) and presenceOperationRequiresRoomAttach matches the tests and spec points.

Copy link
Copy Markdown
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lawrence-forooghian lawrence-forooghian merged commit d7cc8ee into main Oct 21, 2025
17 checks passed
@lawrence-forooghian lawrence-forooghian deleted the 426-427-align-codes-and-messages branch October 21, 2025 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Align error messages with JS Address mismatch between error codes in JS, spec, and Swift

2 participants