Skip to content

Conversation

@ttypic
Copy link
Contributor

@ttypic ttypic commented Nov 19, 2025

  • Introduced APIs to retrieve, update, and delete messages by serial identifier.
  • Added support for fetching historical versions of messages.
  • Implemented MessageEditsMixin to handle message editing operations.
  • Added tests for message editing and retrieval functionality.

Summary by CodeRabbit

  • New Features

    • Channel-level message edits & soft-deletes: retrieve by serial, patch-style updates, async/sync APIs, attach operation metadata, and fetch message version history; supports JSON and binary payloads.
  • Tests

    • New end-to-end REST tests for publish → retrieve → update → versions → delete workflows, async variants, and validation/error cases.
  • Refactor

    • Updated message encoding/decoding and added serialization/response handling to support edit/delete requests and single-message responses.

✏️ Tip: You can customize this high-level summary in your review settings.

@ttypic ttypic requested a review from sacOO7 November 19, 2025 13:24
@coderabbitai
Copy link

coderabbitai bot commented Nov 19, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds REST and realtime message edit/delete/version APIs: new MessageEditsMixin, MessageOperation model and serializers, single-message response handling, BaseMessage encode refactor, and end-to-end REST tests for edit/delete/version flows.

Changes

Cohort / File(s) Summary
Realtime ChannelBase
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java
Add messageEditsMixin field, initialize on creation/setOptions, and expose sync/async message edit, delete, and version APIs delegating to the mixin.
REST ChannelBase
lib/src/main/java/io/ably/lib/rest/ChannelBase.java
Add messageEditsMixin field, initialize in constructor, and add public APIs: getMessage*, updateMessage*, deleteMessage*, getMessageVersions* delegating to the mixin.
Message edits core (new)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java
New mixin implementing sync/async REST operations for get/update/delete and paginated version history; builds requests, handles validation, JSON/MsgPack payloads, and decoding.
MessageOperation model
lib/src/main/java/io/ably/lib/types/MessageOperation.java
New model with fields clientId, description, metadata; MsgPack and JSON helpers for embedding operation metadata.
MessageOperation serialization
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java
New serializer producing JSON or MsgPack request bodies for update/delete requests; handles binary vs text data, extras, and operation payload.
Message response handling
lib/src/main/java/io/ably/lib/types/MessageSerializer.java
Add getSingleMessageResponseHandler(ChannelOptions) and SingleMessageBodyHandler to parse single-message responses (JSON and MsgPack).
Message encoding refactor
lib/src/main/java/io/ably/lib/types/BaseMessage.java
Decompose encode into encodeData(ChannelOptions) returning EncodedMessageData (data + encoding); adjust coercion and encryption flow.
Tests (new)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java
New REST-focused test suite covering publish/get-by-serial, updates (sync/async, encoded), deletions (soft/async), version-history retrieval (sync/async/params), error cases, and full publish→update→versions→delete workflow.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant CB as ChannelBase
    participant MEM as MessageEditsMixin
    participant HTTP as Http Layer
    participant Server as Ably Server

    rect rgb(245,250,255)
    Note left of App: Retrieve latest message by serial (sync)
    App->>CB: getMessage(serial)
    CB->>MEM: getMessage(http, serial)
    MEM->>HTTP: GET /channels/{chan}/messages/{serial}
    HTTP->>Server: HTTP GET
    Server-->>HTTP: 200 + payload (JSON/MsgPack)
    HTTP-->>MEM: parsed Message
    MEM-->>CB: Message
    CB-->>App: Message
    end

    rect rgb(245,250,255)
    Note left of App: Update message (PATCH)
    App->>CB: updateMessage(message, operation?)
    CB->>MEM: updateMessage(http, message, operation)
    MEM->>HTTP: PATCH /channels/{chan}/messages/{serial} with body (JSON/MsgPack)
    HTTP->>Server: HTTP PATCH
    Server-->>HTTP: 200
    HTTP-->>MEM: success
    MEM-->>CB: void
    CB-->>App: void
    end

    rect rgb(245,250,255)
    Note left of App: Delete message (soft/hard)
    App->>CB: deleteMessage(message, operation?)
    CB->>MEM: deleteMessage(http, message, operation)
    MEM->>HTTP: POST /channels/{chan}/messages/{serial}/delete (body)
    HTTP->>Server: HTTP POST
    Server-->>HTTP: 200
    HTTP-->>MEM: success
    MEM-->>CB: void
    CB-->>App: void
    end

    rect rgb(245,250,255)
    Note left of App: Get versions (paginated)
    App->>CB: getMessageVersions(serial, params)
    CB->>MEM: getMessageVersions(http, serial, params)
    MEM->>HTTP: GET /channels/{chan}/messages/{serial}/versions?...
    HTTP->>Server: HTTP GET
    Server-->>HTTP: pages of versions
    HTTP-->>MEM: PaginatedResult<Message>
    MEM-->>CB: PaginatedResult
    CB-->>App: PaginatedResult
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review focus:
    • MessageEditsMixin: request assembly, protocol branching (JSON vs MsgPack), validation, sync/async correctness, error handling.
    • MessageOperation & MessageOperationSerializer: correctness of JSON/MsgPack formats, base64 handling, and binary vs text branching.
    • BaseMessage.encodeData: coercion, encryption ordering, and encoding string composition.
    • MessageSerializer single-message handler: parsing paths and error wrapping.
    • RestChannelMessageEditTest: stability of async waits/polling and timeout assumptions.

Poem

🐇
I nibble bytes and stitch a patch,
I mark old hops with gentle scratch.
Versions stacked in tidy rows,
Soft deletes where memory goes.
A tiny rabbit cheers these flows.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: adding message editing support to channels, which is the primary objective of this changeset.
Linked Issues check ✅ Passed The code changes implement all primary objectives from ECO-5642: write APIs for message edits/deletes, message retrieval by serial, version history retrieval, and message operation support.
Out of Scope Changes check ✅ Passed All changes are scoped to message editing functionality. Minor updates to BaseMessage encoding logic support the new MessageEditsMixin operations, staying within the PR's editing scope.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ECO-5642/edit-deletes-api

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

❤️ Share

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

@github-actions github-actions bot temporarily deployed to staging/pull/1179/features November 19, 2025 13:24 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1179/javadoc November 19, 2025 13:26 Inactive
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: 3

🧹 Nitpick comments (2)
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (1)

52-52: REST message edit delegates look good; tweak version docs and async signature

The new methods are thin wrappers over messageEditsMixin and fit the existing REST ChannelBase pattern.

Two follow‑ups to consider:

  • Javadoc for getMessageVersions says “ordered chronologically”, but tests assert the newest version comes first. It would be clearer to document the actual default order (e.g. “reverse chronological (newest first), matching channel history defaults”) and mention how to change it via params if supported.
  • getMessageVersionsAsync declares throws AblyException, unlike other async methods here. Because the only synchronous failure right now is validation in the mixin (null/empty serial), it would be more consistent to catch that in the async wrapper and surface it via callback.onError(...), then drop throws AblyException from the async signature.

Also applies to: 320-429, 436-444

lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)

239-252: Async getMessageVersions* methods unexpectedly throw; keep validation inside the async flow

getMessageVersionsImpl validates serial and throws an AblyException when it’s null/empty. Because both getMessageVersions(...) and getMessageVersionsAsync(...) call this helper, the async variants end up declaring throws AblyException and can fail synchronously, unlike the other async edit methods which always report failures via callbacks/listeners.

For API consistency and to match existing patterns:

  • Either move the serial validation into the Http.request lambda (like updateMessageImpl / deleteMessageImpl), so even async calls report errors via the callback, or
  • Wrap the getMessageVersionsImpl call in the async method with a try/catch that converts AblyException to callback.onError(e.errorInfo) and remove throws AblyException from the async signatures in this mixin and both ChannelBase classes.

This keeps all *Async APIs behaving uniformly and avoids surprising callers with synchronous exceptions from an async method.

Also applies to: 254-262

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4f354a8 and a46f753.

📒 Files selected for processing (4)
  • lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (4 hunks)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1 hunks)
  • lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (4)
lib/src/test/java/io/ably/lib/test/common/Helpers.java (3)
  • Helpers (74-1229)
  • AsyncWaiter (936-962)
  • CompletionSet (862-912)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/types/Message.java (1)
  • Message (25-443)
lib/src/main/java/io/ably/lib/types/Param.java (1)
  • Param (6-88)
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (4)
lib/src/main/java/io/ably/lib/types/ErrorInfo.java (1)
  • ErrorInfo (17-197)
lib/src/main/java/io/ably/lib/types/Message.java (1)
  • Message (25-443)
lib/src/main/java/io/ably/lib/types/MessageDecodeException.java (1)
  • MessageDecodeException (6-22)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (12)
lib/src/main/java/io/ably/lib/http/BasePaginatedQuery.java (1)
  • BasePaginatedQuery (25-345)
lib/src/main/java/io/ably/lib/http/Http.java (1)
  • Http (10-99)
lib/src/main/java/io/ably/lib/http/HttpCore.java (1)
  • HttpCore (39-583)
lib/src/main/java/io/ably/lib/http/HttpScheduler.java (1)
  • HttpScheduler (23-457)
lib/src/main/java/io/ably/lib/http/HttpUtils.java (1)
  • HttpUtils (28-280)
lib/src/main/java/io/ably/lib/types/ClientOptions.java (1)
  • ClientOptions (19-392)
lib/src/main/java/io/ably/lib/types/Message.java (1)
  • Message (25-443)
lib/src/main/java/io/ably/lib/types/MessageDecodeException.java (1)
  • MessageDecodeException (6-22)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)
  • MessageSerializer (23-189)
lib/src/main/java/io/ably/lib/types/Param.java (1)
  • Param (6-88)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)
  • MessageEditsMixin (24-264)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check (19)
  • GitHub Check: check (29)
  • GitHub Check: check (24)
  • GitHub Check: check (21)
  • GitHub Check: check (29)
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check-liveobjects
  • GitHub Check: check-rest
  • GitHub Check: build
  • GitHub Check: check-realtime
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check
🔇 Additional comments (1)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1)

48-513: Solid coverage of REST message edit, delete, and version flows

The test suite exercises sync/async get/update/delete plus version history and error paths in a realistic end‑to‑end fashion, which aligns well with the new API surface and should catch most regressions around message edits.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds message editing support to channels, enabling retrieval, update, and deletion of messages by serial identifier, along with message version history tracking. The implementation introduces a new MessageEditsMixin class that encapsulates all message editing operations and is integrated into both REST and Realtime channel classes.

  • Introduced MessageEditsMixin to handle message editing operations (get, update, delete, and version history)
  • Added message editing APIs to both REST and Realtime ChannelBase classes
  • Implemented comprehensive test coverage for all message editing operations

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

File Description
MessageEditsMixin.java Core implementation of message editing operations with HTTP request handling for get, update, delete, and version retrieval
ChannelBase.java (REST) Integration of MessageEditsMixin and public API methods for REST channels with added imports
ChannelBase.java (Realtime) Integration of MessageEditsMixin and public API methods for Realtime channels
RestChannelMessageEditTest.java Comprehensive test suite covering all message editing operations including error handling and workflow tests

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sacOO7
Copy link
Collaborator

sacOO7 commented Nov 20, 2025

Overall PR looks good. Couple of comments added

  1. CI might be failing because of compile error -> https://github.com/ably/ably-java/pull/1179/files#r2544815365
  2. Requestbody code duplication can be avoided using -> [ECO-5642] feat: add message editing support to channels #1179 (comment)
    and [ECO-5642] feat: add message editing support to channels #1179 (comment)
  3. Since, channelOptions can change at runtime, might need to adjust initialization for MessageEditsMixin as per [ECO-5642] feat: add message editing support to channels #1179 (comment)
  4. Validate REST calls over REALTIME channel -> [ECO-5642] feat: add message editing support to channels #1179 (comment)

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Collaborator

@sacOO7 sacOO7 left a comment

Choose a reason for hiding this comment

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

LGTM : )
PS. You can check/resolve review comments and check for failing CI.

@ttypic ttypic force-pushed the ECO-5642/edit-deletes-api branch from 548ea8a to 8448c8c Compare November 25, 2025 12:53
@github-actions github-actions bot temporarily deployed to staging/pull/1179/features November 25, 2025 12:54 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1179/javadoc November 25, 2025 12:55 Inactive
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: 2

♻️ Duplicate comments (7)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (3)

1324-1351: Consider adding convenience overloads without params parameter.

The tests and common usage patterns suggest that callers often don't need query parameters. Adding convenience overloads would improve the API ergonomics:

public PaginatedResult<Message> getMessageVersions(String serial) throws AblyException {
    return getMessageVersions(serial, null);
}

public void getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
    getMessageVersionsAsync(serial, null, callback);
}

105-106: MessageEditsMixin may use stale ChannelOptions after setOptions() is called.

The messageEditsMixin is initialized once in the constructor with the initial options reference. However, setOptions() at line 1458 can replace this.options with a new ChannelOptions instance (e.g., when changing encryption), but the mixin continues to use the original options it captured at construction.

This can cause inconsistent behavior where publish/subscribe/history use updated options while edit/delete/version calls still use the old ones (notably for encryption/decoding).

Consider updating the mixin when options change:

     public void setOptions(ChannelOptions options, CompletionListener listener) throws AblyException {
         this.options = options;
+        this.messageEditsMixin.setChannelOptions(options);
         if(this.shouldReattachToSetOptions(options)) {
             this.attach(true, listener);
         } else {
             callCompletionListenerSuccess(listener);
         }
     }

This requires adding a setChannelOptions(ChannelOptions) method to MessageEditsMixin.

Also applies to: 1535-1535


1265-1276: Incomplete Javadoc for @param message.

The documentation for the message parameter at line 1273 is truncated: "A {@link Message} object that may contain."

Consider completing it:

-     * @param message A {@link Message} object that may contain.
+     * @param message A {@link Message} object that may contain operation metadata such as
+     *                clientId, description, or metadata in the version field.
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (3)

117-140: Input message is mutated by setting action field.

Line 123 directly modifies the caller's message.action field, which may be unexpected. Callers may not anticipate their Message object being modified.

Consider documenting this side effect in the Javadoc, or creating a defensive copy:

     private Http.Request<Void> updateMessageImpl(Http http, String serial, Message message, MessageOperation operation) {
         return http.request((scheduler, callback) -> {
             if (serial == null || serial.isEmpty()) {
                 throw AblyException.fromErrorInfo(new ErrorInfo("Message serial cannot be empty", 400, 40003));
             }
-            /* Set action to MESSAGE_UPDATE */
-            message.action = MessageAction.MESSAGE_UPDATE;
+            /* Create working copy with action set to MESSAGE_UPDATE */
+            Message workingMessage = new Message();
+            workingMessage.name = message.name;
+            workingMessage.data = message.data;
+            workingMessage.extras = message.extras;
+            workingMessage.action = MessageAction.MESSAGE_UPDATE;
             /* RTL6g3 */
-            auth.checkClientId(message, true, false);
+            auth.checkClientId(workingMessage, true, false);

171-194: Same input mutation issue in deleteMessageImpl.

Line 177 mutates message.action to MESSAGE_DELETE. Apply the same fix as suggested for updateMessageImpl.


127-129: Request body construction is duplicated.

The pattern for creating HttpCore.RequestBody based on useBinaryProtocol is repeated in both updateMessageImpl and deleteMessageImpl. Consider extracting a helper method in MessageOperationSerializer:

public static HttpCore.RequestBody asRequest(Message message, MessageOperation operation, 
        ChannelOptions channelOptions, boolean useBinaryProtocol) throws AblyException {
    return useBinaryProtocol 
        ? asMsgPackRequest(message, operation, channelOptions) 
        : asJsonRequest(message, operation, channelOptions);
}

Then both methods can simply call:

HttpCore.RequestBody requestBody = MessageOperationSerializer.asRequest(
    message, operation, channelOptions, clientOptions.useBinaryProtocol);

Also applies to: 181-183

lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1)

70-78: Incorrect helper method for retrieving a newly published message.

waitForUpdatedMessageAppear waits for a message with action == MESSAGE_UPDATE, but a freshly published message has action == MESSAGE_CREATE (or null). This test will timeout waiting for an update that never happened.

Use channel.getMessage(serial) directly since no update is expected:

         // Retrieve the message by serial
-        Message retrievedMessage = waitForUpdatedMessageAppear(channel, publishedMessage.serial);
+        Message retrievedMessage = channel.getMessage(publishedMessage.serial);

Or create a separate helper that doesn't filter by action.

🧹 Nitpick comments (2)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)

191-224: Potential null element in returned array when content-type is unrecognized.

If contentType is neither "application/json" nor "application/x-msgpack", message remains null and the method returns new Message[] { null }. Callers that iterate the array or access messages[0] will receive null, which may not be expected behavior.

Consider throwing an exception for unsupported content types (consistent with Serialisation.HttpBodyHandler) or returning an empty array:

+                if (message == null) {
+                    throw AblyException.fromThrowable(new Exception("unknown content type " + contentType));
+                }
+
                 if (message != null) {
                     try {
                         message.decode(opts);

Additionally, Line 206 creates a throwaway new Message() instance just to call readMsgpack. If readMsgpack returns a new Message, consider using Message.fromMsgpack(unpacker) directly (if available) to avoid the unnecessary allocation.

lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1)

434-442: Operator precedence is correct.

The condition at line 438 uses proper parentheses to group the predicate check with the array length check before the timeout || condition:

if (history.items().length > 0 && predicate.test(history.items()) || System.currentTimeMillis() > timeout)

However, for clarity and to match the pattern in other helper methods, consider adding explicit parentheses:

-            if (history.items().length > 0 && predicate.test(history.items()) || System.currentTimeMillis() > timeout)
+            if ((history.items().length > 0 && predicate.test(history.items())) || System.currentTimeMillis() > timeout)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a46f753 and 8448c8c.

📒 Files selected for processing (8)
  • lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/BaseMessage.java (2 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperation.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageSerializer.java (2 hunks)
  • lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/src/main/java/io/ably/lib/types/BaseMessage.java
  • lib/src/main/java/io/ably/lib/types/MessageOperation.java
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java
🧰 Additional context used
🧬 Code graph analysis (4)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (11)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/types/ChannelOptions.java (1)
  • ChannelOptions (13-131)
lib/src/main/java/io/ably/lib/types/ErrorInfo.java (1)
  • ErrorInfo (17-197)
lib/src/main/java/io/ably/lib/types/Message.java (1)
  • Message (25-443)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/Param.java (1)
  • Param (6-88)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1)
  • keys (71-78)
liveobjects/src/test/kotlin/io/ably/lib/objects/integration/setup/IntegrationTest.kt (1)
  • testParams (18-97)
pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestClient.kt (1)
  • channels (3-9)
pubsub-adapter/src/test/kotlin/com/ably/Utils.kt (1)
  • waitFor (11-20)
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (4)
lib/src/main/java/io/ably/lib/http/HttpUtils.java (1)
  • HttpUtils (28-280)
lib/src/main/java/io/ably/lib/util/Base64Coder.java (1)
  • Base64Coder (30-239)
lib/src/main/java/io/ably/lib/util/Log.java (1)
  • Log (21-157)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (2)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)
  • MessageEditsMixin (25-234)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (9)
lib/src/main/java/io/ably/lib/http/BasePaginatedQuery.java (1)
  • BasePaginatedQuery (25-345)
lib/src/main/java/io/ably/lib/http/Http.java (1)
  • Http (10-99)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/types/ErrorInfo.java (1)
  • ErrorInfo (17-197)
lib/src/main/java/io/ably/lib/types/Message.java (1)
  • Message (25-443)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)
  • MessageOperationSerializer (18-172)
lib/src/main/java/io/ably/lib/types/Param.java (1)
  • Param (6-88)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check-liveobjects
  • GitHub Check: check (29)
  • GitHub Check: check (24)
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check (19)
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-rest-httpurlconnection
  • GitHub Check: check (21)
  • GitHub Check: check-realtime-httpurlconnection
  • GitHub Check: check
  • GitHub Check: check (29)
  • GitHub Check: build
🔇 Additional comments (5)
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

150-168: JSON serialization correctly handles binary data encoding.

The asJsonObject method properly base64-encodes binary data and appends /base64 to the encoding string. This aligns with Ably's encoding conventions for JSON payloads.

lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1)

453-471: Helper methods correctly handle operator precedence.

The waitForUpdatedMessageAppear and waitForDeletedMessageAppear methods now have proper parentheses around the compound conditions before the || timeout check. This ensures correct evaluation order.

lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

1179-1206: Message retrieval APIs look good.

The getMessage and getMessageAsync methods properly delegate to the mixin with appropriate parameters. The Javadoc clearly documents the behavior and threading model for async callbacks.

lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (2)

68-87: getMessage implementation correctly uses the new single-message handler.

The implementation properly validates the serial, uses the new getSingleMessageResponseHandler, and throws appropriate errors for missing messages. Good use of the HTTP request pattern.


196-232: Version history retrieval is well-implemented.

The getMessageVersions methods properly use BasePaginatedQuery for pagination support, validate the serial upfront, and correctly pass through query parameters. The Javadoc could clarify the ordering direction (chronological vs reverse chronological) as noted in past reviews.

@ttypic ttypic force-pushed the ECO-5642/edit-deletes-api branch from 8448c8c to b5476b1 Compare November 25, 2025 13:09
@github-actions github-actions bot temporarily deployed to staging/pull/1179/features November 25, 2025 13:10 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1179/javadoc November 25, 2025 13:12 Inactive
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: 1

♻️ Duplicate comments (6)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (3)

1337-1350: Missing convenience overloads for getMessageVersions and getMessageVersionsAsync.

This was noted in previous reviews. Consider adding overloads without the params parameter for consistency with other API methods like history():

/**
 * Retrieves all historical versions of a specific message.
 *
 * @param serial The unique serial identifier of the message.
 * @return A {@link PaginatedResult} containing all versions of the message.
 * @throws AblyException If the versions cannot be retrieved.
 */
public PaginatedResult<Message> getMessageVersions(String serial) throws AblyException {
    return getMessageVersions(serial, null);
}

/**
 * Asynchronously retrieves all historical versions of a specific message.
 *
 * @param serial The unique serial identifier of the message.
 * @param callback A callback to handle the result asynchronously.
 */
public void getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
    getMessageVersionsAsync(serial, null, callback);
}

105-106: MessageEditsMixin will use stale ChannelOptions after setOptions() is called.

This issue was raised in a previous review. The messageEditsMixin is initialized once in the constructor with the initial options reference. When setOptions(...) replaces this.options, the mixin continues using the original ChannelOptions, leading to inconsistent behavior (e.g., different encryption settings for edit/delete vs. publish/history).

Add a setter on MessageEditsMixin and call it from setOptions():

     public void setOptions(ChannelOptions options, CompletionListener listener) throws AblyException {
         this.options = options;
+        this.messageEditsMixin.setChannelOptions(options);
         if(this.shouldReattachToSetOptions(options)) {
             this.attach(true, listener);
         } else {
             callCompletionListenerSuccess(listener);
         }
     }

Also applies to: 1535-1535


1265-1276: Incomplete javadoc for message parameter.

The @param message documentation is cut off mid-sentence at line 1273.

-     * @param message A {@link Message} object that may contain.
+     * @param message A {@link Message} object that may contain operation metadata such as
+     *                clientId, description, or metadata in the version field.
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

55-66: Returning null on IOException will cause NPE downstream.

This issue was flagged in a previous review. If writeMsgpack encounters an IOException, it logs the error and returns null. This null is then passed to ByteArrayRequestBody at line 46, which will cause a NullPointerException.

Propagate the exception instead:

-    private static byte[] writeMsgpack(UpdateDeleteRequest request) {
+    private static byte[] writeMsgpack(UpdateDeleteRequest request) throws AblyException {
         try {
             ByteArrayOutputStream out = new ByteArrayOutputStream();
             MessagePacker packer = Serialisation.msgpackPackerConfig.newPacker(out);
             request.writeMsgpack(packer);
             packer.flush();
             return out.toByteArray();
         } catch (IOException e) {
-            Log.e(TAG, "Failed to write msgpack", e);
-            return null;
+            throw AblyException.fromThrowable(e);
         }
     }
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (2)

403-417: Complete the deleteMessage Javadoc for the message parameter

The first deleteMessage(String serial, Message message, MessageOperation operation) overload still has an incomplete @param message description ("may contain."), while the second overload has the full, intended wording. Please align the first overload with the completed text.

-     * @param message A {@link Message} object that may contain.
+     * @param message A {@link Message} object that may contain operation metadata such as
+     *                clientId, description, or metadata in the version field.

462-488: Clarify version ordering and add convenience overloads for getMessageVersions (sync & async)

Two things here:

  1. Document the actual ordering of versions

Tests and expected behavior indicate versions are returned newest-first, but the Javadoc currently says “ordered chronologically”, which is ambiguous and typically implies oldest-first. Please make this explicit:

-     * This method returns a paginated result containing all versions of the message,
-     * ordered chronologically. Each version includes metadata about when and by whom
+     * This method returns a paginated result containing all versions of the message,
+     * ordered in reverse chronological order (newest first). Each version includes metadata about when and by whom
  1. Provide no-params convenience overloads (mirroring usage in tests)

There are calls like channel.getMessageVersions(publishedMessage.serial) and channel.getMessageVersionsAsync(publishedMessage.serial, waiter), but the implementation only exposes the Param[] variants. Adding convenience overloads keeps API parity with other methods (e.g., history()) and fixes the compile-time gap.

     /**
      * Retrieves all historical versions of a specific message.
@@
      * @throws AblyException If the versions cannot be retrieved.
      */
     public PaginatedResult<Message> getMessageVersions(String serial, Param[] params) throws AblyException {
         return messageEditsMixin.getMessageVersions(ably.http, serial, params);
     }
+
+    /**
+     * Convenience overload for retrieving all historical versions of a specific message
+     * without specifying query parameters.
+     *
+     * @param serial The unique serial identifier of the message.
+     * @return A {@link PaginatedResult} containing an array of {@link Message} objects
+     *         representing all versions of the message.
+     * @throws AblyException If the versions cannot be retrieved.
+     */
+    public PaginatedResult<Message> getMessageVersions(String serial) throws AblyException {
+        return getMessageVersions(serial, null);
+    }
@@
      * @param params Query parameters for filtering or pagination.
      * @param callback A callback to handle the result asynchronously.
      */
     public void getMessageVersionsAsync(String serial, Param[] params, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
         messageEditsMixin.getMessageVersionsAsync(ably.http, serial, params, callback);
     }
+
+    /**
+     * Convenience overload for asynchronously retrieving all historical versions of a specific message
+     * without specifying query parameters.
+     *
+     * @param serial The unique serial identifier of the message.
+     * @param callback A callback to handle the result asynchronously.
+     * @throws AblyException If the versions cannot be retrieved.
+     */
+    public void getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
+        getMessageVersionsAsync(serial, null, callback);
+    }
🧹 Nitpick comments (2)
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (2)

480-488: Align async getMessageVersionsAsync contract (Javadoc vs throws)

getMessageVersionsAsync declares throws AblyException but the Javadoc doesn’t document any synchronous exception behavior, unlike other async methods in this class that generally don’t declare checked exceptions at all.

Consider either:

  • documenting when a synchronous AblyException can be thrown (e.g., invalid serial/params pre-validation), or
  • adjusting the design to keep async methods exception-free at the signature level and route all failures via the callback, for consistency with historyAsync and presence async methods.

497-505: Confirm MessageEditsMixin stays in sync with runtime ChannelOptions changes

ChannelBase.options is mutable (non-final), but MessageEditsMixin receives the options reference only at construction:

this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);

If caller code can replace ChannelOptions with a new instance after channel creation (rather than mutating the existing object), messageEditsMixin will continue using the original options, potentially missing updated encryption or other per-channel configuration.

You may want to:

  • verify how ChannelOptions are updated at runtime for REST channels, and
  • if replacement is possible, either:
    • pass a provider (or ChannelBase itself) into MessageEditsMixin so it always reads the current options, or
    • expose a small setOptions(ChannelOptions options) hook on MessageEditsMixin and call it from wherever ChannelBase.options is changed.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8448c8c and b5476b1.

📒 Files selected for processing (8)
  • lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/BaseMessage.java (2 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperation.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageSerializer.java (2 hunks)
  • lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java
  • lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java
🧰 Additional context used
🧬 Code graph analysis (3)
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (3)
lib/src/main/java/io/ably/lib/http/HttpUtils.java (1)
  • HttpUtils (28-280)
lib/src/main/java/io/ably/lib/util/Base64Coder.java (1)
  • Base64Coder (30-239)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (2)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (1)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check-liveobjects
  • GitHub Check: check-rest-httpurlconnection
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check-realtime-httpurlconnection
  • GitHub Check: check
  • GitHub Check: check (21)
  • GitHub Check: check (19)
  • GitHub Check: check (24)
  • GitHub Check: check (29)
  • GitHub Check: check (29)
  • GitHub Check: build
🔇 Additional comments (8)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)

157-159: LGTM!

The factory method follows the established pattern for body handlers in this class.

lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)

1-100: LGTM!

The MessageOperation class follows existing conventions in the codebase with public fields and package-private serialization methods. The implementation correctly handles optional fields by checking for null before serialization in both JSON and MessagePack formats.

lib/src/main/java/io/ably/lib/types/BaseMessage.java (2)

182-215: LGTM!

The refactoring cleanly separates the encoding logic from the mutation of instance fields. The new encodeData() method enables MessageOperationSerializer to encode message data without modifying the original Message object. The control flow correctly handles all data type cases (JsonElement, String, byte[]).


383-391: LGTM!

The EncodedMessageData inner class is a clean, immutable data holder appropriate for returning encoded results.

lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

72-169: LGTM!

The UpdateDeleteRequest class correctly handles encoding of message data and serialization to both JSON and MessagePack formats. The base64 encoding for binary data in JSON output follows the established pattern from BaseMessage.toJsonObject.

lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (2)

1179-1236: LGTM!

The getMessage, updateMessage, and their async variants are implemented correctly with proper delegation to messageEditsMixin. The overloads without MessageOperation parameter provide good API ergonomics.


1277-1322: LGTM!

The deleteMessage methods and their async variants follow the same consistent delegation pattern and provide appropriate overloads.

lib/src/main/java/io/ably/lib/rest/ChannelBase.java (1)

317-401: Overall message edits/deletes API shape looks good

The new edit/delete APIs (sync + async) with MessageOperation metadata and overloads without the MessageOperation parameter follow existing ChannelBase patterns and give a clear, ergonomic surface for the new feature.

@ttypic ttypic force-pushed the ECO-5642/edit-deletes-api branch from b5476b1 to 29d663b Compare November 25, 2025 13:22
@github-actions github-actions bot temporarily deployed to staging/pull/1179/features November 25, 2025 13:22 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1179/javadoc November 25, 2025 13:25 Inactive
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: 1

♻️ Duplicate comments (6)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (2)

434-442: Operator precedence makes condition confusing.

Line 438's condition relies on implicit operator precedence (&& before ||) which makes the logic harder to follow. While functionally correct, explicit parentheses improve readability.

-            if (history.items().length > 0 && predicate.test(history.items()) || System.currentTimeMillis() > timeout)
+            if ((history.items().length > 0 && predicate.test(history.items())) || System.currentTimeMillis() > timeout)

70-78: Incorrect helper method: test waits for MESSAGE_UPDATE on a freshly published message.

At line 72, the test calls waitForUpdatedMessageAppear() which waits for a message with action == MESSAGE_UPDATE. However, at this point the message was only published, not updated, so it should have action == MESSAGE_CREATE or similar. This will cause the test to timeout and return an unexpected state.

Use getMessage directly since no update was performed:

         // Retrieve the message by serial
-        Message retrievedMessage = waitForUpdatedMessageAppear(channel, publishedMessage.serial);
+        Message retrievedMessage = channel.getMessage(publishedMessage.serial);
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

55-66: Returning null on IOException will cause NPE downstream.

This issue was flagged in a previous review. When writeMsgpack encounters an IOException, returning null causes a NullPointerException when ByteArrayRequestBody is constructed at line 46.

Apply this diff to propagate the exception:

     private static byte[] writeMsgpack(UpdateDeleteRequest request) {
         try {
             ByteArrayOutputStream out = new ByteArrayOutputStream();
             MessagePacker packer = Serialisation.msgpackPackerConfig.newPacker(out);
             request.writeMsgpack(packer);
             packer.flush();
             return out.toByteArray();
         } catch (IOException e) {
-            Log.e(TAG, "Failed to write msgpack", e);
-            return null;
+            throw new RuntimeException("Failed to write msgpack", e);
         }
     }
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)

197-223: Inconsistent behavior: returns array with null element for unsupported content types.

This was flagged in a previous review. Two issues:

  1. Line 202: new String(body) uses platform default charset instead of UTF-8
  2. Line 219: Returns new Message[] { message } even when message is null (unsupported content type), which differs from MessageBodyHandler that returns null for the array

Apply this diff to align behavior and fix charset:

         @Override
         public Message[] handleResponseBody(String contentType, byte[] body) throws AblyException {
             try {
                 Message message = null;
                 if ("application/json".equals(contentType)) {
-                    message = Serialisation.gson.fromJson(new String(body), Message.class);
+                    message = Serialisation.gson.fromJson(new String(body, "UTF-8"), Message.class);
                 } else if ("application/x-msgpack".equals(contentType)) {
                     MessageUnpacker unpacker = Serialisation.msgpackUnpackerConfig.newUnpacker(body);
                     try {
                         message = new Message().readMsgpack(unpacker);
                     } catch (IOException ioe) {
                         throw AblyException.fromThrowable(ioe);
                     }
                 }

                 if (message != null) {
                     try {
                         message.decode(opts);
                     } catch (MessageDecodeException e) {
                         Log.e(TAG, e.errorInfo.message);
                     }
+                    return new Message[] { message };
                 }
-                return new Message[] { message };
+                return null;
             } catch (MessageDecodeException e) {
                 throw AblyException.fromThrowable(e);
             }
         }
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)

194-230: Clarify version ordering and consider aligning async error handling with historyAsync

  • The Javadoc for getMessageVersions says versions are “ordered chronologically”, which is ambiguous and (per previous feedback/tests) likely incorrect. If the service returns newest‑first, update the text to something like:
 * This method returns a paginated result containing all versions of the message,
 * ordered in reverse chronological order (newest first). Each version includes
 * metadata about when and by whom the message was modified.

and mirror that wording on the realtime ChannelBase wrapper.

  • For getMessageVersionsAsync, the serial validation happens in getMessageVersionsImpl (which throws AblyException), so calling the async method can fail synchronously. Other async flows like historyAsync instead capture parameter errors into a failed ResultRequest and surface them via the callback. If you want consistency, consider moving the serial check inside the BasePaginatedQuery (similar to historyImpl) and dropping throws AblyException from the async signature, so all errors are delivered through the callback.
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

1179-1352: Tighten message edit/delete/version API ergonomics and docs

The new realtime APIs nicely mirror the REST mixin, but a few polish items remain:

  • deleteMessage(String serial, Message message, MessageOperation operation) Javadoc has a truncated @param message (“may contain.”). Please complete this description (e.g. “may contain operation metadata such as clientId, description, or metadata in the version field”), consistent with the overload below.

  • getMessageVersions Javadoc says versions are “ordered chronologically”, which is ambiguous and (per existing feedback and tests) should likely be “ordered in reverse chronological order (newest first)”. Please align the wording here with the actual behaviour and with the REST mixin Javadoc.

  • Both update/delete overload sets assume a non‑null Message message and directly delegate to MessageEditsMixin, where message.action is written and auth.checkClientId(message, …) is called. If a caller accidentally passes null, this will result in a NullPointerException rather than an AblyException. Once you address this at the mixin level (see comment in MessageEditsMixin.updateMessageImpl / deleteMessageImpl), these wrappers will be safe too; no additional changes needed here, but it’s worth being aware of the dependency.

  • For symmetry with history(), consider adding convenience overloads:

    • public PaginatedResult<Message> getMessageVersions(String serial) → delegates to getMessageVersions(serial, null)
    • public void getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult<Message>> callback) → delegates to getMessageVersionsAsync(serial, null, callback)
      This matches prior feedback and aligns with the usage patterns in tests.
🧹 Nitpick comments (1)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

1458-1466: Avoid double initialization of messageEditsMixin

setOptions(...) now assigns:

this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);

and the constructor calls this.setOptions(options); and then reassigns:

this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);

The second assignment in the constructor is redundant and risks future divergence if setOptions(...) logic changes.

You can simplify by relying solely on setOptions(options) to initialise the mixin and remove the explicit assignment in the constructor:

ChannelBase(AblyRealtime ably, String name, ChannelOptions options, @Nullable LiveObjectsPlugin liveObjectsPlugin) throws AblyException {
    ...
    this.basePath = "/channels/" + HttpUtils.encodeURIComponent(name);
    this.setOptions(options);
    ...
-   this.annotations = new RealtimeAnnotations(
+   this.annotations = new RealtimeAnnotations(
        this,
        new RestAnnotations(name, ably.http, ably.options, options)
    );
-   this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);
}

This keeps all mixin construction in one place and removes unnecessary work.

Also applies to: 1518-1537

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b5476b1 and 29d663b.

📒 Files selected for processing (8)
  • lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (6 hunks)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/BaseMessage.java (2 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperation.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageSerializer.java (2 hunks)
  • lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java
🧰 Additional context used
🧬 Code graph analysis (4)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (5)
lib/src/main/java/io/ably/lib/types/ChannelOptions.java (1)
  • ChannelOptions (13-131)
lib/src/main/java/io/ably/lib/types/ErrorInfo.java (1)
  • ErrorInfo (17-197)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/Param.java (1)
  • Param (6-88)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
lib/src/main/java/io/ably/lib/types/BaseMessage.java (3)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (2)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)
  • MessageEditsMixin (23-232)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (3)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/util/Log.java (1)
  • Log (21-157)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check
  • GitHub Check: build
  • GitHub Check: check (29)
  • GitHub Check: check (24)
  • GitHub Check: check (21)
  • GitHub Check: check (29)
  • GitHub Check: check (19)
  • GitHub Check: check-liveobjects
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check-rest-httpurlconnection
  • GitHub Check: check-realtime-httpurlconnection
🔇 Additional comments (14)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)

1-100: LGTM!

The MessageOperation class is well-structured with clean serialization logic for both MessagePack and JSON formats. The null-handling for optional fields is correct, and the class follows the existing patterns in the codebase.

lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

72-168: LGTM!

The UpdateDeleteRequest inner class correctly handles message encoding and serialization for both JSON and MessagePack formats. The base64 encoding logic for binary data in JSON output properly appends the encoding suffix.

lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)

157-159: LGTM!

The factory method follows the existing pattern in the codebase.

lib/src/main/java/io/ably/lib/types/BaseMessage.java (2)

182-215: LGTM!

The refactoring cleanly separates the encoding logic into encodeData() which returns an EncodedMessageData object, while encode() simply delegates and updates fields. This enables reuse by MessageOperationSerializer without duplicating encoding logic. The encryption path correctly operates on the local decodedData variable.


383-391: LGTM!

The EncodedMessageData class is a clean, immutable data transfer object with appropriate package-private visibility for internal use.

lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (7)

36-48: LGTM!

Test class setup is well-structured with appropriate timeout configuration and standard initialization pattern.


84-117: LGTM!

The updateMessage_updateData test correctly verifies the update workflow: publish → retrieve → update → verify updated data and MESSAGE_UPDATE action.


119-156: LGTM!

Good coverage of encrypted channel message updates with ChannelOptions.withCipherKey.


195-261: LGTM!

The delete tests (deleteMessage_softDelete and deleteMessage_async) correctly verify soft-delete behavior and MESSAGE_DELETE action.


263-332: LGTM!

The getMessageVersions_retrieveHistory test comprehensively verifies version history including metadata from MessageOperation.


337-387: LGTM!

Error handling tests appropriately verify exceptions for invalid, null, and empty serials.


389-432: LGTM!

The completeWorkflow_publishUpdateVersionsDelete test provides excellent end-to-end coverage of the full message lifecycle.

lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

19-37: MessageEditsMixin wiring into ChannelBase looks correct

Importing MessageEditsMixin / MessageOperation and storing a volatile MessageEditsMixin field, then delegating all new APIs through it, is a reasonable way to keep the realtime surface thin and reuse the REST implementation. Assuming the mixin stays in sync with ChannelOptions (see comment on ctor/setOptions), there are no functional issues here.

Also applies to: 105-105

lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)

25-38: Constructor and getMessage implementation look consistent with existing REST patterns

Capturing basePath, ClientOptions, ChannelOptions, and Auth in the constructor and then using MessageSerializer.getSingleMessageResponseHandler(channelOptions) plus request IDs in getMessage* closely mirrors the existing history/REST flows and addresses earlier concerns about content‑type/charset handling. No issues spotted here.

Also applies to: 40-85

@ttypic ttypic force-pushed the ECO-5642/edit-deletes-api branch from 29d663b to 51d3719 Compare November 26, 2025 09:45
@github-actions github-actions bot temporarily deployed to staging/pull/1179/features November 26, 2025 09:46 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1179/javadoc November 26, 2025 09:47 Inactive
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: 1

♻️ Duplicate comments (4)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1)

54-79: Use a non‑update helper (or direct getMessage) in getMessage_retrieveBySerial

getMessage_retrieveBySerial calls waitForUpdatedMessageAppear, which polls until it sees MESSAGE_UPDATE, but this test never updates the message. The helper will just spin until the timeout elapses and then return whatever getMessage last returned, adding latency and hiding the fact that no update is expected here.

Consider simplifying this test to call channel.getMessage(publishedMessage.serial) directly (or introduce a helper that just waits for any non‑null message by serial) instead of reusing the “updated” helper.

Also applies to: 464-472

lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)

191-223: Avoid returning an array containing null and use explicit UTF‑8 decoding

SingleMessageBodyHandler.handleResponseBody currently:

  • Returns new Message[] { message } even when message is null, so callers like getMessageImpl will treat “no message parsed” as a successful lookup and return null instead of throwing “Message not found”.
  • Uses new String(body) which depends on the platform default charset rather than UTF‑8.

Consider aligning behavior with MessageBodyHandler:

  • When no message can be parsed (unsupported content type or parse failure), return null (or an empty array) instead of a single‑element array containing null, so messages != null && messages.length > 0 only passes when a real message exists.
  • Decode JSON using UTF‑8 explicitly, e.g. via new String(body, StandardCharsets.UTF_8) and add the corresponding import.

Example sketch:

-                if ("application/json".equals(contentType)) {
-                    message = Serialisation.gson.fromJson(new String(body), Message.class);
+                if ("application/json".equals(contentType)) {
+                    message = Serialisation.gson.fromJson(
+                        new String(body, java.nio.charset.StandardCharsets.UTF_8),
+                        Message.class
+                    );
                 }
@@
-                if (message != null) {
+                if (message != null) {
                     try {
                         message.decode(opts);
                     } catch (MessageDecodeException e) {
                         Log.e(TAG, e.errorInfo.message);
                     }
-                }
-                return new Message[] { message };
+                    return new Message[] { message };
+                }
+                return null;

This avoids surprising null elements and keeps charset handling deterministic.

lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

55-66: Don’t return null from writeMsgpack – propagate the failure instead

If writeMsgpack hits an IOException, it logs the error and returns null, which is then passed into new HttpUtils.ByteArrayRequestBody(packed, ...). That will likely cause a NullPointerException at request encoding time instead of a clear, Ably‑level exception.

Given asMsgPackRequest already declares throws AblyException, it’s safer to propagate:

-    private static byte[] writeMsgpack(UpdateDeleteRequest request) {
+    private static byte[] writeMsgpack(UpdateDeleteRequest request) throws AblyException {
         try {
             ByteArrayOutputStream out = new ByteArrayOutputStream();
             MessagePacker packer = Serialisation.msgpackPackerConfig.newPacker(out);
             request.writeMsgpack(packer);
             packer.flush();
             return out.toByteArray();
         } catch (IOException e) {
-            Log.e(TAG, "Failed to write msgpack", e);
-            return null;
+            Log.e(TAG, "Failed to write msgpack", e);
+            throw AblyException.fromThrowable(e);
         }
     }

and keep asMsgPackRequest unchanged except for handling the new throws AblyException on writeMsgpack. This avoids surprising NPEs and surfaces a meaningful error to callers.

lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)

97-137: Guard against null messages in update/delete operations

Both updateMessageImpl and deleteMessageImpl assume message is non‑null and immediately dereference message.serial. If a caller accidentally passes null, this will surface as a NullPointerException rather than a clear AblyException describing invalid input.

Consider adding an explicit guard near the top of each method:

     private Http.Request<Void> updateMessageImpl(Http http, Message message, MessageOperation operation) {
         return http.request((scheduler, callback) -> {
+            if (message == null) {
+                throw AblyException.fromErrorInfo(
+                    new ErrorInfo("Message cannot be null", 400, 40003)
+                );
+            }
             if (message.serial == null || message.serial.isEmpty()) {
                 throw AblyException.fromErrorInfo(new ErrorInfo("Message serial cannot be empty", 400, 40003));
             }
@@
     private Http.Request<Void> deleteMessageImpl(Http http, Message message, MessageOperation operation) {
         return http.request((scheduler, callback) -> {
+            if (message == null) {
+                throw AblyException.fromErrorInfo(
+                    new ErrorInfo("Message cannot be null", 400, 40003)
+                );
+            }
             if (message.serial == null || message.serial.isEmpty()) {
                 throw AblyException.fromErrorInfo(new ErrorInfo("Message serial cannot be empty", 400, 40003));
             }

This yields a predictable, Ably‑style error instead of an opaque NPE and aligns with the existing serial validation.

Also applies to: 148-187

🧹 Nitpick comments (2)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (2)

1449-1452: Avoid redundant messageEditsMixin initialisation

setOptions initialises messageEditsMixin (Line 1451), and the constructor both calls setOptions(options) and then reassigns messageEditsMixin again at Line 1527. The second assignment is redundant.

You can simplify by relying on setOptions for initialisation and dropping the explicit assignment in the constructor:

-        this.setOptions(options);
+        this.setOptions(options);
@@
-        this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);

This keeps the lifecycle in one place and avoids duplication.

Also applies to: 1527-1528


1181-1341: Consider documenting that realtime message edit APIs use REST under the hood

All new realtime getMessage / updateMessage / deleteMessage / getMessageVersions methods delegate to messageEditsMixin with ably.http, so they execute over REST even when called on a realtime channel.

This is a reasonable implementation given current server capabilities, but it may surprise users expecting websocket‑based semantics and ultra‑low latency. Consider adding a brief note to the class or method Javadocs clarifying that these operations are performed via REST calls so expectations around latency and failure modes are clear.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 29d663b and 51d3719.

📒 Files selected for processing (8)
  • lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (6 hunks)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/BaseMessage.java (2 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperation.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageSerializer.java (2 hunks)
  • lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (2)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (5)
lib/src/main/java/io/ably/lib/http/HttpCore.java (1)
  • HttpCore (39-583)
lib/src/main/java/io/ably/lib/http/HttpUtils.java (1)
  • HttpUtils (28-280)
lib/src/main/java/io/ably/lib/util/Base64Coder.java (1)
  • Base64Coder (30-239)
lib/src/main/java/io/ably/lib/util/Log.java (1)
  • Log (21-157)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (7)
lib/src/test/java/io/ably/lib/test/common/Helpers.java (2)
  • Helpers (74-1229)
  • CompletionSet (862-912)
lib/src/main/java/io/ably/lib/types/ChannelOptions.java (1)
  • ChannelOptions (13-131)
lib/src/main/java/io/ably/lib/types/ErrorInfo.java (1)
  • ErrorInfo (17-197)
lib/src/main/java/io/ably/lib/types/Message.java (1)
  • Message (25-443)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/Param.java (1)
  • Param (6-88)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (2)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)
  • MessageEditsMixin (23-227)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (1)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check
  • GitHub Check: build
  • GitHub Check: check (29)
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-rest-httpurlconnection
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check-realtime-httpurlconnection
  • GitHub Check: check-liveobjects
  • GitHub Check: check (19)
  • GitHub Check: check (21)
  • GitHub Check: check (24)
  • GitHub Check: check (29)
🔇 Additional comments (3)
lib/src/main/java/io/ably/lib/types/BaseMessage.java (1)

182-215: Encode refactor via encodeData looks consistent with previous behavior

The new encodeData(ChannelOptions) + EncodedMessageData split preserves the prior encoding rules (JSON element → JSON string, String → UTF‑8 bytes when encrypted, then cipher application) while making the transformation reusable for other serializers. No correctness issues stand out.

Also applies to: 383-391

lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)

14-98: MessageOperation structure and JSON/MsgPack serialization look sound

Field selection and dynamic field counts are correct, and both MsgPack and JSON paths only include non‑null fields. This fits the existing patterns used for similar types in the library.

lib/src/main/java/io/ably/lib/rest/ChannelBase.java (1)

49-50: REST message edit/delete/version wiring via MessageEditsMixin looks correct

The REST ChannelBase now composes a MessageEditsMixin and cleanly delegates all new message edit/delete/version operations to it. Constructor initialisation passes the expected basePath, ClientOptions, ChannelOptions, and Auth, and the public API surface on ChannelBase matches the mixin’s capabilities. No issues spotted in this wiring.

Also applies to: 317-481, 488-496

@ttypic ttypic force-pushed the ECO-5642/edit-deletes-api branch from 51d3719 to aa67dc6 Compare November 26, 2025 12:36
@github-actions github-actions bot temporarily deployed to staging/pull/1179/features November 26, 2025 12:36 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1179/javadoc November 26, 2025 12:39 Inactive
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: 0

♻️ Duplicate comments (8)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)

197-223: Address charset and null-handling issues from prior review.

The past review flagged two issues that still appear present:

  1. Line 202: new String(body) uses platform default charset instead of UTF-8
  2. Line 219: Returns new Message[] { message } even when message is null (unsupported content type), which differs from MessageBodyHandler behavior that returns the full array or null

Apply the suggested fix from the prior review:

 if ("application/json".equals(contentType)) {
-    message = Serialisation.gson.fromJson(new String(body), Message.class);
+    message = Serialisation.gson.fromJson(new String(body, "UTF-8"), Message.class);
 }
 ...
 if (message != null) {
     try {
         message.decode(opts);
     } catch (MessageDecodeException e) {
         Log.e(TAG, e.errorInfo.message);
     }
+    return new Message[] { message };
 }
-return new Message[] { message };
+return null;
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (3)

72-72: Incorrect helper method for initial message retrieval.

waitForUpdatedMessageAppear() waits for MESSAGE_UPDATE action, but at this point the message was only published and should have MESSAGE_CREATE action. This test may pass only due to timeout fallback returning the message regardless of action.

-        Message retrievedMessage = waitForUpdatedMessageAppear(channel, publishedMessage.serial);
+        Message retrievedMessage = channel.getMessage(publishedMessage.serial);

449-449: Add parentheses for operator precedence clarity.

The condition lacks parentheses around the main success check, making the logic harder to read. While the current behavior may work due to short-circuit evaluation, explicit grouping improves clarity.

-            if (history.items().length > 0 && predicate.test(history.items()) || System.currentTimeMillis() > timeout)
+            if ((history.items().length > 0 && predicate.test(history.items())) || System.currentTimeMillis() > timeout)

323-339: Incomplete async test - does not exercise async API.

This test publishes a message and retrieves history but never calls channel.getMessageVersionsAsync(...) or performs any assertions on version history. It doesn't exercise the async API it claims to test.

Either complete the test to actually call getMessageVersionsAsync with assertions, or remove it to avoid misleading test coverage.

lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

55-66: Returning null on IOException will cause NPE downstream.

When writeMsgpack encounters an IOException, it returns null, which is then passed to ByteArrayRequestBody at line 46. This will cause a NullPointerException when getEncoded() is called on the request body.

Propagate the exception instead:

 private static byte[] writeMsgpack(UpdateDeleteRequest request) {
     try {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         MessagePacker packer = Serialisation.msgpackPackerConfig.newPacker(out);
         request.writeMsgpack(packer);
         packer.flush();
         return out.toByteArray();
     } catch (IOException e) {
-        Log.e(TAG, "Failed to write msgpack", e);
-        return null;
+        throw new RuntimeException("Failed to write msgpack", e);
     }
 }
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

1328-1341: Consider adding convenience overloads for getMessageVersions.

Tests and common usage patterns would benefit from convenience overloads that don't require the params argument:

public PaginatedResult<Message> getMessageVersions(String serial) throws AblyException {
    return getMessageVersions(serial, null);
}

public void getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
    getMessageVersionsAsync(serial, null, callback);
}
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)

113-134: Add null guard for message parameter.

If a caller passes null for message, a NullPointerException will occur when accessing message.serial at line 115. Add an explicit check for clearer error messaging.

 private Http.Request<Void> updateMessageImpl(Http http, Message message, MessageOperation operation) {
     return http.request((scheduler, callback) -> {
+        if (message == null) {
+            throw AblyException.fromErrorInfo(new ErrorInfo("Message cannot be null", 400, 40003));
+        }
         if (message.serial == null || message.serial.isEmpty()) {
             throw AblyException.fromErrorInfo(new ErrorInfo("Message serial cannot be empty", 400, 40003));
         }

Apply the same guard to deleteMessageImpl at line 162.

lib/src/main/java/io/ably/lib/rest/ChannelBase.java (1)

454-478: Fix version ordering docs, add convenience overloads, and align async signature

  1. The Javadoc says versions are “ordered chronologically”, but tests and expected behaviour are reverse‑chronological (newest first). Please update the wording accordingly.

    Suggested tweak:

    • This method returns a paginated result containing all versions of the message,
    • ordered chronologically. Each version includes metadata about when and by whom
    • This method returns a paginated result containing all versions of the message,
    • ordered in reverse chronological order (newest first). Each version includes metadata about when and by whom
    
    
  1. There’s no convenience overloads without Param[]:

    • getMessageVersions(String serial) delegating to getMessageVersions(serial, null).
    • getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult<Message>> callback) delegating to the 3‑arg version.

    These match existing history() patterns and simplify common usage.

  • /**
  • * Convenience overload for retrieving all historical versions of a specific message
    
  • * without query parameters.
    
  • */
    
  • public PaginatedResult getMessageVersions(String serial) throws AblyException {
  •    return getMessageVersions(serial, null);
    
  • }
  • /**
  • * Convenience overload for asynchronously retrieving all historical versions of a specific message
    
  • * without query parameters.
    
  • */
    
  • public void getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult> callback) {
  •    getMessageVersionsAsync(serial, null, callback);
    
  • }




3. `getMessageVersionsAsync` declares `throws AblyException`, unlike other async APIs here (e.g. `historyAsync`, `getAsync`). Unless this method can synchronously throw for programmer errors, it would be more consistent to drop `throws AblyException` from the signature and surface errors exclusively via the callback.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)</summary><blockquote>

`1527-1527`: **Redundant initialization of `messageEditsMixin`.**

This line is redundant because `this.setOptions(options)` at line 1514 already initializes `messageEditsMixin`. The mixin will be created twice during construction.



Remove this redundant initialization:

```diff
      this.annotations = new RealtimeAnnotations(
          this,
          new RestAnnotations(name, ably.http, ably.options, options)
      );
-        this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);
  }
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (3)

49-50: MessageEditsMixin lifecycle vs ChannelOptions updates

messageEditsMixin is constructed once with the options instance passed into the constructor. If channel options can change at runtime (e.g. encryption or other per‑channel settings), the mixin may keep using stale options.

Consider either:

  • passing ChannelOptions into each mixin call instead of capturing it, or
  • ensuring any setOptions‑style logic also updates the mixin’s view of the options.

Also applies to: 488-496


345-353: Clarify operation metadata wording and align Javadoc between sync/async methods

The operation Javadoc currently says “metadata in the version field”, but MessageOperation exposes clientId, description, and a metadata map; there is no explicit “version field” on that type. Also, deleteMessageAsync’s message parameter Javadoc mentions “operation metadata”, while the sync variant just mentions the serial.

To reduce confusion:

  • Reword the operation param to something like “additional operation metadata such as clientId, description, or arbitrary key–value metadata”.
  • Make the message parameter description for delete/update sync and async variants consistent (likely just “containing the serial identifier” and, for updates, the fields to patch).

Also applies to: 374-379, 399-408, 428-445


52-60: Minor Javadoc polish for publish/publishAsync

The @param data descriptions end with a semicolon ("the message payload;"), which reads oddly compared with the rest of the file’s Javadocs. Consider changing to a period for consistency:

- * @param data the message payload;
+ * @param data the message payload.

Also applies to: 68-79

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 51d3719 and aa67dc6.

📒 Files selected for processing (8)
  • lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (6 hunks)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/BaseMessage.java (2 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperation.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageSerializer.java (2 hunks)
  • lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (9)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/types/ErrorInfo.java (1)
  • ErrorInfo (17-197)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/Param.java (1)
  • Param (6-88)
liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1)
  • keys (71-78)
pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestClient.kt (1)
  • channels (3-9)
liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (1)
  • data (162-172)
liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt (1)
  • action (164-220)
pubsub-adapter/src/test/kotlin/com/ably/Utils.kt (1)
  • waitFor (11-20)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (3)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/util/Log.java (1)
  • Log (21-157)
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (5)
lib/src/main/java/io/ably/lib/http/HttpCore.java (1)
  • HttpCore (39-583)
lib/src/main/java/io/ably/lib/http/HttpUtils.java (1)
  • HttpUtils (28-280)
lib/src/main/java/io/ably/lib/util/Base64Coder.java (1)
  • Base64Coder (30-239)
lib/src/main/java/io/ably/lib/util/Log.java (1)
  • Log (21-157)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (2)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)
  • MessageSerializer (23-228)
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (1)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (2)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)
  • MessageEditsMixin (23-223)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/BaseMessage.java (2)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check (21)
  • GitHub Check: check (19)
  • GitHub Check: check (24)
  • GitHub Check: check (29)
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-liveobjects
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check-rest-httpurlconnection
  • GitHub Check: check-realtime-httpurlconnection
  • GitHub Check: build
  • GitHub Check: check (29)
  • GitHub Check: check
🔇 Additional comments (7)
lib/src/main/java/io/ably/lib/types/BaseMessage.java (2)

182-215: Clean refactoring to separate encoding logic.

The decomposition of encode() into encodeData() that returns an EncodedMessageData result is a good design choice. This allows MessageOperationSerializer to obtain encoded data without mutating the original message object, which is important for the new message edit operations.


383-391: LGTM!

The EncodedMessageData class is a clean, immutable data holder with appropriate package-private visibility for internal use.

lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)

14-100: Well-structured metadata class.

The MessageOperation class follows established patterns in the codebase with consistent JSON and MsgPack serialization. Good reuse of MessageSerializer.write() for the metadata map.

lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

93-101: Good use of the new encodeData() method.

The constructor correctly uses message.encodeData(channelOptions) to obtain encoded data without mutating the original message. This is the intended use of the BaseMessage refactoring.

lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

1179-1314: Message edit/delete API surface looks well-designed.

The new public API methods for getMessage, updateMessage, deleteMessage, and their async variants follow consistent patterns with the existing channel methods. The delegation to messageEditsMixin keeps the implementation clean.

lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (2)

66-85: Well-structured implementation using new single-message response handler.

The getMessageImpl method correctly uses the new getSingleMessageResponseHandler and handles the response appropriately, throwing a 404 error when the message is not found.


213-221: LGTM!

The getMessageVersionsImpl properly validates the serial parameter and uses BasePaginatedQuery consistently with other paginated endpoints in the codebase.

- Introduced APIs to retrieve, update, and delete messages by serial identifier.
- Added support for fetching historical versions of messages.
- Implemented `MessageEditsMixin` to handle message editing operations.
- Added tests for message editing and retrieval functionality.
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: 0

♻️ Duplicate comments (8)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (2)

28-37: Add a setter for channelOptions to support runtime option changes.

The channelOptions field is final, but the realtime ChannelBase can change its options via setOptions(). Without a setter, the mixin will use stale options after the channel's options are updated at runtime.

-    private final ChannelOptions channelOptions;
+    private ChannelOptions channelOptions;

Add a setter method:

public void setChannelOptions(ChannelOptions channelOptions) {
    this.channelOptions = channelOptions;
}

Then call this setter from ChannelBase.setOptions() to keep the mixin in sync.

#!/bin/bash
# Check if ChannelBase.setOptions updates the mixin's channelOptions
rg -n -A10 "setOptions.*ChannelOptions" lib/src/main/java/io/ably/lib/rest/ChannelBase.java lib/src/main/java/io/ably/lib/realtime/ChannelBase.java

112-133: Guard against null message parameter.

Both updateMessageImpl and deleteMessageImpl assume a non-null Message. If a caller passes null, they'll get a NullPointerException from message.serial rather than a clear AblyException.

Add a null check at the start of the method:

     private Http.Request<Void> updateMessageImpl(Http http, Message message, MessageOperation operation) {
         return http.request((scheduler, callback) -> {
+            if (message == null) {
+                throw AblyException.fromErrorInfo(new ErrorInfo("Message cannot be null", 400, 40003));
+            }
             if (message.serial == null || message.serial.isEmpty()) {

Apply the same pattern to deleteMessageImpl at line 161.

lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)

55-66: Returning null on IOException will cause NPE downstream.

If writeMsgpack encounters an IOException, it logs the error and returns null. This null is passed to ByteArrayRequestBody at line 46, which will cause a NullPointerException when the request body is used.

Propagate the exception instead:

-    private static byte[] writeMsgpack(UpdateDeleteRequest request) {
+    private static byte[] writeMsgpack(UpdateDeleteRequest request) throws AblyException {
         try {
             ByteArrayOutputStream out = new ByteArrayOutputStream();
             MessagePacker packer = Serialisation.msgpackPackerConfig.newPacker(out);
             request.writeMsgpack(packer);
             packer.flush();
             return out.toByteArray();
         } catch (IOException e) {
-            Log.e(TAG, "Failed to write msgpack", e);
-            return null;
+            throw AblyException.fromThrowable(e);
         }
     }

Since asMsgPackRequest already declares throws AblyException, this change is compatible.

lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (3)

70-78: Incorrect wait helper for initial message retrieval.

waitForUpdatedMessageAppear() waits for a message with action == MESSAGE_UPDATE, but a newly published message will have MESSAGE_CREATE action (or null), not MESSAGE_UPDATE. This test may time out or return incorrect results.

Use getMessage() directly since there's no update to wait for:

-        Message retrievedMessage = waitForUpdatedMessageAppear(channel, publishedMessage.serial);
+        Message retrievedMessage = channel.getMessage(publishedMessage.serial);

322-339: Incomplete async test - no async call or assertions.

getMessageVersions_async only publishes a message and fetches history synchronously; it never invokes getMessageVersionsAsync() or makes any assertions on version history. This gives a misleading sense of coverage.

Either complete the test to actually exercise the async API:

// After getting publishedMessage...
Helpers.AsyncWaiter<AsyncPaginatedResult<Message>> waiter = new Helpers.AsyncWaiter<>();
channel.getMessageVersionsAsync(publishedMessage.serial, null, waiter);
AsyncPaginatedResult<Message> versions = waiter.waitFor();
assertNotNull("Expected non-null versions", versions);

Or remove the test to avoid false coverage.


447-452: Operator precedence issue in condition.

The condition history.items().length > 0 && predicate.test(history.items()) || System.currentTimeMillis() > timeout will evaluate incorrectly due to && having higher precedence than ||. The timeout check should be grouped with the entire condition.

-            if (history.items().length > 0 && predicate.test(history.items()) || System.currentTimeMillis() > timeout)
+            if ((history.items().length > 0 && predicate.test(history.items())) || System.currentTimeMillis() > timeout)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

1315-1341: Tighten versions API docs and add convenience overloads before release

Two points here:

  1. Version ordering Javadoc is ambiguous (also raised previously):

    • The comment says “ordered chronologically” (Line 1318), while tests and API design typically expect reverse chronological (newest first), similar to history.
    • Consider clarifying to “ordered in reverse chronological order (newest first)” to avoid confusion.
  2. Missing convenience overloads likely cause test/compile friction (also raised previously):

    • Tests and callers often expect getMessageVersions(serial) and getMessageVersionsAsync(serial, callback) overloads, analogous to history(...).
    • Currently only the (String serial, Param[] params) and (String serial, Param[] params, Callback<...>) methods exist.

You can address both with something like:

-     * This method returns a paginated result containing all versions of the message,
-     * ordered chronologically. Each version includes metadata about when and by whom
+     * This method returns a paginated result containing all versions of the message,
+     * ordered in reverse chronological order (newest first). Each version includes metadata about when and by whom
      * the message was modified.
@@
     public PaginatedResult<Message> getMessageVersions(String serial, Param[] params) throws AblyException {
         return messageEditsMixin.getMessageVersions(ably.http, serial, params);
     }
+
+    /**
+     * Convenience overload that retrieves all historical versions of a specific message
+     * without specifying query parameters.
+     *
+     * @param serial The unique serial identifier of the message.
+     * @return A {@link PaginatedResult} containing an array of {@link Message} objects
+     *         representing all versions of the message.
+     * @throws AblyException If the versions cannot be retrieved.
+     */
+    public PaginatedResult<Message> getMessageVersions(String serial) throws AblyException {
+        return getMessageVersions(serial, null);
+    }
@@
-    public void getMessageVersionsAsync(String serial, Param[] params, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
+    public void getMessageVersionsAsync(String serial, Param[] params, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
         messageEditsMixin.getMessageVersionsAsync(ably.http, serial, params, callback);
     }
+
+    /**
+     * Convenience overload for asynchronously retrieving all historical versions of a specific
+     * message without specifying query parameters.
+     *
+     * @param serial The unique serial identifier of the message.
+     * @param callback A callback to handle the result asynchronously.
+     * @throws AblyException If the versions cannot be retrieved.
+     */
+    public void getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
+        getMessageVersionsAsync(serial, null, callback);
+    }

Additionally, getMessageVersionsAsync(...) is the only async channel method here that declares throws AblyException. For API consistency with historyAsync(...) and other async methods, consider moving any synchronous validation into the mixin and surfacing failures via the callback instead of a checked exception on this public async API.

lib/src/main/java/io/ably/lib/rest/ChannelBase.java (1)

454-479: Align versions docs/overloads/async signature with expectations

Same concerns as on the realtime channel:

  1. Clarify version ordering in Javadoc (previously raised):

    • “ordered chronologically” (Line 457) is ambiguous; tests and history APIs typically expect newest-first behaviour.
    • Recommend: “ordered in reverse chronological order (newest first)”.
  2. Add convenience overloads for callers/tests (previously raised):

    • Only (String serial, Param[] params) and (String serial, Param[] params, Callback<...>) exist.
    • Tests and client code often use getMessageVersions(serial) and getMessageVersionsAsync(serial, callback).

Suggested changes:

-     * This method returns a paginated result containing all versions of the message,
-     * ordered chronologically. Each version includes metadata about when and by whom
+     * This method returns a paginated result containing all versions of the message,
+     * ordered in reverse chronological order (newest first). Each version includes metadata about when and by whom
      * the message was modified.
@@
     public PaginatedResult<Message> getMessageVersions(String serial, Param[] params) throws AblyException {
         return messageEditsMixin.getMessageVersions(ably.http, serial, params);
     }
+
+    /**
+     * Convenience overload that retrieves all historical versions of a specific message
+     * without specifying query parameters.
+     *
+     * @param serial The unique serial identifier of the message.
+     * @return A {@link PaginatedResult} containing an array of {@link Message} objects
+     *         representing all versions of the message.
+     * @throws AblyException If the versions cannot be retrieved.
+     */
+    public PaginatedResult<Message> getMessageVersions(String serial) throws AblyException {
+        return getMessageVersions(serial, null);
+    }
@@
-    public void getMessageVersionsAsync(String serial, Param[] params, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
+    public void getMessageVersionsAsync(String serial, Param[] params, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
         messageEditsMixin.getMessageVersionsAsync(ably.http, serial, params, callback);
     }
+
+    /**
+     * Convenience overload for asynchronously retrieving all historical versions of a specific
+     * message without specifying query parameters.
+     *
+     * @param serial The unique serial identifier of the message.
+     * @param callback A callback to handle the result asynchronously.
+     * @throws AblyException If the versions cannot be retrieved.
+     */
+    public void getMessageVersionsAsync(String serial, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
+        getMessageVersionsAsync(serial, null, callback);
+    }

As with the realtime class, consider whether getMessageVersionsAsync(...) should expose throws AblyException at all, given other async methods on this type do not. It may be cleaner to keep synchronous validation in the mixin and surface failures via the callback rather than a checked exception on the public API.

🧹 Nitpick comments (2)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)

184-196: Clarify version ordering in documentation.

The documentation states versions are "ordered chronologically" which is ambiguous. Based on test expectations (line 313-316 in the test file checking the last item for latest data), versions appear to be in chronological order (oldest first). Consider making this explicit.

-     * ordered chronologically. Each version includes metadata about when and by whom
+     * ordered in chronological order (oldest first). Each version includes metadata about when and by whom
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

105-105: Avoid double-initialising messageEditsMixin

messageEditsMixin is initialised both in setOptions(...) (Line 1451) and again in the constructor (Line 1527). The second assignment is redundant and risks future divergence if the initialisation logic changes in one place but not the other.

You can rely on setOptions(...) for initialisation and drop the explicit constructor assignment:

     public void setOptions(ChannelOptions options, CompletionListener listener) throws AblyException {
         this.options = options;
         this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);
@@
         this.annotations = new RealtimeAnnotations(
             this,
             new RestAnnotations(name, ably.http, ably.options, options)
         );
-        this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);
     }

Also applies to: 1450-1452, 1527-1528

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between aa67dc6 and c6cf81d.

📒 Files selected for processing (8)
  • lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (6 hunks)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java (5 hunks)
  • lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/BaseMessage.java (2 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperation.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1 hunks)
  • lib/src/main/java/io/ably/lib/types/MessageSerializer.java (2 hunks)
  • lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/src/main/java/io/ably/lib/types/MessageSerializer.java
🧰 Additional context used
🧬 Code graph analysis (3)
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (2)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)
  • MessageEditsMixin (22-222)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/BaseMessage.java (4)
lib/src/main/java/io/ably/lib/util/Serialisation.java (1)
  • Serialisation (40-299)
lib/src/main/java/io/ably/lib/util/Log.java (1)
  • Log (21-157)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (12)
lib/src/main/java/io/ably/lib/http/BasePaginatedQuery.java (1)
  • BasePaginatedQuery (25-345)
lib/src/main/java/io/ably/lib/http/Http.java (1)
  • Http (10-99)
lib/src/main/java/io/ably/lib/http/HttpCore.java (1)
  • HttpCore (39-583)
lib/src/main/java/io/ably/lib/http/HttpUtils.java (1)
  • HttpUtils (28-280)
lib/src/main/java/io/ably/lib/types/AblyException.java (1)
  • AblyException (12-67)
lib/src/main/java/io/ably/lib/types/ErrorInfo.java (1)
  • ErrorInfo (17-197)
lib/src/main/java/io/ably/lib/types/Message.java (1)
  • Message (25-443)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (1)
  • MessageOperation (14-100)
lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (1)
  • MessageOperationSerializer (18-172)
lib/src/main/java/io/ably/lib/types/MessageSerializer.java (1)
  • MessageSerializer (23-228)
lib/src/main/java/io/ably/lib/types/Param.java (1)
  • Param (6-88)
lib/src/main/java/io/ably/lib/util/Crypto.java (1)
  • Crypto (25-410)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check (29)
  • GitHub Check: check-realtime-httpurlconnection
  • GitHub Check: check-rest-httpurlconnection
  • GitHub Check: check (21)
  • GitHub Check: check (19)
  • GitHub Check: check (24)
  • GitHub Check: check (29)
  • GitHub Check: check-liveobjects
  • GitHub Check: check
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-rest-okhttp
  • GitHub Check: build
🔇 Additional comments (17)
lib/src/main/java/io/ably/lib/types/MessageOperation.java (3)

14-48: LGTM!

The class is well-structured with clear documentation, appropriate visibility modifiers, and consistent serialization patterns matching other types in the codebase.


56-76: LGTM!

The MessagePack serialization correctly handles optional fields by counting non-null fields before packing the map header, and properly delegates metadata serialization to MessageSerializer.write().


84-98: LGTM!

The JSON serialization mirrors the MessagePack approach, properly handling optional fields and using Serialisation.gson.toJsonTree() for the metadata map.

lib/src/main/java/io/ably/lib/types/BaseMessage.java (2)

182-215: Clean refactor enabling immutable encoding.

The extraction of encodeData() allows serializers to obtain encoded data without mutating the original message, addressing the mutation concerns raised in past reviews. The logic correctly handles JSON elements, UTF-8 conversion for encryption, and cipher application.


383-391: LGTM!

The EncodedMessageData class is a simple, immutable value holder appropriate for returning the encoded result. The package-private visibility is correct since it's only used internally by MessageOperationSerializer.

lib/src/main/java/io/ably/lib/rest/MessageEditsMixin.java (1)

65-84: LGTM! Properly uses the shared single-message response handler.

The implementation correctly uses MessageSerializer.getSingleMessageResponseHandler() as suggested in past reviews, ensuring consistent content-type handling and charset decoding.

lib/src/main/java/io/ably/lib/types/MessageOperationSerializer.java (3)

93-101: Good use of encodeData() to avoid double encoding.

Using message.encodeData(channelOptions) instead of message.encode() correctly obtains the encoded data without mutating the input message, addressing the double encoding concerns raised in past reviews.


109-145: LGTM!

The MessagePack serialization correctly handles all field types including binary data and properly delegates to nested object serializers.


150-168: LGTM!

The JSON serialization correctly applies base64 encoding for byte[] data and updates the encoding string accordingly, matching the pattern in BaseMessage.toJsonObject().

lib/src/test/java/io/ably/lib/test/rest/RestChannelMessageEditTest.java (5)

464-472: LGTM - operator precedence is correct.

The condition now has proper parentheses: (message != null && message.action == MessageAction.MESSAGE_UPDATE) || ...


474-482: LGTM - operator precedence is correct.

The condition now has proper parentheses: (message != null && message.action == MessageAction.MESSAGE_DELETE) || ...


36-48: LGTM!

Good test setup with appropriate timeout rule and proper initialization of the Ably REST client.


268-317: Comprehensive version history test with MessageOperation metadata verification.

The test properly verifies that MessageOperation metadata (description, metadata map) is preserved and retrievable in version history. Good coverage of the operation metadata feature.


398-443: Excellent end-to-end workflow test.

This comprehensive test covers the complete message lifecycle (publish → update → verify → delete → verify versions), providing good integration coverage for the new message editing feature.

lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (1)

19-20: Realtime message edit/delete API wiring looks solid

The new imports (MessageEditsMixin, MessageOperation) and the Message Edits & Deletes region are wired cleanly:

  • All methods delegate to messageEditsMixin using ably.http, mirroring the existing REST-style history(...) behaviour.
  • Sync/async overloads and MessageOperation usage are symmetric across update/delete.
  • Overloads that omit MessageOperation correctly delegate with null.

No functional issues spotted here; just rely on the mixin tests to keep behaviour aligned with the REST side.

Also applies to: 36-37, 1179-1331

lib/src/main/java/io/ably/lib/rest/ChannelBase.java (2)

14-14: MessageEditsMixin wiring on REST channels looks correct

The REST ChannelBase wiring for edits support looks coherent:

  • MessageOperation import (Line 14) is used only in the new edit/delete APIs.
  • messageEditsMixin is final and initialised once in the constructor (Line 495) with basePath, ably.options, options, and ably.auth, matching the realtime wiring.
  • Using a dedicated mixin keeps REST and Realtime implementations aligned while centralising HTTP/serialization logic.

No issues here.

Also applies to: 49-50, 488-496


317-452: REST message edit/delete API surface is consistent and well factored

The new Message Edits and Deletes region mirrors the realtime API:

  • Symmetric sync/async methods for get/update/delete.
  • Overloads that omit MessageOperation delegate with null.
  • All operations delegate cleanly to messageEditsMixin using ably.http, consistent with existing history(...) patterns.

This symmetry between REST and Realtime surfaces is good for usability.

@ttypic ttypic merged commit 16d372d into main Nov 27, 2025
18 of 19 checks passed
@ttypic ttypic deleted the ECO-5642/edit-deletes-api branch November 27, 2025 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants