Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ECO-4942] Send/Receive Messages Spec Complete #45

Merged
merged 1 commit into from
Oct 16, 2024
Merged

Conversation

umair-ably
Copy link
Collaborator

@umair-ably umair-ably commented Sep 17, 2024

Implemented the spec items for sending and receiving messages in the AblyChat SDK.

This PR largely implements the public API which was previously defined. Small changes to the API were required e.g. making clientID optional since it is optional in ably-cocoa, so initialising this SDK with Realtime could also result in a null clientID.

Dependant on ably/ably-cocoa#1992

Outstanding:

  • The logger work was merged after this PR was in, so I'll create a task on the board to add that to this retrospectively.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced a ChatAPI class for managing chat operations with functionalities for sending messages, retrieving messages, and checking room occupancy.
    • Added a SwiftUI-based MessageDemoView for an interactive chat interface.
  • Improvements

    • Updated platform requirements to macOS v12 and adopted a branch-based dependency for ably-cocoa.
    • Enhanced the DefaultMessages class for improved management of real-time chat messages.
    • Added a new section for potential package references in the project configuration.
  • Bug Fixes

    • Adjusted initializations in tests to ensure compatibility with updated classes.

Copy link

coderabbitai bot commented Sep 17, 2024

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Walkthrough

This pull request updates the AblyChat project by modifying dependency specifications and introducing new functionalities. The Package.resolved file reflects changes in the ably-cocoa dependency, switching from a specific version to a branch-based approach. Additionally, a new Swift package configuration file is introduced, establishing the foundational structure for the project. The AblyChatExample project is updated to include a new view for the user interface, while several classes are enhanced to support chat functionalities, including message handling and channel management.

Changes

File Path Change Summary
AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved Updated originHash and modified ably-cocoa dependency from version-based to branch-based specification.
Package.swift Changed macOS platform version from .macOS(.v11) to .macOS(.v12) and updated ably-cocoa dependency to branch-based.
[email protected] Introduced a new Swift package configuration with library products and dependencies for AblyChat.
Example/AblyChatExample.xcodeproj/project.pbxproj Added a new empty packageReferences section in the PBXProject object.
Example/AblyChatExample/AblyChatExampleApp.swift Replaced ContentView with MessageDemoView in the app's main window group.
Example/AblyChatExample/MessageDemoView.swift Implemented a SwiftUI-based chat interface with structures for managing messages and user interactions.
Example/AblyChatExample/Mocks/MockRealtime.swift Enhanced MockRealtime class with new methods and properties to support concurrency features.
Sources/AblyChat/ChatAPI.swift Introduced ChatAPI class for managing chat operations, including message sending and retrieval.
Sources/AblyChat/DefaultMessages.swift Enhanced DefaultMessages class for managing real-time chat messages with new private types.

Possibly related PRs

Suggested reviewers

  • maratal

Poem

🐇 In the chat we hop, with messages bright,
New views and APIs make our code take flight.
With AblyChat we connect, share, and play,
In the world of Swift, we brighten the day! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@umair-ably umair-ably changed the title Ably chat demo wip Send/Receive message draft Sep 17, 2024
@lawrence-forooghian
Copy link
Collaborator

lawrence-forooghian commented Sep 17, 2024

@umair-ably what was your aim in using existential types (i.e. the any Foo<T> which is giving you the OS support issue)? Is there a reason you avoided just using the concrete types i.e. something like 71270cf?

@lawrence-forooghian
Copy link
Collaborator

Also, just to check — are you intending to add tests?

@umair-ably
Copy link
Collaborator Author

@umair-ably what was your aim in using existential types (i.e. the any Foo<T> which is giving you the OS support issue)? Is there a reason you avoided just using the concrete types i.e. something like 71270cf?

I started off with a generic implementation hence the protocol itself being passed in. I didn't want the base network "layer" to know about any concrete types in case we add additional implementations of the protocol later. But on second thoughts (and from seeing your solution), I'd already implemented the concrete type to also be fairly generic so I achieve my initial aim too - I just became blind to tying all of it together from staring at this too long 😅

Thanks for this - it perfectly solves the problem!

@umair-ably
Copy link
Collaborator Author

umair-ably commented Sep 17, 2024

Also, just to check — are you intending to add tests?

Yeah for sure, ideally I want to put the completed implementation of the message spec in this PR... hoping you can review this first to point out any obvious errors/deviations/unnecessary public api changes, etc, and then it'll be a good base to finish the rest off

Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian left a comment

Choose a reason for hiding this comment

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

Added a few general thoughts, as requested. Some of the review is quite superficial, will look in more detail at the final one.

Also, please could you make it clear where the behaviour implemented here has come from, to aid with review now and to help our future selves understand it better. My suggestion would be to make sure that:

  • the code includes spec references, where they exist (I know that the code that I’m looking at now predates the existence of the spec)
  • code that's based on the implementation in the JS SDK should state this and should state the specific version (i.e. commit hash) of the JS SDK that it's based on – either do this at the place where the behaviour is implemented, or in commit message, whichever seems most appropriate

Example/AblyChatExample/ContentView.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Channel.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Channel.swift Outdated Show resolved Hide resolved
Sources/AblyChat/ChatAPI.swift Outdated Show resolved Hide resolved
Sources/AblyChat/ChatAPI.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Metadata.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Metadata.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Timeserial.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Timeserial.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Version.swift Outdated Show resolved Hide resolved
@maratal maratal self-requested a review September 27, 2024 12:10
@lawrence-forooghian
Copy link
Collaborator

Before removing from draft please could you also tidy up the commits; i.e. in addition to what I mentioned here also make sure to remove the merge commit and to incorporate linting fixes into the commit that introduced the errors. Thanks!

Copy link
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.

Changes in subscribe method prototype should be addressed as well as other minor suggestions.

Sources/AblyChat/ChatAPI.swift Outdated Show resolved Hide resolved
Sources/AblyChat/ChatAPI.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Room.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Timeserial.swift Show resolved Hide resolved
Sources/AblyChat/Version.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Version.swift Show resolved Hide resolved
Sources/AblyChat/Version.swift Show resolved Hide resolved
@umair-ably umair-ably marked this pull request as ready for review October 2, 2024 12:19
@umair-ably
Copy link
Collaborator Author

Marking as ready for review to see what code-rabbit has got to say... will respond to other PR comments shortly

Copy link

@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: 52

🧹 Outside diff range and nitpick comments (33)
Sources/AblyChat/Events.swift (2)

1-3: LGTM! Consider adding documentation.

The MessageEvent enum is well-structured and follows Swift best practices. It's internal, which is appropriate for module-level use, and conforms to String for easy string representation.

Consider adding documentation comments to explain the purpose of the enum and its case. For example:

/// Represents various events related to messages in the chat system.
internal enum MessageEvent: String {
    /// Indicates that a new message has been created.
    case created = "message.created"
}

1-3: Consider future extensibility

The current implementation with a single case is clean and straightforward. However, if you anticipate adding more message events in the future, it might be worth mentioning this in a comment for other developers.

You could add a comment like:

// TODO: Add more message event cases as needed for future requirements.

This would help signal to other developers that this enum is expected to grow over time.

Sources/AblyChat/Occupancy.swift (1)

Line range hint 1-17: Suggestion: Add documentation for improved clarity

While the code structure is clear, adding documentation comments would greatly enhance the readability and maintainability of this file. Consider adding:

  1. A brief description of the Occupancy protocol's purpose and its relationship with the Ably SDK.
  2. Documentation for each method in the Occupancy protocol, explaining their purpose and usage.
  3. A description of the OccupancyEvent struct and its properties.

Example for the Occupancy protocol:

/// Represents the occupancy of a channel in the Ably real-time system.
/// This protocol defines methods to subscribe to and retrieve occupancy events.
public protocol Occupancy: AnyObject, Sendable, EmitsDiscontinuities {
    /// Subscribes to occupancy events with a specified buffering policy.
    /// - Parameter bufferingPolicy: The policy for buffering events.
    /// - Returns: A subscription to occupancy events.
    func subscribe(bufferingPolicy: BufferingPolicy) -> Subscription<OccupancyEvent>

    /// Retrieves the current occupancy event.
    /// - Returns: The current occupancy event.
    /// - Throws: An error if the retrieval fails.
    func get() async throws -> OccupancyEvent

    /// The Ably real-time channel associated with this occupancy.
    var channel: ARTRealtimeChannelProtocol { get }
}
Sources/AblyChat/EmitsDiscontinuities.swift (1)

Line range hint 1-20: Overall, the file is well-structured and follows good Swift practices

The EmitsDiscontinuities.swift file contains two well-defined protocols that work together to provide a system for handling and emitting discontinuities. The use of proper access control (public and internal) and the @MainActor attribute demonstrates attention to detail and consideration for thread safety.

A few points to consider:

  1. Ensure that the change from optional to non-optional channel in HandlesDiscontinuity doesn't negatively impact conforming types.
  2. Consider adding documentation to the EmitsDiscontinuities protocol to match the level of detail provided for HandlesDiscontinuity.
  3. If there are any specific requirements or constraints for conforming types, it might be beneficial to add them as comments to guide implementers.
Sources/AblyChat/Metadata.swift (3)

1-3: Acknowledge the ongoing work and suggest improvements to the comment

Thank you for providing context about the current implementation and acknowledging the potential for future improvements. To enhance clarity:

  1. Consider adding more context about the "Headers" reference. This might not be immediately clear to all readers.
  2. It would be beneficial to update the GitHub issue (Decide correct Swift types for user-provided data #13) with the current implementation details and reasoning. This will help track the evolution of this type and facilitate future discussions.

4-9: Approve the MetadataValue enum and suggest minor enhancements

The MetadataValue enum is a good implementation that enhances type safety and clarity in representing metadata. The chosen cases and conformances are appropriate.

Consider the following suggestions:

  1. If floating-point numbers are needed, you might want to add a Double case:

    case number(Double)  // Instead of Int

    This would allow for both integer and floating-point values.

  2. Add documentation comments for the enum and its cases to improve code readability:

    /// Represents a value in the metadata dictionary.
    public enum MetadataValue: Sendable, Codable, Hashable {
        /// A string value.
        case string(String)
        /// A numeric value.
        case number(Int)  // or Double
        /// A boolean value.
        case bool(Bool)
        /// Represents a null value.
        case null
    }

11-11: Approve the Metadata typealias update and suggest documentation

The updated Metadata typealias is a good improvement, providing more structure and type safety.

To further enhance code clarity, consider adding a documentation comment:

/// A dictionary representing metadata, where keys are strings and values are optional `MetadataValue`s.
public typealias Metadata = [String: MetadataValue?]

This comment helps developers understand the purpose and structure of the Metadata type at a glance.

Sources/AblyChat/Version.swift (2)

1-4: Address the TODO comment and track it properly

The TODO comment indicates that the implementation is copied from chat-js. It's important to ensure that this is addressed and tracked properly.

Consider the following actions:

  1. Review the referenced issue Define how to send up agent info  #76 to ensure it accurately describes the task of implementing agent info sending.
  2. If the issue is not yet created or doesn't fully capture the TODO, create or update the issue accordingly.
  3. Replace the TODO comment with a more specific comment referencing the issue, e.g., "// Implementation of agent info sending. See issue Define how to send up agent info  #76".

This will help in tracking and addressing this task in the future.


1-18: Overall structure is good, but consider further improvements

The file introduces necessary versioning and channel options for the Ably-based chat application. The overall structure is good, but there are several areas for improvement:

  1. Documentation: Add comprehensive documentation for each public constant, explaining their purpose and usage.

  2. Organization: Consider moving configuration constants to a separate Config.swift file for better maintainability.

  3. Naming: Ensure consistency in naming conventions, possibly prefixing with "ably" where appropriate.

  4. Concurrency: The use of @preconcurrency is a temporary solution. Plan for a more robust approach to handle concurrency issues in the future.

  5. Spec Compliance: Ensure all implementations align with the Ably specification and include references where applicable.

To improve the overall architecture:

  1. Create a Config.swift file for all configuration-related constants.
  2. Implement a Version struct to encapsulate version-related functionality.
  3. Consider creating an AblyChannelOptions struct to wrap ARTRealtimeChannelOptions and handle any Ably-specific configurations.

These changes will enhance the modularity and maintainability of the codebase while addressing the current limitations and concerns.

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

Line range hint 24-35: Consider implementing or documenting unimplemented methods

While not directly related to the current changes, I noticed that several methods in the MockChannels class are not implemented and throw fatal errors. This includes exists(_:), release(_:callback:), and release(_:).

To improve the robustness of your test suite and prevent unexpected crashes during testing, consider:

  1. Implementing these methods with mock behavior that suits your test scenarios.
  2. If these methods are intentionally left unimplemented, add comments explaining why and in which scenarios they're not needed.
  3. If these methods are truly not needed, consider removing them from the protocol or creating a separate protocol for the subset of methods you're actually using in tests.
Sources/AblyChat/Rooms.swift (2)

Line range hint 31-45: Critical: Update get method signature to match protocol

The get method implementation in DefaultRooms doesn't match the updated protocol signature. It should be marked as async.

Please update the method signature as follows:

- internal func get(roomID: String, options: RoomOptions) throws -> any Room {
+ internal func get(roomID: String, options: RoomOptions) async throws -> any Room {

This change is necessary to conform to the Rooms protocol and maintain consistency with the async nature of the operation.


Line range hint 1-45: Summary of changes and recommendations

  1. The Rooms protocol has been updated to make the get method async, which is a good improvement.
  2. The DefaultRooms implementation needs to be updated to match the new async signature of the get method in the Rooms protocol.
  3. A new ChatAPI parameter has been added when creating a DefaultRoom. More context is needed to understand the purpose and impact of this change.

Please address the inconsistency in the get method signature and provide more information about the ChatAPI addition. Once these issues are resolved, the changes look good overall.

Consider documenting the purpose of the ChatAPI in the codebase, especially its relationship with DefaultRoom. This will help maintain clarity as the project evolves.

Tests/AblyChatTests/Mocks/MockRealtime.swift (3)

12-12: LGTM! Consider adding a customizable clientId.

The change from throwing a fatalError to returning a hardcoded "mockClientId" is a good improvement. It allows tests to use the clientId property without causing a fatal error, which is more appropriate for a mock object.

Consider making the clientId customizable by initializing it in the constructor. This would provide more flexibility in testing different scenarios:

class MockRealtime: NSObject, RealtimeClientProtocol, Sendable {
    let clientId: String?
    
    init(clientId: String? = "mockClientId", channels: MockChannels = .init(channels: [])) {
        self.clientId = clientId
        self.channels = channels
    }
    
    // ... rest of the class
}

66-68: LGTM! Consider implementing a basic mock behavior.

The addition of the request method to match the RealtimeClientProtocol is appropriate. The current implementation throwing a fatalError is consistent with other unimplemented methods in this mock.

For future iterations, consider implementing a basic mock behavior for this method. This could involve:

  1. Storing the parameters for later assertion in tests.
  2. Allowing customization of the response in the mock's initializer.
  3. Calling the callback with a predefined response.

Here's a potential implementation:

class MockRealtime: NSObject, RealtimeClientProtocol, Sendable {
    var lastRequestParams: (method: String, path: String, params: [String: String]?, body: Any?, headers: [String: String]?)?
    var mockRequestResponse: (HTTPURLResponse, Data?)?

    func request(_ method: String, path: String, params: [String: String]?, body: Any?, headers: [String: String]?, callback: @escaping ARTHTTPPaginatedCallback) throws {
        lastRequestParams = (method, path, params, body, headers)
        if let (response, data) = mockRequestResponse {
            callback(response, data, nil)
        } else {
            callback(HTTPURLResponse(), nil, NSError(domain: "MockRealtime", code: 0, userInfo: [NSLocalizedDescriptionKey: "No mock response set"]))
        }
    }

    // ... rest of the class
}

This implementation would allow more flexible and thorough testing of code that uses this method.


Line range hint 1-68: Overall, good progress on the MockRealtime implementation.

The changes made to this mock object are appropriate and contribute to a more useful implementation for testing purposes. The clientId property now returns a value instead of throwing an error, and the new request method has been added to match the protocol requirements.

For future iterations, consider the following architectural improvements:

  1. Implement a strategy for customizing mock behaviors, possibly through dependency injection or a builder pattern.
  2. Add documentation comments to explain the purpose and usage of each mock method, especially for complex ones like request.
  3. Consider creating a separate configuration object for the mock to manage all customizable behaviors in one place.

These improvements would make the mock more flexible and easier to use in various test scenarios, enhancing the overall testability of the system.

Tests/AblyChatTests/MessageSubscriptionTests.swift (2)

35-35: LGTM: Updated MessageSubscription initialization

The MessageSubscription initialization has been correctly updated to include the new closure parameter. This change is consistent with the modifications to the MessageSubscription class.

Consider extracting the fatalError closure to a separate constant for better readability and reusability across tests. For example:

let notImplementedClosure: (Any) -> Void = { _ in fatalError("Not implemented") }
let subscription = MessageSubscription(bufferingPolicy: .unbounded, notImplementedClosure)

This would make the test setup more concise and easier to maintain.


Line range hint 45-53: LGTM: Effective test for getPreviousMessages

The mockGetPreviousMessages test method effectively verifies the getPreviousMessages functionality of MessageSubscription. The use of MockPaginatedResult and the type casting workaround are appropriate given the compiler limitations.

To improve clarity, consider adding a comment explaining why the type casting is necessary. For example:

// Type casting is needed to work around compiler limitations with parameterized protocol types
let resultAsConcreteType = try #require(result as? MockPaginatedResult<Message>)

This would help future developers understand the reason for this seemingly unusual code.

Tests/AblyChatTests/DefaultRoomsTests.swift (4)

9-9: LGTM! Consider extracting the channel name as a constant.

The addition of a specific channel configuration to the MockRealtime.create() call improves the test's realism and alignment with the actual implementation. This change is good and makes the test more robust.

To improve maintainability, consider extracting the channel name "basketball::$chat::$chatMessages" as a constant at the class level, as it might be used in other tests and could change in the future.

private let testChannelName = "basketball::$chat::$chatMessages"

Then use it in the test:

let realtime = MockRealtime.create(channels: .init(channels: [.init(name: testChannelName)]))

28-28: LGTM! Consider using the suggested constant for the channel name.

The change is consistent with the modification in the first test method, which is good for maintaining uniformity across tests.

As suggested for the previous test, consider using the extracted constant for the channel name here as well:

let realtime = MockRealtime.create(channels: .init(channels: [.init(name: testChannelName)]))

This will improve maintainability and ensure consistency across all tests.


46-46: LGTM! Great job on comprehensive test coverage.

The change is consistent with the modifications in the previous test methods, maintaining uniformity across the test suite. It's excellent to see thorough testing, including this important edge case for mismatched options.

As suggested for the previous tests, consider using the extracted constant for the channel name here as well:

let realtime = MockRealtime.create(channels: .init(channels: [.init(name: testChannelName)]))

This will improve maintainability and ensure consistency across all tests.


Line range hint 1-68: Overall, great improvements to the test suite!

The changes consistently enhance the specificity and realism of the tests across all methods. This is a positive step towards more robust and reliable testing.

To further improve the code, consider the following refactoring:

  1. Extract the channel name as a constant at the class level:

    private let testChannelName = "basketball::$chat::$chatMessages"
  2. Create a helper method for creating the MockRealtime instance:

    private func createMockRealtime() -> MockRealtime {
        return MockRealtime.create(channels: .init(channels: [.init(name: testChannelName)]))
    }
  3. Use this helper method in all test methods:

    let realtime = createMockRealtime()

This refactoring will centralize the MockRealtime creation logic, making it easier to maintain and update in the future if needed.

Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (3)

5-5: LGTM! Consider adding a comment explaining the purpose of properties.

The addition of the properties property and its initialization in the constructor are good changes that likely reflect updates in the actual RealtimeChannel class. This enhances the mock's fidelity to the real implementation.

Consider adding a brief comment explaining the purpose and usage of the properties property in this mock class. This would improve code readability and maintainability.

Also applies to: 17-17


Line range hint 20-45: Good implementation. Consider moving Counter to a separate file.

The Counter class is well-implemented with proper thread safety, which is crucial for potential use in concurrent test scenarios. The use of @unchecked Sendable is appropriate here.

Consider moving the Counter class to a separate file in your test utilities. This would improve reusability and keep the MockRealtimeChannel class focused on its primary responsibility. You could then import and use Counter where needed.

Example:

// TestUtilities/Counter.swift
public class Counter: @unchecked Sendable {
    // ... (existing implementation)
}

// MockRealtimeChannel.swift
import TestUtilities

final class MockRealtimeChannel: NSObject, RealtimeChannelProtocol {
    // ... (use Counter as before)
}

146-148: Good start. Consider enhancing the on method for more comprehensive mocking.

The change from fatalError to returning an ARTEventListener is an improvement, making the mock more functional.

To make this mock more useful for testing, consider enhancing the on method to capture the event and callback for later verification. This would allow tests to check if specific events were subscribed to with the correct callbacks. Here's a possible implementation:

class MockRealtimeChannel: NSObject, RealtimeChannelProtocol {
    // ...
    var capturedEvents: [(ARTChannelEvent, (ARTChannelStateChange) -> Void)] = []
    
    func on(_ event: ARTChannelEvent, callback: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener {
        capturedEvents.append((event, callback))
        return ARTEventListener()
    }
    // ...
}

This enhancement would allow tests to verify the correct usage of the on method in the code under test.

Tests/AblyChatTests/DefaultRoomTests.swift (1)

Line range hint 1-130: Summary of changes and suggestions for improvement

The changes in this file consistently add a chatAPI parameter to the DefaultRoom initializer across all test methods. While this change appears to be correctly implemented, there are a few points to consider for improving the test suite:

  1. Verify that the introduction of ChatAPI doesn't unintentionally alter the existing test behavior.
  2. Consider adding new test cases that specifically target the interaction between DefaultRoom and ChatAPI.
  3. Ensure that error handling and edge cases related to ChatAPI are adequately covered in the test suite.
  4. Review the need for mock objects for ChatAPI to allow for more granular control in testing.

To improve the overall test architecture, consider the following:

  1. Create a separate test file for ChatAPI-specific tests if it doesn't already exist.
  2. Implement a mock version of ChatAPI for more precise control over its behavior in tests.
  3. Add integration tests that cover the interaction between DefaultRoom and ChatAPI in various scenarios.

These changes will help ensure that the new ChatAPI functionality is thoroughly tested and that the existing tests remain valid with the new implementation.

Sources/AblyChat/Timeserial.swift (1)

16-91: Maintain consistency in documentation comments

The code contains comments like // Timeserial Protocol and // DefaultTimeserial Class, but DefaultTimeserial is a struct, not a class. For clarity and accuracy, update the comment to reflect that DefaultTimeserial is a struct.

Apply this diff:

-// DefaultTimeserial Class
+// DefaultTimeserial Struct
Sources/AblyChat/PaginatedResult.swift (2)

Line range hint 3-13: Remove Redundant Generic Parameter or Associated Type in Protocol

In the declaration of public protocol PaginatedResult<T>, you are using both a generic type parameter <T> and an associatedtype T. This is redundant and may cause confusion or compiler errors. Consider removing the generic type parameter and relying solely on the associatedtype T.

Apply this diff to fix the redundancy:

-public protocol PaginatedResult<T>: AnyObject, Sendable {
-    associatedtype T
+public protocol PaginatedResult: AnyObject, Sendable {
+    associatedtype T

Line range hint 8-8: Address the TODO Comment Regarding Unwrapping

There's a TODO note to consider how to avoid the need for an unwrap in the next property. I can help address this issue. Would you like me to suggest a solution or open a GitHub issue to track this task?

Example/AblyChatExample/MessageDemoView.swift (1)

31-31: Remove unnecessary empty else block

The else block at line 31 is empty and can be removed to clean up the code.

Apply this diff to remove the empty else block:

-                } else {}
Sources/AblyChat/ChatAPI.swift (3)

61-61: Ensure consistent naming for roomId

In the Message initializer, you're using roomID while the variable is roomId. To maintain consistency and adhere to Swift naming conventions, consider using roomId throughout the codebase.


75-75: Address the TODO comment for occupancy/presence improvements

There's a TODO comment indicating that this function should be improved as part of occupancy/presence work.

Would you like assistance in implementing these improvements or opening a GitHub issue to track this task?


86-89: Provide a more descriptive error when response items are missing

When paginatedResponse?.items.first is nil, the function throws an unknown error. Providing a more descriptive error message would aid in debugging.

Consider throwing an error that clearly indicates the absence of items in the response, such as:

continuation.resume(throwing: ARTErrorInfo.create(withCode: 40002, message: "No items found in the response"))
Sources/AblyChat/DefaultMessages.swift (1)

125-125: Remove unused parameter roomId

The parameter roomId is not used in the function handleChannelEvents. Consider removing it to clean up the code.

Apply this diff:

-private func handleChannelEvents(roomId _: String) {
+private func handleChannelEvents() {

Ensure to update any calls to this function accordingly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7059ef1 and d459edf.

📒 Files selected for processing (30)
  • AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved (1 hunks)
  • Example/AblyChatExample.xcodeproj/project.pbxproj (1 hunks)
  • Example/AblyChatExample/AblyChatExampleApp.swift (1 hunks)
  • Example/AblyChatExample/ContentView.swift (0 hunks)
  • Example/AblyChatExample/MessageDemoView.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockRealtime.swift (3 hunks)
  • Package.resolved (1 hunks)
  • Package.swift (2 hunks)
  • [email protected] (1 hunks)
  • Sources/AblyChat/ChatAPI.swift (1 hunks)
  • Sources/AblyChat/DefaultMessages.swift (1 hunks)
  • Sources/AblyChat/Dependencies.swift (1 hunks)
  • Sources/AblyChat/EmitsDiscontinuities.swift (1 hunks)
  • Sources/AblyChat/Events.swift (1 hunks)
  • Sources/AblyChat/Headers.swift (1 hunks)
  • Sources/AblyChat/Message.swift (2 hunks)
  • Sources/AblyChat/Messages.swift (3 hunks)
  • Sources/AblyChat/Metadata.swift (1 hunks)
  • Sources/AblyChat/Occupancy.swift (1 hunks)
  • Sources/AblyChat/PaginatedResult.swift (2 hunks)
  • Sources/AblyChat/Room.swift (2 hunks)
  • Sources/AblyChat/Rooms.swift (1 hunks)
  • Sources/AblyChat/Timeserial.swift (1 hunks)
  • Sources/AblyChat/Version.swift (1 hunks)
  • Tests/AblyChatTests/DefaultRoomTests.swift (4 hunks)
  • Tests/AblyChatTests/DefaultRoomsTests.swift (3 hunks)
  • Tests/AblyChatTests/MessageSubscriptionTests.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockChannels.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtime.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (3 hunks)
💤 Files with no reviewable changes (1)
  • Example/AblyChatExample/ContentView.swift
✅ Files skipped from review due to trivial changes (2)
  • Example/AblyChatExample.xcodeproj/project.pbxproj
  • Package.resolved
🧰 Additional context used
📓 Learnings (2)
Example/AblyChatExample/AblyChatExampleApp.swift (1)
Learnt from: maratal
PR: ably-labs/ably-chat-swift#34
File: Example/AblyChatExample/ContentView.swift:35-118
Timestamp: 2024-09-22T21:32:01.793Z
Learning: Skip code review suggestions for `ContentView.swift` in the `Example/AblyChatExample` project.
Tests/AblyChatTests/MessageSubscriptionTests.swift (1)
Learnt from: maratal
PR: ably-labs/ably-chat-swift#34
File: Example/AblyChatExample/Mocks/MockSubscription.swift:0-0
Timestamp: 2024-09-22T20:24:30.986Z
Learning: In mock implementations of `MockSubscription`, adding optional parameters for buffering policies is unnecessary.
🔇 Additional comments (36)
Example/AblyChatExample/AblyChatExampleApp.swift (1)

7-7: LGTM! Please provide more context about MessageDemoView.

The change from ContentView to MessageDemoView as the main view of the app aligns with the PR objectives of addressing the divergence between macOS and iOS features. This modification likely represents the new UI for the chat functionality mentioned in the PR summary.

To ensure that MessageDemoView is properly implemented and imported, could you please run the following verification script?

Additionally, could you provide more information about MessageDemoView? Specifically:

  1. Does it implement the OS version checking mentioned in the PR objectives?
  2. How does it differ from the previous ContentView in terms of functionality?
  3. Are there any new public APIs or changes to existing ones introduced with this view?

This information will help ensure that the implementation aligns with the PR objectives and maintains the expected functionality.

✅ Verification successful

Verified! MessageDemoView is properly implemented with appropriate OS version checks.

The verification confirms that MessageDemoView is correctly defined and includes necessary compiler directives for OS-specific functionality as outlined in the PR objectives.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation and import of MessageDemoView

# Test 1: Check if MessageDemoView is defined in the project
echo "Searching for MessageDemoView definition:"
rg --type swift "struct\s+MessageDemoView\s*:\s*View" Example/AblyChatExample

# Test 2: Check if MessageDemoView is imported (in case it's in a separate module)
echo "Checking for MessageDemoView import:"
rg --type swift "import.*MessageDemoView" Example/AblyChatExample

# Test 3: Check for any compiler directives related to OS version
echo "Checking for OS version related compiler directives:"
rg --type swift "#if.*os\(" Example/AblyChatExample

Length of output: 649

Sources/AblyChat/Headers.swift (2)

3-3: Excellent addition of Codable and Hashable conformance!

The addition of Codable and Hashable conformance to the HeadersValue enum is a positive change. This enhancement allows for easier serialization/deserialization (Codable) and enables the use of HeadersValue in sets or as dictionary keys (Hashable). These additions improve the versatility and usability of the enum without introducing any apparent issues.


5-5: Approved: Simplified number case, but verify impact.

The change from NSNumber to Int for the number case is a good simplification that aligns better with Swift's native types. However, this modification may have some implications:

  1. Existing code relying on NSNumber functionality might need updates.
  2. The ability to represent floating-point numbers could be limited now.

Please ensure that this change doesn't negatively impact other parts of the codebase that might be using the number case.

Let's verify the usage of the number case in the codebase:

✅ Verification successful

Verification Complete: No Issues Found

The change from NSNumber to Int for the number case in HeadersValue does not impact other parts of the codebase. No usages of HeadersValue.number were found outside its definition, ensuring that this modification is safe and aligns with Swift's native types.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usages of HeadersValue.number case in the codebase

# Search for HeadersValue.number usage
echo "Searching for HeadersValue.number usage:"
rg --type swift "HeadersValue\.number" -C 3

# Search for any HeadersValue enum usage
echo "\nSearching for any HeadersValue enum usage:"
rg --type swift "HeadersValue" -C 3

Length of output: 1026

Sources/AblyChat/Occupancy.swift (1)

9-9: Approved: Enhanced serialization capabilities for OccupancyEvent

The addition of Encodable and Decodable conformance to the OccupancyEvent struct is a positive change. This enhancement allows for easier serialization and deserialization of the struct, which can be particularly useful for data persistence, network communication, or integration with other parts of the system that work with serialized data formats like JSON.

This change:

  1. Improves the versatility of the OccupancyEvent struct.
  2. Aligns with best practices for data structures that may need to be converted to/from various formats.
  3. Doesn't introduce any breaking changes to the existing functionality.
Sources/AblyChat/EmitsDiscontinuities.swift (2)

Line range hint 1-5: LGTM: EmitsDiscontinuities protocol looks good

The EmitsDiscontinuities protocol is well-defined and its purpose is clear. It provides a method for subscribing to discontinuity events, which aligns with the protocol's name and intended use.


7-20: LGTM: HandlesDiscontinuity protocol is well-designed

The HandlesDiscontinuity protocol is well-structured and its purpose is clear. The use of @MainActor is appropriate for ensuring thread safety when dealing with potential UI updates.

However, I have a question about the commented-out line:

Could you explain the reasoning behind changing channel from an optional (RealtimeChannelProtocol?) to a non-optional type? This change might impact how conforming types handle cases where a channel might not be immediately available.

To verify the impact of this change, let's check for any uses of this protocol:

✅ Verification successful

Verified: Change to channel property is consistent across conforming types

The update to make the channel property non-optional in the HandlesDiscontinuity protocol is consistently applied in all conforming types, specifically in the DefaultMessages class. No other conformers were found that might be affected by this change.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for uses of HandlesDiscontinuity protocol
rg --type swift "HandlesDiscontinuity" -C 5

Length of output: 1693

Sources/AblyChat/Metadata.swift (1)

1-11: Overall assessment: Good improvements with minor suggestions

The changes in this file significantly enhance type safety and clarity in metadata representation. The introduction of the MetadataValue enum and the updated Metadata typealias are well-thought-out improvements.

The suggestions provided (considering Double for numbers, adding documentation comments) are minor and aimed at further improving code clarity and flexibility.

These changes align well with the PR objectives of enhancing code organization and addressing type-related concerns.

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

Line range hint 1-35: Overall assessment: Changes are acceptable with minor improvements suggested

The addition of the new get method with options parameter improves the mock's compatibility with the real implementation. The changes are minimal and don't introduce any breaking changes.

However, there are opportunities for improvement:

  1. Consider handling or documenting the unused options parameter in the new method.
  2. Address the unimplemented methods in the class to improve the overall robustness of the mock.

These suggestions will enhance the clarity and completeness of the mock implementation, leading to more reliable and maintainable tests.

Sources/AblyChat/Rooms.swift (2)

Line range hint 4-4: Approved: Async update to get method

The change to make the get method async is a good improvement, aligning with modern Swift concurrency practices. This change enhances the protocol's flexibility for asynchronous operations.

To ensure this change doesn't break existing implementations, please run the following script:

#!/bin/bash
# Description: Check for any non-async implementations of the `get` method in `Rooms` protocol conformances

# Test: Search for non-async implementations of `get` method
rg --type swift 'class.*:.*Rooms.*\{' -A 10 | rg 'func\s+get.*\{' | rg -v 'async'

If this script returns any results, those implementations need to be updated to match the new async signature.


42-42: Clarification needed: Addition of ChatAPI

The addition of ChatAPI as a parameter when creating a DefaultRoom seems to be a significant change. Could you provide more context on why this was added and how it relates to the PR objectives?

To understand the impact of this change, let's check how ChatAPI is used in DefaultRoom:

This will help us understand how ChatAPI is integrated into DefaultRoom and ensure it's being used correctly.

✅ Verification successful

The addition of ChatAPI is verified and aligns with the PR objectives.
ChatAPI is correctly integrated into DefaultRoom, ensuring enhanced chat functionalities and improved testability.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of ChatAPI in DefaultRoom

# Test: Search for ChatAPI usage in DefaultRoom
rg --type swift 'class DefaultRoom' -A 20 | rg 'ChatAPI'

Length of output: 81


Script:

#!/bin/bash
# Description: Search for all occurrences of ChatAPI in the Swift codebase

rg --type swift 'ChatAPI'

Length of output: 1451


Script:

#!/bin/bash
# Description: Search for all DefaultRoom initializations with ChatAPI parameter

rg --type swift 'DefaultRoom\(' | rg 'chatAPI: ChatAPI'

Length of output: 1005

Package.swift (1)

8-8: Verify the macOS version update.

The macOS platform version has been updated from .v11 to .v12. This change increases the minimum required macOS version for the package, which may break compatibility with older macOS versions.

However, this seems inconsistent with the PR objective of "not supporting macOS 16". Could you please clarify the reasoning behind this change and confirm if this aligns with the intended macOS support strategy?

AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved (1)

2-2: LGTM: Updated originHash reflects changes in dependencies

The originHash update is consistent with the modifications made to the package dependencies, particularly the changes to the ably-cocoa package.

[email protected] (6)

1-1: LGTM: Swift tools version and package name are appropriate.

The Swift tools version is set to 6.0, which is up-to-date and allows for the use of the latest Swift features. The package name "AblyChat" correctly matches the project name mentioned in the PR objectives.

Also applies to: 5-6


7-11: Verify platform versions and tvOS support.

The supported platforms (macOS v12, iOS v14, tvOS v14) seem to have lower minimum versions than what's mentioned in the PR objectives (which target iOS 16). Additionally, tvOS support is included, which wasn't mentioned in the PR objectives.

Please confirm if these platform versions are intentional and if tvOS support is required for this project.


12-19: LGTM: Product definition is correct.

The library product "AblyChat" is properly defined and targets the "AblyChat" module. This aligns with the package name and structure.


25-28: Clarify the need for swift-argument-parser.

The inclusion of swift-argument-parser suggests command-line functionality, which wasn't mentioned in the PR objectives. Could you please clarify the intended use of this dependency in the project?


54-66: Clarify the purpose of the BuildTool target.

An executable target named "BuildTool" is defined, which depends on ArgumentParser and AsyncAlgorithms. The purpose of this target and its relation to the main AblyChat functionality is not clear from the PR objectives or comments. Could you please provide more information about the intended use of this BuildTool and why it's needed in the package?


1-68: Summary of Package.swift review

Overall, the package definition is well-structured and aligns with the project goals. However, there are a few points that require attention:

  1. Verify the minimum platform versions, especially for iOS, and confirm if tvOS support is intended.
  2. Consider using a specific version or tag for the ably-cocoa dependency instead of the main branch.
  3. Clarify the need for the swift-argument-parser dependency and its intended use in the project.
  4. Provide more information about the purpose of the BuildTool target and its relation to the main AblyChat functionality.

Addressing these points will improve the package definition's clarity and stability. Once these are resolved, the package structure looks good to proceed with implementation.

Tests/AblyChatTests/MessageSubscriptionTests.swift (3)

Line range hint 5-19: LGTM: Well-structured mock class for testing

The MockPaginatedResult class is well-implemented for testing purposes. It correctly conforms to the PaginatedResult protocol and uses fatalError for unimplemented methods, which is a good practice for mock objects. The generic type parameter T provides flexibility for testing different types of paginated results.


Line range hint 26-31: LGTM: Well-structured test method

The withMockAsyncSequence test method is well-implemented. It effectively tests the MessageSubscription using a mock async sequence and verifies the expected output. The use of #expect for assertion is appropriate.


Line range hint 1-53: Overall: Well-structured and comprehensive test suite

The changes in this file enhance the test coverage for the MessageSubscription class. The addition of the MockPaginatedResult class and the updates to the test methods, particularly the emit method, align well with the changes in the main implementation.

Key points:

  1. The new MockPaginatedResult class provides a solid foundation for testing paginated results.
  2. The updated emit test method correctly incorporates the new closure parameter in MessageSubscription initialization.
  3. The mockGetPreviousMessages test effectively handles compiler limitations with parameterized protocol types.

These changes contribute to a more robust and comprehensive test suite for the MessageSubscription functionality.

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

Line range hint 1-224: Overall, good progress on enhancing the mock implementation.

The changes to MockRealtimeChannel align well with the PR objectives of improving code organization and preparing for future implementations. The addition of the properties property, the Counter class, and the functional on method are all positive steps towards a more realistic and useful mock.

To further improve this implementation:

  1. Consider adding comments to explain the purpose of new properties and methods.
  2. Move the Counter class to a separate file for better reusability.
  3. Enhance the on method to capture events and callbacks for more comprehensive testing capabilities.

These changes will make your tests more robust and the mock more versatile for future development.

Tests/AblyChatTests/DefaultRoomTests.swift (4)

53-53: Consistent change across test methods

The addition of the chatAPI parameter is consistently applied here as well.

Please refer to the previous comment regarding the verification and potential improvements related to this change.


115-115: Verify error handling with new chatAPI parameter

The addition of the chatAPI parameter is consistently applied in this test method for handling detach failures.

Please ensure that the error handling behavior is not affected by the introduction of the chatAPI parameter. Consider adding specific test cases to verify how errors from ChatAPI are propagated and handled in the DefaultRoom class.

To verify the error handling, run the following script:

#!/bin/bash
# Description: Verify the error handling in DefaultRoom related to ChatAPI

# Test: Check if there's error handling related to chatAPI in DefaultRoom
ast-grep --lang swift --pattern 'class DefaultRoom {
  $$$
  func $methodName($_) {
    $$$
    do {
      $$$
      $chatAPI.$_
      $$$
    } catch {
      $$$
    }
    $$$
  }
  $$$
}'

# Test: Check if there are any specific error types related to ChatAPI
ast-grep --lang swift --pattern 'enum $ErrorType: Error {
  $$$
  case $chatAPIError($_)
  $$$
}'

82-82: Verify detach behavior with new chatAPI parameter

The addition of the chatAPI parameter is consistently applied in this test method for detaching channels.

Please ensure that the detach behavior is not affected by the introduction of the chatAPI parameter. Consider adding specific test cases to verify the interaction between DefaultRoom, ChatAPI, and the detach process.

To verify the detach behavior, run the following script:


20-20: Verify the impact of adding chatAPI parameter

The addition of the chatAPI parameter to the DefaultRoom initializer is noted. This change introduces a new dependency in the test setup.

Please consider the following:

  1. Ensure that this change doesn't alter the behavior being tested in this specific test case.
  2. Verify if mock objects for ChatAPI are needed for more granular control in testing.
  3. Consider adding test cases that specifically target the interaction between DefaultRoom and ChatAPI.

To verify the usage of ChatAPI in the DefaultRoom class, run the following script:

Sources/AblyChat/Dependencies.swift (1)

18-18: Ensure consistent method signatures in protocol hierarchy

The added method func get(_ name: String, options: ARTRealtimeChannelOptions) -> Channel in RealtimeChannelsProtocol overloads the existing get(_ name: String) -> Channel method. Please ensure that all conforming types implement this new method and that overloading does not introduce ambiguity in method resolution.

Consider reviewing the implementations to confirm that they conform to the updated protocol.

Sources/AblyChat/Message.swift (1)

10-13: Verify handling of optional clientID and createdAt properties

Changing clientID and createdAt to optional types may impact code that assumes these values are always present. Please ensure that all usages of these properties throughout the codebase correctly handle nil values to prevent potential runtime errors.

Sources/AblyChat/Room.swift (3)

23-25: LGTM!

The addition of chatAPI and _messages properties is appropriate and correctly implemented.


39-51: Initializer updated correctly with chatAPI parameter.

The chatAPI parameter is properly added to the initializer, and the properties are correctly initialized.


55-55: Implemented messages property correctly.

The messages property now returns the _messages instance, fulfilling the protocol requirement.

Example/AblyChatExample/Mocks/MockRealtime.swift (3)

1-1: Verify the use of @preconcurrency with the Ably import

The @preconcurrency attribute suppresses concurrency warnings for the imported module. While this is acceptable as a temporary measure, consider ensuring that the Ably library and its types are fully compatible with Swift concurrency to leverage concurrency features safely.


45-46: Ensure thread safety of the properties property

Introducing let properties: ARTChannelProperties adds state to the Channel class. Verify that ARTChannelProperties is immutable or accessed safely in concurrent contexts to prevent data races or inconsistent state.


47-49: Validate the initializer for Channel

The initializer accepts ARTChannelProperties. Ensure that initializing with these properties doesn't introduce concurrency issues, especially if the properties contain mutable state.

Sources/AblyChat/DefaultMessages.swift (2)

88-90: ⚠️ Potential issue

Avoid using fatalError in public methods

Using fatalError("not implemented") in a public method can cause the application to crash at runtime. If the method is not implemented yet, consider implementing it or, if it's a placeholder for future functionality, throw an appropriate error or mark the method as unavailable using @available(*, unavailable).

⛔ Skipped due to learnings
Learnt from: lawrence-forooghian
PR: ably-labs/ably-chat-swift#33
File: Sources/AblyChat/Rooms.swift:9-64
Timestamp: 2024-08-29T10:55:26.337Z
Learning: When reviewing code, do not ask lawrence-forooghian about "not yet implemented" `fatalError` calls.

171-179: Handle missing channelSerial appropriately

In resolveSubscriptionStart, there is a possibility that channel.properties.channelSerial is nil even when the channel is in the attached state. Currently, an error is thrown. Ensure that this scenario is handled gracefully, possibly with a fallback mechanism or a more descriptive error message to aid debugging.

Run the following script to check how often channelSerial is nil when the channel is attached:

✅ Verification successful

Handling of missing channelSerial is appropriate

The resolveSubscriptionStart function correctly checks if channelSerial is nil and throws a descriptive error when necessary. This ensures that the scenario where channelSerial is missing is handled gracefully, aiding in debugging.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify occurrences where channelSerial is nil when channel is attached.

# Test: Search for instances accessing channelSerial and check for nil checks.
# Expect: Proper handling when channelSerial is nil.

rg --type swift 'channel\.properties\.channelSerial' -A 5

Length of output: 583

Sources/AblyChat/Version.swift Show resolved Hide resolved
Sources/AblyChat/Version.swift Show resolved Hide resolved
Sources/AblyChat/Version.swift Outdated Show resolved Hide resolved
Tests/AblyChatTests/Mocks/MockChannels.swift Show resolved Hide resolved
Package.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Copy link

@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: 9

🧹 Outside diff range and nitpick comments (10)
Sources/AblyChat/PaginatedResult.swift (3)

Line range hint 3-13: Consider addressing the TODO comment for unwrapping

The TODO comment suggests reconsidering the need for unwrapping. Consider exploring alternatives that don't require unwrapping, such as using a non-optional type or providing a default value. This could improve the API's ease of use and reduce the potential for runtime errors.

Would you like assistance in exploring alternative designs that don't require unwrapping?


15-46: Approved with a suggestion for improved clarity

The ARTHTTPPaginatedCallbackWrapper implementation looks good. It effectively handles error cases and decodes the response as expected.

To improve clarity, consider adding a brief comment explaining what CHA-M5i and CHA-M6b refer to. This would help other developers understand the context of these requirements without needing to look them up externally.


73-82: LGTM with a suggestion for potential refactoring

The first property is correctly implemented, mirroring the structure of the next property. This consistency is good for maintainability.

Consider refactoring the next and first properties to reduce code duplication. You could create a private method that takes a closure parameter to differentiate between next and first calls, which both properties could then use.

Sources/AblyChat/Messages.swift (2)

47-75: Approved with suggestion: toDictionary() method added to QueryOptions

The addition of the toDictionary() method is a good improvement, providing a convenient way to convert QueryOptions to a dictionary format suitable for API requests. The handling of all properties, including the new timeSerial, is comprehensive.

Suggestion for improvement:
Consider using a more type-safe approach for the start and end properties. Currently, they are converted to strings using string interpolation, which might not provide the desired format for API requests.

Consider updating the handling of start and end properties as follows:

 if let start {
-    dict["start"] = "\(start)"
+    dict["start"] = ISO8601DateFormatter().string(from: start)
 }

 if let end {
-    dict["end"] = "\(end)"
+    dict["end"] = String(end)
 }

This change ensures that the start date is properly formatted as an ISO8601 string, and the end timeserial is correctly converted to a string.


Line range hint 1-128: Overall: Significant improvements with some follow-up tasks

The changes in this file represent a substantial improvement to the messaging system's design, particularly in terms of flexibility, consistency, and error handling. Key improvements include:

  1. Adding @MainActor to the Messages protocol.
  2. Making the subscribe method asynchronous and error-throwing.
  3. Updating QueryOptions to use timeserials.
  4. Adding a toDictionary() method to QueryOptions.
  5. Refactoring MessageSubscription to use a more flexible design.

These changes will likely require updates in implementing classes and calling code throughout the project.

Next steps:

  1. Address the TODO regarding the representation of start and end dates in QueryOptions.
  2. Ensure all conforming types and usages are updated to reflect these changes.
  3. Consider adding more comprehensive error handling, especially for the new asynchronous methods.
  4. Update documentation to reflect these API changes.

To facilitate a smooth transition and ensure consistency across the codebase, consider creating a migration guide for these changes. This guide could include:

  1. A summary of the API changes.
  2. Examples of how to update existing code to use the new APIs.
  3. Any necessary steps for error handling with the new asynchronous methods.
  4. Best practices for working with the updated QueryOptions and MessageSubscription structs.

This will help team members understand and implement these changes consistently across the project.

Sources/AblyChat/DefaultMessages.swift (5)

1-11: LGTM with a minor suggestion

The imports, typealias, and struct definitions look good and serve their purpose well. However, I have one minor suggestion:

Consider capitalizing the FromSerial typealias name to FromSerial to follow Swift naming conventions for types.

-private typealias FromSerial = String
+private typealias FromSerial = String

12-32: LGTM with a suggestion for event handling

The DefaultMessages class structure and initialization look good. However, I have a suggestion regarding the channel event handling:

Consider implementing an explicit start() method instead of using an implicit Task in the initializer. This approach would give more control to the caller and make the lifecycle of the event handling more clear. For example:

public func start() async {
    await handleChannelEvents(roomId: roomID)
}

This way, the caller can decide when to start handling events, which might be beneficial in certain scenarios.


34-77: LGTM with a suggestion for error handling

The subscribe method implementation looks good and follows the specified requirements. The use of weak self in closures is a good practice to prevent retain cycles.

Consider adding explicit error handling for the case when resolveSubscriptionStart() fails. You could wrap the call in a do-catch block and handle any potential errors. For example:

do {
    let timeserial = try await resolveSubscriptionStart()
    // ... rest of the method
} catch {
    throw MessagesError.failedToResolveSubscriptionStart(error)
}

This would provide more context if the subscription start resolution fails.


126-152: LGTM with suggestions for error handling

The handleChannelEvents method is well-implemented and follows the specified requirements. The use of weak self in closures is a good practice to prevent retain cycles.

Consider improving the error handling to provide more context. Instead of throwing the error directly, you could wrap it in a custom error type that provides more information about where the error occurred. For example:

enum ChannelEventError: Error {
    case attachFailure(underlying: Error)
    case updateFailure(underlying: Error)
}

// In the error handling blocks:
catch {
    throw ChannelEventError.attachFailure(underlying: error)
}

// And for the update event:
catch {
    throw ChannelEventError.updateFailure(underlying: error)
}

This would provide more context about where the error occurred when it's caught higher up in the call stack.


154-170: LGTM with suggestions for error handling

The handleAttach method is well-implemented and follows the specified requirements. It correctly handles the case when the channel is attached and updates the subscription points appropriately.

Similar to the previous suggestion, consider improving the error handling to provide more context. You could create a specific error type for attach-related errors:

enum AttachError: Error {
    case subscriptionStartFailure(underlying: Error)
}

// In the error handling block:
catch {
    throw AttachError.subscriptionStartFailure(underlying: error)
}

This would provide more specific information about the nature of the error when it's caught and handled elsewhere in the code.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d459edf and 3d8892c.

📒 Files selected for processing (7)
  • Sources/AblyChat/ChatAPI.swift (1 hunks)
  • Sources/AblyChat/DefaultMessages.swift (1 hunks)
  • Sources/AblyChat/Dependencies.swift (1 hunks)
  • Sources/AblyChat/Message.swift (2 hunks)
  • Sources/AblyChat/Messages.swift (3 hunks)
  • Sources/AblyChat/PaginatedResult.swift (2 hunks)
  • Sources/AblyChat/Timeserial.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Sources/AblyChat/ChatAPI.swift
  • Sources/AblyChat/Dependencies.swift
🧰 Additional context used
📓 Learnings (3)
Sources/AblyChat/DefaultMessages.swift (1)
Learnt from: lawrence-forooghian
PR: ably-labs/ably-chat-swift#67
File: Sources/AblyChat/RoomLifecycleManager.swift:134-137
Timestamp: 2024-10-01T20:11:26.655Z
Learning: In `RoomLifecycleManager`, capturing `self` as `[weak self]` in closures is acceptable because if `self` is deallocated, missing state changes is acceptable since there's no object to process them.
Sources/AblyChat/Messages.swift (1)
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/Messages.swift:104-106
Timestamp: 2024-10-02T23:37:00.308Z
Learning: In `MessageSubscription`, `getPreviousMessages` should be non-optional.
Sources/AblyChat/PaginatedResult.swift (3)
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:64-66
Timestamp: 2024-10-02T23:40:28.836Z
Learning: When handling closures passed to `paginatedResponse.next` in `PaginatedResultWrapper`, it is unnecessary to capture `paginatedResponse` weakly since it is provided by the method and not retained by `self`; therefore, there is no risk of a retain cycle.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:24-25
Timestamp: 2024-10-02T23:39:04.481Z
Learning: The syntax `if let variable {` is valid in newer versions of Swift and should not be flagged as incorrect.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:29-29
Timestamp: 2024-10-02T23:38:51.440Z
Learning: In Swift, the guard statement can use shorthand optional binding syntax like `guard let variable else { ... }` without needing to assign `variable = variable`.
🔇 Additional comments (21)
Sources/AblyChat/Timeserial.swift (4)

4-13: LGTM: Well-designed Timeserial protocol

The Timeserial protocol is well-structured with appropriate properties and comparison methods. The Sendable conformance is a good choice for thread safety.


16-27: LGTM: DefaultTimeserial struct implementation

The DefaultTimeserial struct correctly implements the Timeserial protocol. The private initializer suggests a controlled instance creation process, likely through the calculateTimeserial method.


77-90: LGTM: Concise comparison methods

The before, after, and equal methods are implemented correctly and concisely using the timeserialCompare method. This approach maintains consistency with other SDKs.


1-96: LGTM: Well-structured and organized implementation

The overall structure of the file is clean, logical, and easy to follow. The use of internal access levels is appropriate for a library, and the file name accurately reflects its contents. Good job on maintaining a clear and organized codebase.

Sources/AblyChat/PaginatedResult.swift (4)

48-60: LGTM: Well-structured implementation of PaginatedResultWrapper

The PaginatedResultWrapper class is well-implemented. It correctly conforms to the PaginatedResult protocol and encapsulates the necessary properties. The use of final is a good choice for preventing unintended subclassing and potential performance improvements.


62-71: LGTM: Correct implementation of asynchronous 'next' property

The next property is well-implemented. It correctly uses withCheckedThrowingContinuation to handle the asynchronous nature of fetching the next page. The use of ARTHTTPPaginatedCallbackWrapper ensures consistent error handling and response processing.


84-87: LGTM: Correct implementation of 'current' property

The current property is correctly implemented. Returning self is the appropriate way to provide access to the current page, as the PaginatedResultWrapper instance already represents the current page of results.


90-95: LGTM: Useful extension for ARTHTTPPaginatedResponse

The private extension on ARTHTTPPaginatedResponse is a good addition. The toPaginatedResult method provides a clean and convenient way to convert an ARTHTTPPaginatedResponse to a PaginatedResultWrapper. The private access level is appropriate for this internal implementation detail.

Sources/AblyChat/Messages.swift (5)

9-9: Approved: channel property type updated to RealtimeChannelProtocol

The change from ARTRealtimeChannelProtocol to RealtimeChannelProtocol for the channel property type is a good move towards using a more generic protocol.

Could you please provide more information about the RealtimeChannelProtocol? Specifically:

  1. Is it a custom protocol or part of a library?
  2. Does it provide all the necessary functionality that ARTRealtimeChannelProtocol did?
  3. Are there any implications for existing implementations of the Messages protocol?

24-37: Approved with suggestions: QueryOptions struct updates

The changes to the QueryOptions struct, including the update of the end property to Int? and the addition of the timeSerial property, align with the goal of using timeserials for representation. However, there are a few points to address:

  1. The TODO comment suggests that this is a temporary solution. Could you provide an estimated timeline for revisiting this issue?
  2. The start property is still of type Date?, while end is now Int?. This inconsistency might lead to confusion. Consider updating start to Int? as well for consistency, or provide a clear explanation for the difference.
  3. The comment for timeSerial mentions it's used for the subscriber's subscription point. It might be helpful to add a brief explanation of how this is used in the codebase.

To ensure that these changes are properly handled throughout the codebase, please run the following script to find usages of QueryOptions that might need updating:

#!/bin/bash
# Find usages of QueryOptions
rg --type swift 'QueryOptions' -C 2

Review the results to ensure that all usages of QueryOptions are updated to handle the new property types correctly.

Also applies to: 39-44


85-100: Approved: MessageSubscription struct updates

The changes to the MessageSubscription struct, including the addition of the getPreviousMessages property and the updates to the initializers, improve the flexibility and consistency of the API. The use of QueryOptions instead of QueryOptionsWithoutDirection is a good simplification.

To ensure that these changes are properly implemented across the codebase, please run the following script to check for any remaining uses of QueryOptionsWithoutDirection or old-style initializations of MessageSubscription:

#!/bin/bash
# Find any remaining uses of QueryOptionsWithoutDirection
rg --type swift 'QueryOptionsWithoutDirection'

# Find initializations of MessageSubscription
rg --type swift 'MessageSubscription\(' -C 2

Review the results to ensure that all usages of MessageSubscription are updated to use the new initializer signature and QueryOptions.

Also applies to: 107-108


107-108: Approved: getPreviousMessages method updated

The update to the getPreviousMessages method, using QueryOptions and simplifying the method body, is a good improvement that aligns with the other changes in the MessageSubscription struct.

To ensure that this change is properly handled throughout the codebase, please run the following script to find calls to getPreviousMessages that might need updating:

#!/bin/bash
# Find calls to getPreviousMessages
rg --type swift 'getPreviousMessages\(' -C 2

Review the results to ensure that all calls to getPreviousMessages are updated to use QueryOptions instead of QueryOptionsWithoutDirection.


6-6: Approved: subscribe method updated to async throws

The subscribe method has been updated to be asynchronous and throw errors, which is a good improvement for handling potential asynchronous operations and error cases during subscription.

To ensure that this change is properly implemented across the codebase, please run the following script to check for any remaining synchronous subscribe method calls that need to be updated:

Make sure to update any calls to this method to use await and handle potential errors.

Sources/AblyChat/DefaultMessages.swift (3)

79-86: LGTM

The get and send methods are implemented correctly and follow the specified requirements. They appropriately delegate the work to the chatAPI object, which keeps the DefaultMessages class focused on its responsibilities.


97-124: LGTM

The getBeforeSubscriptionStart method is well-implemented and follows the specified requirements. It includes appropriate error handling for invalid subscription points and end times, and correctly sets up the query options as per the specifications.


172-230: LGTM

The resolveSubscriptionStart and subscribeAtChannelAttach methods are well-implemented and follow the specified requirements. The error handling is appropriate and provides clear error messages. The use of continuations in subscribeAtChannelAttach is a good approach for handling the asynchronous nature of channel attachment.

The implementation correctly handles different channel states and provides appropriate error messages for each case. The use of withCheckedThrowingContinuation is a good choice for managing the asynchronous channel attachment process.

Sources/AblyChat/Message.swift (5)

6-7: Clear and Informative Documentation

The added comments provide a clear explanation of the Message struct and its conformance to Hashable, enhancing code readability and maintainability.


8-8: Correct Protocol Conformances

Adding Codable and Hashable conformances is appropriate for serialization and compatibility with SwiftUI components like ForEach.


10-13: Appropriate Use of Optional Properties

Changing clientID and createdAt to optional types allows the Message struct to represent messages where these values may not be present, improving flexibility.


Line range hint 17-24: Initializer Matches Updated Properties

The initializer correctly reflects the optional nature of clientID and createdAt, ensuring proper initialization of Message instances.


27-34: Consistent Coding Keys

The CodingKeys enum accurately maps the property names to their JSON counterparts, maintaining consistency between the model and its serialized form.

Sources/AblyChat/Timeserial.swift Show resolved Hide resolved
Sources/AblyChat/Messages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Message.swift Show resolved Hide resolved
Sources/AblyChat/Message.swift Show resolved Hide resolved
Sources/AblyChat/Message.swift Show resolved Hide resolved
Copy link

@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: 4

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3d8892c and 97ee439.

📒 Files selected for processing (1)
  • Sources/AblyChat/Timeserial.swift (1 hunks)
🔇 Additional comments (3)
Sources/AblyChat/Timeserial.swift (3)

3-12: LGTM: Well-designed Timeserial protocol

The Timeserial protocol is well-structured with appropriate properties and comparison methods. The use of the Sendable protocol is a good choice for ensuring thread safety.


14-25: LGTM: Well-implemented DefaultTimeserial struct

The DefaultTimeserial struct correctly implements the Timeserial protocol. The use of a private initializer enhances encapsulation. Good job on addressing the previous comment about using a struct instead of a class.


75-88: LGTM: Well-implemented comparison methods

The before, after, and equal methods are concisely and correctly implemented using the timeserialCompare method. This ensures consistent comparison across all methods.

@umair-ably umair-ably changed the title Send/Receive message draft [ECO-4942] Send/Receive Messages Spec Complete Oct 3, 2024
@umair-ably
Copy link
Collaborator Author

@coderabbitai summary

Copy link

coderabbitai bot commented Oct 3, 2024

✅ Actions performed

Summary regeneration triggered.

@maratal
Copy link
Collaborator

maratal commented Oct 3, 2024

I've tried this branch without preconcurrency import, and as I said on a meeting, you just need to replace all occurrences of let properties: ARTChannelProperties with var properties: ARTChannelProperties { .init() } (it's a getter in cocoa anyways) and also replace let defaultChannelOptions... with var defaultChannelOptions... (for now at least). FYI @lawrence-forooghian

So yeah, no need for ably/ably-cocoa#1973 atm

Copy link
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.

Left some questions.

Copy link

@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: 19

🧹 Outside diff range and nitpick comments (15)
Sources/AblyChat/PaginatedResult.swift (4)

Line range hint 3-13: Consider addressing the TODO comment for unwrapping

The TODO comment suggests reconsidering the need for unwrapping. To improve type safety and reduce the need for optional unwrapping, you might want to explore using a non-optional type or a more explicit error handling mechanism.

Consider alternatives such as:

  1. Using a Result type to represent success/failure states.
  2. Implementing a custom error type that includes a "no next page" case.
  3. Using a non-optional property with a sentinel value to indicate the absence of a next page.

Would you like me to propose a specific implementation for any of these alternatives?


15-46: Solid implementation with room for minor improvement

The ARTHTTPPaginatedCallbackWrapper struct effectively handles the callback result and error cases. The use of CheckedContinuation is appropriate for bridging callback-based APIs to async/await.

Consider enhancing error handling by creating a custom error type that encapsulates both ARTErrorInfo and PaginatedResultError. This would provide a more unified error handling approach:

enum PaginatedResultError: Error {
    case artError(ARTErrorInfo)
    case noErrorWithInvalidResponse
    case decodingError(Error)
}

Then, you can use this enum in the handleResponse method:

if let error {
    continuation.resume(throwing: PaginatedResultError.artError(ARTErrorInfo.create(from: error)))
    return
}

// ... (rest of the method)

} catch {
    continuation.resume(throwing: PaginatedResultError.decodingError(error))
}

This approach would provide more context about the error type to the caller.


48-88: Efficient implementation with room for optimization

The PaginatedResultWrapper class effectively wraps the ARTHTTPPaginatedResponse and provides a clean interface conforming to PaginatedResult. The use of withCheckedThrowingContinuation for next and first properties is appropriate.

Consider optimizing the current property implementation. Since it always returns self, it doesn't need to be asynchronous or throwing. You can simplify it as follows:

internal var current: any PaginatedResult<T> {
    self
}

This change would improve performance by avoiding unnecessary asynchronous overhead when accessing the current page.


Line range hint 1-95: Well-structured pagination system with room for minor improvements

The implementation of the pagination system is well-designed and effectively bridges callback-based APIs to async/await. The code follows Swift best practices and is easy to understand.

To further improve the codebase, consider adding documentation comments (using ///) for the PaginatedResult protocol and PaginatedResultWrapper class. This would enhance code readability and make it easier for other developers to understand and use these types.

For example:

/// A protocol representing a paginated result set.
///
/// `PaginatedResult` provides a generic interface for accessing paginated data,
/// including the current items, pagination status, and methods to fetch additional pages.
public protocol PaginatedResult<T>: AnyObject, Sendable {
    // ... (existing code)
}

/// A concrete implementation of `PaginatedResult` that wraps an `ARTHTTPPaginatedResponse`.
///
/// `PaginatedResultWrapper` provides access to paginated data and implements
/// the necessary logic to fetch additional pages using the underlying `ARTHTTPPaginatedResponse`.
internal final class PaginatedResultWrapper<T: Codable & Sendable>: PaginatedResult {
    // ... (existing code)
}

These documentation comments will be particularly helpful when generating API documentation or when using code completion in Xcode.

Example/AblyChatExample/MessageDemoView.swift (1)

1-8: Consider using environment variables for sensitive information

The clientId and apiKey are currently set as empty strings. For better security and flexibility, consider loading these values from environment variables or a secure configuration file, especially when moving beyond this example code.

Sources/AblyChat/ChatAPI.swift (5)

3-13: LGTM with a minor suggestion

The ChatAPI class is well-structured with appropriate access levels and protocol conformance. The use of final prevents subclassing, which is good for maintaining control over the API.

However, consider making the apiProtocolVersion more flexible:

private let apiProtocolVersion: Int

public init(realtime: RealtimeClient, apiProtocolVersion: Int = 3) {
    self.realtime = realtime
    self.apiProtocolVersion = apiProtocolVersion
}

This allows for easier version updates in the future without changing the class implementation.


26-68: LGTM with a suggestion for date conversion clarity

The sendMessage function correctly implements the CHA-M3 requirements, including proper validation of metadata and headers. The error handling for invalid keys is well implemented.

However, the date conversion from milliseconds to seconds could be more explicit:

let createdAtInSeconds = TimeInterval(response.createdAt) / 1000

This makes it clear that we're working with milliseconds without relying on the integerLiteral initializer.

Also, consider adding a comment explaining why clientID can be an empty string:

clientID: realtime.clientId ?? "", // Empty string is allowed as per Ably's specification

75-102: LGTM with a note on the TODO

The makeAuthorizedRequest function is well-implemented, using the continuation pattern correctly and handling errors comprehensively.

Regarding the TODO comment:

// TODO: Improve as part of occupancy/presence

It's good practice to include more context in TODO comments. Consider adding a brief explanation of what improvements are needed and why they're related to occupancy/presence. This will help future developers (including yourself) understand the intent behind the comment.

For example:

// TODO: Refactor error handling and response processing when implementing occupancy/presence features to ensure consistent behavior across different request types.

104-118: LGTM with a suggestion for error handling consistency

The makeAuthorizedPaginatedRequest function is well-implemented, using the continuation pattern and ARTHTTPPaginatedCallbackWrapper appropriately for handling paginated results.

However, the error handling in this function is minimal compared to makeAuthorizedRequest. Consider adding more specific error handling similar to makeAuthorizedRequest for consistency:

if let error {
    continuation.resume(throwing: ARTErrorInfo.create(from: error))
    return
}

guard let paginatedResponse else {
    continuation.resume(throwing: ChatError.noPaginatedResponse)
    return
}

ARTHTTPPaginatedCallbackWrapper<Response>(callbackResult: (paginatedResponse, nil)).handleResponse(continuation: continuation)

This would provide more detailed error information and ensure consistent error handling across both request functions.


125-139: LGTM with suggestions for error handling improvements

The DictionaryDecoder struct is well-implemented and serves its purpose effectively. However, there are a few improvements that could be made to enhance error handling and provide more context in case of failures:

  1. Consider wrapping the JSONSerialization and decoding errors in a custom error type to provide more context:
enum DictionaryDecoderError: Error {
    case serializationFailed(Error)
    case decodingFailed(Error)
}

internal func decode<T: Decodable>(_: T.Type, from dictionary: NSDictionary) throws -> T {
    do {
        let data = try JSONSerialization.data(withJSONObject: dictionary)
        do {
            return try decoder.decode(T.self, from: data)
        } catch {
            throw DictionaryDecoderError.decodingFailed(error)
        }
    } catch {
        throw DictionaryDecoderError.serializationFailed(error)
    }
}
  1. Add similar error handling to the array decoding method.

  2. Consider adding a method to decode from [String: Any] as well, which is a more common Swift dictionary type:

internal func decode<T: Decodable>(_: T.Type, from dictionary: [String: Any]) throws -> T {
    // Implementation similar to NSDictionary method
}

These changes will make the struct more robust and easier to use in different contexts.

Sources/AblyChat/DefaultMessages.swift (1)

80-91: TODO: Implement subscribeToDiscontinuities method.

The subscribeToDiscontinuities method is currently not implemented and throws a fatal error. As per the TODO comment, this needs to be implemented to allow users to subscribe to discontinuity events. Consider creating a GitHub issue to track this task if it doesn't already exist.

Would you like me to create a GitHub issue for implementing the subscribeToDiscontinuities method?

Sources/AblyChat/Rooms.swift (1)

Line range hint 17-17: Add async to the get method implementation to match the protocol

The get(roomID:options:) method in DefaultRooms is missing the async keyword, whereas the Rooms protocol defines it as an async function. This mismatch could lead to compiler errors or unexpected behavior.

Apply this diff to update the method signature:

-internal func get(roomID: String, options: RoomOptions) throws -> any Room {
+internal func get(roomID: String, options: RoomOptions) async throws -> any Room {
Sources/AblyChat/Room.swift (1)

55-55: Consider marking _messages as nonisolated

Since _messages is accessed from a nonisolated context in the messages property, it would be appropriate to declare it as nonisolated to clarify the concurrency context and ensure thread safety. Update the declaration as follows:

-private let _messages: any Messages
+private nonisolated let _messages: any Messages
Sources/AblyChat/Messages.swift (2)

24-24: Reminder: Implement Date Conversion for start and end Parameters

The TODO comment indicates that start and end should be properly converted between Date and timeserial Int. Addressing this will ensure accurate API requests and data consistency.

Would you like assistance in implementing the date conversion, or should we open a GitHub issue to track this task?


47-75: Ensure Proper Formatting of Date and Timeserial Parameters in toDictionary()

When converting Date and Int parameters to strings in the toDictionary() method, it's important to format them correctly for API consumption. For instance:

  • Convert Date objects to timestamps (milliseconds since epoch) or ISO 8601 strings.
  • Ensure that timeserial integers are accurately represented as strings without loss of precision.

If you need assistance with the formatting, I'm happy to help implement the necessary conversions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 97ee439 and 924e103.

📒 Files selected for processing (30)
  • AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved (1 hunks)
  • Example/AblyChatExample.xcodeproj/project.pbxproj (1 hunks)
  • Example/AblyChatExample/AblyChatExampleApp.swift (1 hunks)
  • Example/AblyChatExample/ContentView.swift (0 hunks)
  • Example/AblyChatExample/MessageDemoView.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockRealtime.swift (3 hunks)
  • Package.resolved (1 hunks)
  • Package.swift (2 hunks)
  • [email protected] (1 hunks)
  • Sources/AblyChat/ChatAPI.swift (1 hunks)
  • Sources/AblyChat/DefaultMessages.swift (1 hunks)
  • Sources/AblyChat/Dependencies.swift (1 hunks)
  • Sources/AblyChat/EmitsDiscontinuities.swift (1 hunks)
  • Sources/AblyChat/Events.swift (1 hunks)
  • Sources/AblyChat/Headers.swift (1 hunks)
  • Sources/AblyChat/Message.swift (2 hunks)
  • Sources/AblyChat/Messages.swift (3 hunks)
  • Sources/AblyChat/Metadata.swift (1 hunks)
  • Sources/AblyChat/Occupancy.swift (1 hunks)
  • Sources/AblyChat/PaginatedResult.swift (2 hunks)
  • Sources/AblyChat/Room.swift (2 hunks)
  • Sources/AblyChat/Rooms.swift (1 hunks)
  • Sources/AblyChat/Timeserial.swift (1 hunks)
  • Sources/AblyChat/Version.swift (1 hunks)
  • Tests/AblyChatTests/DefaultRoomTests.swift (4 hunks)
  • Tests/AblyChatTests/DefaultRoomsTests.swift (3 hunks)
  • Tests/AblyChatTests/MessageSubscriptionTests.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockChannels.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtime.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (3 hunks)
💤 Files with no reviewable changes (1)
  • Example/AblyChatExample/ContentView.swift
✅ Files skipped from review due to trivial changes (1)
  • Sources/AblyChat/Events.swift
🚧 Files skipped from review as they are similar to previous changes (19)
  • AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved
  • Example/AblyChatExample.xcodeproj/project.pbxproj
  • Example/AblyChatExample/AblyChatExampleApp.swift
  • Package.resolved
  • Package.swift
  • [email protected]
  • Sources/AblyChat/Dependencies.swift
  • Sources/AblyChat/EmitsDiscontinuities.swift
  • Sources/AblyChat/Headers.swift
  • Sources/AblyChat/Message.swift
  • Sources/AblyChat/Metadata.swift
  • Sources/AblyChat/Occupancy.swift
  • Sources/AblyChat/Version.swift
  • Tests/AblyChatTests/DefaultRoomTests.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/MessageSubscriptionTests.swift
  • Tests/AblyChatTests/Mocks/MockChannels.swift
  • Tests/AblyChatTests/Mocks/MockRealtime.swift
  • Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift
🧰 Additional context used
📓 Learnings (6)
Example/AblyChatExample/MessageDemoView.swift (3)
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Example/AblyChatExample/MessageDemoView.swift:131-145
Timestamp: 2024-10-02T23:24:01.894Z
Learning: The example code in `Example/AblyChatExample/MessageDemoView.swift` is temporary and will soon be replaced; issues in this file can be deprioritized in code reviews.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Example/AblyChatExample/MessageDemoView.swift:122-123
Timestamp: 2024-10-02T23:28:40.105Z
Learning: In `Example/AblyChatExample/MessageDemoView.swift`, UI updates within `startChat` are acceptable because `startChat` is called on the main thread.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Example/AblyChatExample/MessageDemoView.swift:49-54
Timestamp: 2024-10-02T23:26:46.896Z
Learning: The `clientOptions` for `Realtime` and `DefaultChatClient` are different: one is for Realtime, and one is for log handling.
Example/AblyChatExample/Mocks/MockRealtime.swift (4)
Learnt from: lawrence-forooghian
PR: ably-labs/ably-chat-swift#35
File: Example/AblyChatExample/Mocks/MockRealtime.swift:16-32
Timestamp: 2024-09-02T16:30:26.840Z
Learning: When reviewing code, do not ask lawrence-forooghian about "not yet implemented" `fatalError` calls.
Learnt from: lawrence-forooghian
PR: ably-labs/ably-chat-swift#35
File: Example/AblyChatExample/Mocks/MockRealtime.swift:34-174
Timestamp: 2024-09-02T16:30:41.278Z
Learning: When reviewing code, do not ask lawrence-forooghian about "not yet implemented" `fatalError` calls.
Learnt from: maratal
PR: ably-labs/ably-chat-swift#34
File: Example/AblyChatExample/Mocks/MockClients.swift:24-44
Timestamp: 2024-09-22T21:36:09.485Z
Learning: In mock implementations, it's acceptable to leave the `release` method unimplemented during early development.
Learnt from: maratal
PR: ably-labs/ably-chat-swift#34
File: Example/AblyChatExample/Mocks/MockClients.swift:367-369
Timestamp: 2024-09-22T21:19:09.956Z
Learning: In mock implementations, it's acceptable to leave `subscribeToDiscontinuities` unimplemented during early development.
Sources/AblyChat/ChatAPI.swift (1)
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/ChatAPI.swift:104-118
Timestamp: 2024-10-02T23:22:40.173Z
Learning: In the `makeAuthorizedPaginatedRequest` function, nested `do-catch` blocks are acceptable for maintaining lower-level error propagation without additional error mapping.
Sources/AblyChat/DefaultMessages.swift (1)
Learnt from: lawrence-forooghian
PR: ably-labs/ably-chat-swift#67
File: Sources/AblyChat/RoomLifecycleManager.swift:134-137
Timestamp: 2024-10-01T20:11:26.655Z
Learning: In `RoomLifecycleManager`, capturing `self` as `[weak self]` in closures is acceptable because if `self` is deallocated, missing state changes is acceptable since there's no object to process them.
Sources/AblyChat/PaginatedResult.swift (3)
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:64-66
Timestamp: 2024-10-02T23:40:28.836Z
Learning: When handling closures passed to `paginatedResponse.next` in `PaginatedResultWrapper`, it is unnecessary to capture `paginatedResponse` weakly since it is provided by the method and not retained by `self`; therefore, there is no risk of a retain cycle.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:24-25
Timestamp: 2024-10-02T23:39:04.481Z
Learning: The syntax `if let variable {` is valid in newer versions of Swift and should not be flagged as incorrect.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:29-29
Timestamp: 2024-10-02T23:38:51.440Z
Learning: In Swift, the guard statement can use shorthand optional binding syntax like `guard let variable else { ... }` without needing to assign `variable = variable`.
Sources/AblyChat/Room.swift (1)
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/Room.swift:50-50
Timestamp: 2024-10-02T23:39:50.226Z
Learning: In the Cocoa SDK, `clientID` can be `nil`, and using an empty string as `clientID` works fine.
🔇 Additional comments (13)
Sources/AblyChat/Timeserial.swift (2)

3-12: LGTM: Timeserial protocol definition is well-structured

The Timeserial protocol is well-defined with appropriate properties and methods for timeserial comparison. The conformance to Sendable is a good practice for concurrent environments.


14-25: LGTM: DefaultTimeserial struct is well-implemented

The DefaultTimeserial struct correctly implements the Timeserial protocol. The use of a private initializer is good for encapsulation and control over object creation.

Sources/AblyChat/PaginatedResult.swift (1)

90-95: Clean and purposeful extension

The private extension on ARTHTTPPaginatedResponse provides a concise and useful method for converting to PaginatedResultWrapper. This approach enhances code readability and maintainability.

Example/AblyChatExample/MessageDemoView.swift (1)

1-150: Overall assessment of MessageDemoView.swift

The implementation provides a functional chat interface using SwiftUI and Ably. While it serves its purpose as example code, there are several areas where it could be improved:

  1. Security: Move sensitive information (clientId, apiKey) to a secure configuration.
  2. User Experience: Improve message alignment for better visual distinction between senders.
  3. Code Organization: Extract configuration and utility functions to separate files or extensions.
  4. Error Handling: Implement more robust error handling, especially in the startChat() function.
  5. Concurrency: Address potential race conditions in the sendMessage() function.

These suggestions can serve as valuable considerations when implementing the final version of the chat interface.

Sources/AblyChat/DefaultMessages.swift (3)

1-10: LGTM! Well-structured file header and imports.

The file structure is clean, with appropriate imports and well-documented private types. The FromSerial typealias and MessageSubscriptionWrapper struct are clearly explained and serve specific purposes in the implementation.


97-124: LGTM! Well-implemented getBeforeSubscriptionStart method.

The method correctly handles querying message history before a subscription point, including proper error handling for invalid query parameters. The implementation aligns well with the specified requirements (CHA-M5j, CHA-M5f, CHA-M5g).


1-235: Overall, well-implemented messaging system with room for minor improvements.

The DefaultMessages class provides a comprehensive implementation of a messaging system using the Ably SDK. It adheres well to the specified requirements and demonstrates good attention to detail. The suggested improvements, such as enhancing error handling, improving logging, and expanding the error enum, will further increase the robustness and maintainability of the code. Great job on the implementation!

Sources/AblyChat/Room.swift (1)

39-51: ⚠️ Potential issue

Ensure ChatAPI and DefaultMessages conform to Sendable for concurrency safety

Accessing chatAPI and _messages from nonisolated contexts within the DefaultRoom actor requires that these types conform to Sendable to guarantee thread safety in Swift's concurrency model. Please verify that ChatAPI, Messages (and specifically DefaultMessages), conform to Sendable to avoid potential concurrency issues.

You can run the following script to verify the conformance:

Sources/AblyChat/Messages.swift (4)

6-6: Approved: Asynchronous subscribe Method Enhances Responsiveness

Changing the subscribe method to be async throws is appropriate. This update allows for asynchronous operations and better error handling, improving the responsiveness of the API.


9-9: Approved: Updated channel Property Improves Consistency

Updating the channel property to use RealtimeChannelProtocol ensures consistency with the latest Ably SDK protocols.


85-94: Approved: Enhanced Initializers for MessageSubscription Increase Flexibility

The updated initializers with the getPreviousMessages closure enhance the flexibility of MessageSubscription, allowing for customized retrieval of previous messages.

Also applies to: 97-100


107-108: Approved: Proper Delegation in getPreviousMessages Method

The getPreviousMessages method correctly delegates the execution to the provided closure, maintaining clean code architecture and separation of concerns.

Example/AblyChatExample/Mocks/MockRealtime.swift (1)

45-49: ⚠️ Potential issue

Verify ARTChannelProperties Conformance to Sendable

Since Channel is marked as Sendable, all of its stored properties, including ARTChannelProperties, should also conform to Sendable to ensure concurrency safety. If ARTChannelProperties does not conform to Sendable, this may lead to concurrency issues when accessing its properties across different threads.

To confirm if ARTChannelProperties conforms to Sendable, please run the following script:

Sources/AblyChat/Timeserial.swift Show resolved Hide resolved
Sources/AblyChat/Timeserial.swift Show resolved Hide resolved
Sources/AblyChat/Timeserial.swift Show resolved Hide resolved
Sources/AblyChat/Timeserial.swift Show resolved Hide resolved
Example/AblyChatExample/MessageDemoView.swift Outdated Show resolved Hide resolved
Sources/AblyChat/ChatAPI.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/Messages.swift Outdated Show resolved Hide resolved
@umair-ably
Copy link
Collaborator Author

@coderabbitai pause

Copy link

coderabbitai bot commented Oct 3, 2024

✅ Actions performed

Reviews paused.

Copy link

@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: 8

🧹 Outside diff range and nitpick comments (16)
Sources/AblyChat/Message.swift (4)

37-41: LGTM with suggestion: Implement isBefore method with potential optimization

The isBefore method correctly implements the comparison logic based on timeserials. However, consider optimizing the method by calculating selfTimeserial once and reusing it:

 public func isBefore(_ otherMessage: Message) throws -> Bool {
+    let selfTimeserial = try DefaultTimeserial.calculateTimeserial(from: timeserial)
     let otherMessageTimeserial = try DefaultTimeserial.calculateTimeserial(from: otherMessage.timeserial)
-    return try DefaultTimeserial.calculateTimeserial(from: timeserial).before(otherMessageTimeserial)
+    return selfTimeserial.before(otherMessageTimeserial)
 }

This optimization can improve performance by avoiding redundant calculations.


43-47: LGTM with suggestion: Implement isAfter method with potential optimization

The isAfter method correctly implements the comparison logic based on timeserials. Similar to the isBefore method, consider optimizing by calculating selfTimeserial once:

 public func isAfter(_ otherMessage: Message) throws -> Bool {
+    let selfTimeserial = try DefaultTimeserial.calculateTimeserial(from: timeserial)
     let otherMessageTimeserial = try DefaultTimeserial.calculateTimeserial(from: otherMessage.timeserial)
-    return try DefaultTimeserial.calculateTimeserial(from: timeserial).after(otherMessageTimeserial)
+    return selfTimeserial.after(otherMessageTimeserial)
 }

This optimization aligns with the suggestion for the isBefore method and can improve overall performance.


49-53: LGTM with suggestion: Implement isEqual method with potential optimization

The isEqual method correctly implements the comparison logic based on timeserials. To maintain consistency with the previous suggestions, consider optimizing by calculating selfTimeserial once:

 public func isEqual(_ otherMessage: Message) throws -> Bool {
+    let selfTimeserial = try DefaultTimeserial.calculateTimeserial(from: timeserial)
     let otherMessageTimeserial = try DefaultTimeserial.calculateTimeserial(from: otherMessage.timeserial)
-    return try DefaultTimeserial.calculateTimeserial(from: timeserial).equal(otherMessageTimeserial)
+    return selfTimeserial.equal(otherMessageTimeserial)
 }

This optimization completes the set of optimized comparison methods, ensuring consistent performance improvements across all three methods.


Line range hint 6-53: Consider conforming to Comparable and Equatable protocols

The Message struct now implements comparison methods (isBefore, isAfter, isEqual) and conforms to Hashable. To further enhance its functionality and provide a more idiomatic Swift interface, consider conforming to the Comparable and Equatable protocols:

public struct Message: Sendable, Codable, Hashable, Comparable, Equatable {
    // ... existing properties and methods ...

    public static func < (lhs: Message, rhs: Message) throws -> Bool {
        return try lhs.isBefore(rhs)
    }

    public static func == (lhs: Message, rhs: Message) throws -> Bool {
        return try lhs.isEqual(rhs)
    }
}

This change would allow for more natural sorting and comparison operations using standard Swift operators, improving the overall usability of the Message struct in various contexts.

Sources/AblyChat/PaginatedResult.swift (3)

Line range hint 10-10: Address the TODO comment regarding unwrapping

The TODO comment suggests a potential issue with unwrapping that needs to be addressed. Consider creating a GitHub issue to track this task and ensure it's not overlooked.

Would you like me to create a GitHub issue to track this TODO item?


16-46: Well-structured error handling and response decoding

The ARTHTTPPaginatedCallbackWrapper struct is well-designed for handling paginated responses and errors. The error handling logic is comprehensive and covers various scenarios.

Consider adding a more specific error case for decoding failures to provide better context when debugging:

internal enum PaginatedResultError: Error {
    case noErrorWithInvalidResponse
    case decodingError(Error)
}

Then, update the catch block in handleResponse:

} catch {
    continuation.resume(throwing: PaginatedResultError.decodingError(error))
}

This change would provide more context about the specific error that occurred during decoding.


49-88: Solid implementation of PaginatedResult protocol

The PaginatedResultWrapper<T> class provides a robust implementation of the PaginatedResult protocol. The use of withCheckedThrowingContinuation for asynchronous operations is appropriate.

For consistency, consider using the same pattern for the current property as used in next and first. This would make the code more uniform and easier to maintain:

internal var current: any PaginatedResult<T> {
    get async throws {
        try await withCheckedThrowingContinuation { continuation in
            continuation.resume(returning: self)
        }
    }
}

This change would make all properties consistent in their asynchronous nature, even if current doesn't perform any actual asynchronous operations.

Sources/AblyChat/Messages.swift (5)

3-9: Approved: Protocol updates enhance concurrency and type safety

The changes to the Messages protocol are well-considered:

  1. The @MainActor annotation addresses a Swift 6.0 error and allows for mutations on listeners.
  2. The subscribe method is now asynchronous and throwing, aligning with modern Swift concurrency practices.
  3. The channel property type change to RealtimeChannelProtocol suggests a move towards a custom abstraction.

These updates improve type safety and concurrency handling in the protocol.

Consider updating the comment to mention the async nature of the subscribe method:

// MainActor is required to resolve a Swift 6.0 error "Incorrect actor executor assumption",
// whilst also allowing for mutations on listeners within the concrete class.
// The async subscribe method ensures proper concurrency handling.

47-77: Approved: Well-implemented toDictionary() method with minor optimization opportunity

The toDictionary() method is a well-implemented addition to the QueryOptions struct:

  1. It correctly handles all optional properties.
  2. The conversion of ResultOrder enum to string representation is appropriate.
  3. The method provides a clean way to convert QueryOptions to a format suitable for API requests.

Consider a minor optimization to reduce dictionary mutations:

public func toDictionary() -> [String: String] {
    var dict: [String: String] = [:]
    dict.reserveCapacity(5) // Pre-allocate capacity for potential items

    if let start { dict["start"] = "\(start)" }
    if let end { dict["end"] = "\(end)" }
    if let limit { dict["limit"] = "\(limit)" }
    if let orderBy {
        dict["direction"] = orderBy == .oldestFirst ? "forwards" : "backwards"
    }
    if let fromSerial { dict["fromSerial"] = fromSerial }

    return dict
}

This optimization pre-allocates dictionary capacity and uses a more concise syntax for the orderBy conversion.


85-94: Approved: Enhanced MessageSubscription with flexible message retrieval

The changes to the MessageSubscription struct improve its functionality and flexibility:

  1. The addition of the getPreviousMessages property allows for customizable message retrieval.
  2. The new initializer provides a clean way to inject the getPreviousMessages functionality.

These changes enhance the struct's capabilities and align with dependency injection principles.

Consider adding documentation for the getPreviousMessages property to clarify its purpose and usage:

/// A closure that retrieves previous messages based on the provided query options.
/// This allows for customizable message retrieval strategies.
private let getPreviousMessages: @Sendable (QueryOptions) async throws -> any PaginatedResult<Message>

107-109: Approved: Simplified getPreviousMessages method with suggestion

The changes to the getPreviousMessages method improve consistency and reduce code duplication:

  1. Using QueryOptions instead of QueryOptionsWithoutDirection aligns with earlier changes in the struct.
  2. The simplified implementation directly calls the getPreviousMessages property, reducing code duplication.

Consider adding error handling to provide more context if an error occurs:

public func getPreviousMessages(params: QueryOptions) async throws -> any PaginatedResult<Message> {
    do {
        return try await getPreviousMessages(params)
    } catch {
        throw MessageSubscriptionError.getPreviousMessagesFailed(underlyingError: error)
    }
}

enum MessageSubscriptionError: Error {
    case getPreviousMessagesFailed(underlyingError: Error)
}

This change would provide more context about where the error occurred, which can be helpful for debugging and error reporting.


Line range hint 1-128: Overall: Significant improvements with minor refinements needed

The changes to this file represent a substantial improvement in functionality, testability, and type safety:

  1. The Messages protocol now leverages Swift's concurrency features and provides better actor isolation.
  2. The QueryOptions struct has been enhanced, although there's still work to be done on date representation.
  3. The MessageSubscription struct now offers more flexible message retrieval and improved testability.
  4. The new toDictionary() method in QueryOptions provides a clean way to convert options for API requests.

These changes collectively enhance the SDK's capabilities and align it with modern Swift practices.

To further improve the codebase:

  1. Resolve the TODO regarding date representation in QueryOptions.
  2. Ensure consistent error handling throughout the file.
  3. Consider creating a separate file for QueryOptions if it grows in complexity.
  4. Review the usage of RealtimeChannelProtocol to ensure it meets all requirements previously covered by ARTRealtimeChannelProtocol.

These refinements will help maintain code quality and improve long-term maintainability.

Example/AblyChatExample/Mocks/MockRealtime.swift (2)

46-46: Consider initializing properties in the initializer.

The empty initializer init() has been added to the Channel class. While this is acceptable for a mock implementation, consider using this initializer to set up any required properties or perform any necessary initialization, even if it's just setting default values. This can help ensure that the mock object is in a consistent state when created.


Line range hint 4-231: Overall structure looks good, consider next steps for implementation.

The additions to the MockRealtime class and its nested classes are consistent with the purpose of this mock implementation. Here are some suggestions for next steps:

  1. Prioritize implementing methods that are crucial for your current testing needs.
  2. Consider adding TODO comments for methods that will need implementation in the future.
  3. As mentioned in a previous comment, address the concurrency concerns with ARTRealtimeChannelOptions when you start implementing the methods.
  4. When you begin implementing methods, consider adding basic logging to help with debugging during tests.

Remember to update the comment at the top of the file if the purpose or scope of this mock implementation changes.

Sources/AblyChat/Rooms.swift (2)

Line range hint 35-49: Missing async in get method implementation

The get method in the Rooms protocol is declared as async throws, but the implementation in DefaultRooms is missing the async keyword. This mismatch can lead to compilation errors and violates protocol conformance.

Apply this diff to fix the issue:

-    internal func get(roomID: String, options: RoomOptions) throws -> any Room {
+    internal func get(roomID: String, options: RoomOptions) async throws -> any Room {

Line range hint 49-51: Implement the release method to manage room lifecycle

The release method currently contains fatalError("Not yet implemented"). Implementing this method is crucial for proper resource management and to prevent runtime crashes when the method is called.

Would you like assistance in drafting an implementation for the release method or opening a GitHub issue to track this task?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 924e103 and 2716671.

📒 Files selected for processing (30)
  • AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved (1 hunks)
  • Example/AblyChatExample.xcodeproj/project.pbxproj (1 hunks)
  • Example/AblyChatExample/AblyChatExampleApp.swift (1 hunks)
  • Example/AblyChatExample/ContentView.swift (0 hunks)
  • Example/AblyChatExample/MessageDemoView.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockRealtime.swift (3 hunks)
  • Package.resolved (1 hunks)
  • Package.swift (2 hunks)
  • [email protected] (1 hunks)
  • Sources/AblyChat/ChatAPI.swift (1 hunks)
  • Sources/AblyChat/DefaultMessages.swift (1 hunks)
  • Sources/AblyChat/Dependencies.swift (1 hunks)
  • Sources/AblyChat/EmitsDiscontinuities.swift (1 hunks)
  • Sources/AblyChat/Events.swift (1 hunks)
  • Sources/AblyChat/Headers.swift (1 hunks)
  • Sources/AblyChat/Message.swift (2 hunks)
  • Sources/AblyChat/Messages.swift (3 hunks)
  • Sources/AblyChat/Metadata.swift (1 hunks)
  • Sources/AblyChat/Occupancy.swift (1 hunks)
  • Sources/AblyChat/PaginatedResult.swift (2 hunks)
  • Sources/AblyChat/Room.swift (2 hunks)
  • Sources/AblyChat/Rooms.swift (1 hunks)
  • Sources/AblyChat/Timeserial.swift (1 hunks)
  • Sources/AblyChat/Version.swift (1 hunks)
  • Tests/AblyChatTests/DefaultRoomTests.swift (4 hunks)
  • Tests/AblyChatTests/DefaultRoomsTests.swift (3 hunks)
  • Tests/AblyChatTests/MessageSubscriptionTests.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockChannels.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtime.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (2 hunks)
💤 Files with no reviewable changes (1)
  • Example/AblyChatExample/ContentView.swift
🚧 Files skipped from review as they are similar to previous changes (22)
  • AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved
  • Example/AblyChatExample.xcodeproj/project.pbxproj
  • Example/AblyChatExample/AblyChatExampleApp.swift
  • Package.resolved
  • Package.swift
  • [email protected]
  • Sources/AblyChat/ChatAPI.swift
  • Sources/AblyChat/Dependencies.swift
  • Sources/AblyChat/EmitsDiscontinuities.swift
  • Sources/AblyChat/Events.swift
  • Sources/AblyChat/Headers.swift
  • Sources/AblyChat/Metadata.swift
  • Sources/AblyChat/Occupancy.swift
  • Sources/AblyChat/Room.swift
  • Sources/AblyChat/Timeserial.swift
  • Sources/AblyChat/Version.swift
  • Tests/AblyChatTests/DefaultRoomTests.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
  • Tests/AblyChatTests/MessageSubscriptionTests.swift
  • Tests/AblyChatTests/Mocks/MockChannels.swift
  • Tests/AblyChatTests/Mocks/MockRealtime.swift
  • Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift
🧰 Additional context used
📓 Learnings (4)
Example/AblyChatExample/MessageDemoView.swift (3)
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Example/AblyChatExample/MessageDemoView.swift:131-145
Timestamp: 2024-10-02T23:24:01.894Z
Learning: The example code in `Example/AblyChatExample/MessageDemoView.swift` is temporary and will soon be replaced; issues in this file can be deprioritized in code reviews.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Example/AblyChatExample/MessageDemoView.swift:122-123
Timestamp: 2024-10-02T23:28:40.105Z
Learning: In `Example/AblyChatExample/MessageDemoView.swift`, UI updates within `startChat` are acceptable because `startChat` is called on the main thread.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Example/AblyChatExample/MessageDemoView.swift:49-54
Timestamp: 2024-10-02T23:26:46.896Z
Learning: The `clientOptions` for `Realtime` and `DefaultChatClient` are different: one is for Realtime, and one is for log handling.
Example/AblyChatExample/Mocks/MockRealtime.swift (4)
Learnt from: lawrence-forooghian
PR: ably-labs/ably-chat-swift#35
File: Example/AblyChatExample/Mocks/MockRealtime.swift:16-32
Timestamp: 2024-09-02T16:30:26.840Z
Learning: When reviewing code, do not ask lawrence-forooghian about "not yet implemented" `fatalError` calls.
Learnt from: lawrence-forooghian
PR: ably-labs/ably-chat-swift#35
File: Example/AblyChatExample/Mocks/MockRealtime.swift:34-174
Timestamp: 2024-09-02T16:30:41.278Z
Learning: When reviewing code, do not ask lawrence-forooghian about "not yet implemented" `fatalError` calls.
Learnt from: maratal
PR: ably-labs/ably-chat-swift#34
File: Example/AblyChatExample/Mocks/MockClients.swift:24-44
Timestamp: 2024-09-22T21:36:09.485Z
Learning: In mock implementations, it's acceptable to leave the `release` method unimplemented during early development.
Learnt from: maratal
PR: ably-labs/ably-chat-swift#34
File: Example/AblyChatExample/Mocks/MockClients.swift:367-369
Timestamp: 2024-09-22T21:19:09.956Z
Learning: In mock implementations, it's acceptable to leave `subscribeToDiscontinuities` unimplemented during early development.
Sources/AblyChat/DefaultMessages.swift (1)
Learnt from: lawrence-forooghian
PR: ably-labs/ably-chat-swift#67
File: Sources/AblyChat/RoomLifecycleManager.swift:134-137
Timestamp: 2024-10-01T20:11:26.655Z
Learning: In `RoomLifecycleManager`, capturing `self` as `[weak self]` in closures is acceptable because if `self` is deallocated, missing state changes is acceptable since there's no object to process them.
Sources/AblyChat/PaginatedResult.swift (3)
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:64-66
Timestamp: 2024-10-02T23:40:28.836Z
Learning: When handling closures passed to `paginatedResponse.next` in `PaginatedResultWrapper`, it is unnecessary to capture `paginatedResponse` weakly since it is provided by the method and not retained by `self`; therefore, there is no risk of a retain cycle.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:24-25
Timestamp: 2024-10-02T23:39:04.481Z
Learning: The syntax `if let variable {` is valid in newer versions of Swift and should not be flagged as incorrect.
Learnt from: umair-ably
PR: ably-labs/ably-chat-swift#45
File: Sources/AblyChat/PaginatedResult.swift:29-29
Timestamp: 2024-10-02T23:38:51.440Z
Learning: In Swift, the guard statement can use shorthand optional binding syntax like `guard let variable else { ... }` without needing to assign `variable = variable`.
🔇 Additional comments (15)
Sources/AblyChat/Message.swift (3)

6-15: LGTM: Enhanced struct conformance and property optionality

The changes to the Message struct, including conformance to Codable and Hashable, and making clientID and createdAt optional, align well with the PR objectives. These modifications improve the struct's flexibility and compatibility with SwiftUI and serialization processes.


Line range hint 17-26: LGTM: Initializer updated to match property changes

The initializer has been correctly updated to accept optional clientID and createdAt parameters, maintaining consistency with the property changes. This modification enhances the flexibility of the Message struct initialization.


27-35: LGTM: CodingKeys enum added with correct mappings

The addition of the CodingKeys enum ensures proper JSON key mapping for the Message struct. The previously reported issue with metadata and headers mapping has been correctly addressed. This change enhances the struct's serialization capabilities.

Sources/AblyChat/PaginatedResult.swift (1)

90-95: Clean extension for type conversion

The private extension on ARTHTTPPaginatedResponse provides a clean and efficient way to convert ARTHTTPPaginatedResponse to PaginatedResultWrapper. This approach enhances code readability and maintainability by encapsulating the conversion logic.

Sources/AblyChat/Messages.swift (2)

97-100: Approved: Improved testability for MessageSubscription

The changes to the testing initializer enhance the testability of the MessageSubscription struct:

  1. The addition of the mockGetPreviousMessages parameter allows for easy injection of mock behavior.
  2. Setting the getPreviousMessages property with the provided mock ensures consistent behavior during testing.

These changes facilitate more comprehensive and flexible unit testing of the MessageSubscription struct.


24-45: ⚠️ Potential issue

Address type inconsistency and improve documentation

The changes to the QueryOptions struct introduce some inconsistencies and potential issues:

  1. The start property remains as Date? while end is changed to Int?. This inconsistency could lead to confusion and potential errors.
  2. The TODO comment acknowledges the need to revisit the date representation.
  3. The new fromSerial property lacks documentation explaining its purpose and usage.

To resolve these issues:

  1. Implement the TODO by converting both start and end to use a consistent type (preferably Date?).
  2. If Int? is necessary for compatibility reasons, consider creating a custom type that encapsulates the conversion logic.
  3. Add documentation for the fromSerial property explaining its purpose and when it should be set.

Example implementation:

public struct QueryOptions: Sendable {
    // ... other properties ...

    /// The start time for the query range.
    public var start: Date?
    
    /// The end time for the query range.
    public var end: Date?
    
    /// Internal property used to specify the subscriber's subscription point.
    /// This is used in the fromSerial query parameter.
    internal var fromSerial: String?

    // ... rest of the implementation ...
}

To ensure that the fromSerial property is used correctly throughout the codebase, run the following script:

✅ Verification successful

Address Type Inconsistency and Enhance Documentation

The changes to the QueryOptions struct introduce some inconsistencies and areas that require clarification:

  1. The start property is of type Date?, while the end property is of type Int?. This inconsistency may lead to confusion and potential errors.
  2. The TODO comment acknowledges the need to revisit the date representation for proper conversion between Int and Date.
  3. The new fromSerial property is used in a limited scope and lacks documentation explaining its purpose and usage.

Recommendations:

  • Implement the TODO: Convert both start and end properties to use a consistent type, preferably Date?, to maintain uniformity and reduce potential errors.

    public struct QueryOptions: Sendable {
        /// The start time for the query range.
        public var start: Date?
        
        /// The end time for the query range.
        public var end: Date?
        
        // ... other properties ...
    }
  • Document fromSerial: Add clear documentation for the fromSerial property to explain its role and usage within the QueryOptions struct.

    /// The subscriber's subscription point, specified internally in the fromSerial query parameter.
    internal var fromSerial: String?
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find usage of fromSerial property
rg "fromSerial" --type swift

Length of output: 675

Example/AblyChatExample/Mocks/MockRealtime.swift (1)

44-44: LGTM: Property addition looks good.

The properties property of type ARTChannelProperties has been correctly added and initialized with a default instance. This addition enhances the mock implementation of the Channel class.

Sources/AblyChat/DefaultMessages.swift (3)

1-11: LGTM! Good use of comments and type definitions.

The import, typealias, and struct definitions are well-organized and appropriately commented. The TimeserialString typealias and MessageSubscriptionWrapper struct provide clear abstractions for working with message subscriptions and timestamps.


80-86: LGTM! Clean implementation of get and send methods.

The get and send methods are implemented correctly, delegating to the chatAPI for retrieving and sending messages. This approach maintains a clean separation of concerns.


97-124: LGTM! Well-implemented getBeforeSubscriptionStart method.

The getBeforeSubscriptionStart method is implemented correctly, adhering to the specifications mentioned in the comments. It properly handles error cases and ensures that the query parameters are valid before proceeding with the message retrieval.

Example/AblyChatExample/MessageDemoView.swift (5)

10-22: Well-structured MessageCell View

The MessageCell struct is effectively implemented, utilizing SwiftUI components to display message content with appropriate styling based on the sender.


23-42: Efficient Message Alignment in MessageView

The MessageView struct correctly handles message alignment and presentation, ensuring messages are properly displayed from different users.


49-54: Appropriate Configuration of Client Options

The clientOptions computed property accurately sets up the ARTClientOptions with the necessary clientId and apiKey.


96-129: Correct Implementation of startChat() Function

The startChat() function effectively initializes the chat client, connects to the specified room, retrieves previous messages, and subscribes to new incoming messages.


131-145: Proper Message Sending in sendMessage() Function

The sendMessage() function appropriately checks for empty messages, sends the message to the chat room, and clears the input field upon successful sending.

Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Show resolved Hide resolved
Sources/AblyChat/Rooms.swift Outdated Show resolved Hide resolved
@umair-ably umair-ably force-pushed the ablyChatDemo-wip branch 3 times, most recently from 01a5c15 to bb45c3e Compare October 14, 2024 22:34
@umair-ably
Copy link
Collaborator Author

@umair-ably please can you address #45 (comment)?

done

@umair-ably
Copy link
Collaborator Author

@lawrence-forooghian Getting that linting error again...

Error: packageLockfilesHaveDifferentContents(paths: ["Package.resolved", "AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved"])

Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian left a comment

Choose a reason for hiding this comment

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

I haven't looked at (and don't intend to look at) the test changes; I think I’ve reached my limit on how much review of this PR I can do :) Other than that I think it's basically good to go

Example/AblyChatExample/Mocks/Misc.swift Outdated Show resolved Hide resolved
Copy link
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.

Left a few small questions. Didn't check the validity of tests either, @umair-ably couldn't you assure that all implemented spec points are included in the tests of this PR?

Example/AblyChatExample/Mocks/Misc.swift Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Sources/AblyChat/DefaultMessages.swift Outdated Show resolved Hide resolved
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift Outdated Show resolved Hide resolved
@umair-ably
Copy link
Collaborator Author

Left a few small questions. Didn't check the validity of tests either, @umair-ably couldn't you assure that all implemented spec points are included in the tests of this PR?

A chunk of them have been but it's not exhaustive. I want to implement more chat features first, I'll add more tests for this later

Copy link
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.

A chunk of them have been but it's not exhaustive. I want to implement more chat features first, I'll add more tests for this later

In this case it's better to have an issue to track all unimplemented spec points IMHO. I'm approving regardless.

@umair-ably
Copy link
Collaborator Author

A chunk of them have been but it's not exhaustive. I want to implement more chat features first, I'll add more tests for this later

In this case it's better to have an issue to track all unimplemented spec points IMHO. I'm approving regardless.

thank you!

added an issue here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants