Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[CHAT-5063] feat: add createWrapperSdkProxy implementation for kotlin adapter of ably-java #1066

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

ttypic
Copy link
Contributor

@ttypic ttypic commented Feb 13, 2025

We want to be able to distinguish requests made inside the wrapper from those made by the core PubSub SDK without the wrapper, allowing us to track agents across wrapper SDKs such as the Chat SDK or Asset Tracking. To achieve this, we introduce special proxy Realtime and Rest clients that inject additional agents parameters into the underlying SDK.

To do this we introduced several package-private methods that make it possible to use custom http module to invoke Rest API, so we can inject agent information.

Also special SdkWrapperCompatible introduced, allowing us to hide createWrapperSdkProxy from the public interface and use extension function instead

Summary by CodeRabbit

  • New Features

    • Introduced API enhancements that allow creating shallow client copies for both Real-time and REST, offering flexible HTTP configuration.
    • Added proxy client support to enable SDK wrapper integrations.
    • Upgraded dependency management with new networking libraries.
    • Implemented an embedded HTTP server for testing purposes.
  • Bug Fixes

    • Improved handling of agent information in channel parameters and headers for both Realtime and REST clients.
  • Refactor

    • Updated channel history and presence operations to explicitly accept an HTTP context for improved consistency.
    • Adjusted internal access levels to support future extensibility.
    • Consolidated import statements for better code clarity.

Copy link

coderabbitai bot commented Feb 13, 2025

Walkthrough

This pull request introduces several additive changes across the codebase. New constructors and methods are added to the AblyRest, AblyRealtime, and AblyBase classes to enable the creation of shallow copies with modified HTTP core parameters. Several HTTP-related classes now include methods (e.g., exchangeHttpCore and injectDynamicAgents) to enable enhanced HTTP context management. Method signatures in channel history and publish operations have been updated to accept an explicit HTTP parameter. Additionally, dependency management has been updated with new coroutine dependencies, and new wrapper classes and interfaces have been implemented in the pubsub adapter for SDK proxy creation.

Changes

File(s) Change Summary
android/.../AblyRest.java
java/.../AblyRest.java
Added a protected constructor and a public createShallowCopy method for creating shallow copies of AblyRest instances.
gradle/libs.versions.toml Added new versions for coroutine and turbine, and introduced new libraries (coroutine-core, coroutine-test).
lib/.../AsyncHttpScheduler.java
lib/.../Http.java
Introduced new exchangeHttpCore methods and a private constructor in AsyncHttpScheduler to support HTTP core exchanges.
lib/.../HttpCore.java
lib/.../AgentHeaderCreator.java
Added a private dynamicAgents variable, a constructor for state copying, and an injectDynamicAgents method; enhanced agent header creation in AgentHeaderCreator.
lib/.../HttpScheduler.java Changed the executor field visibility from private to protected.
lib/.../AblyRealtime.java
lib/.../AblyBase.java
Added new constructors and createShallowCopy methods to support shallow copy creation for Realtime and Rest base clients.
lib/.../ChannelBase.java
lib/.../Presence.java
Updated history, publish, get, and async methods to include an explicit Http parameter for precise HTTP context handling.
pubsub-adapter/.../WrapperSdkProxy.kt
pubsub-adapter/.../RealtimeClientAdapter.kt
pubsub-adapter/.../WrapperRealtimeClient.kt
pubsub-adapter/.../RestClientAdapter.kt
pubsub-adapter/.../WrapperRestClient.kt
Introduced new Kotlin wrapper classes and the SdkWrapperCompatible interface with methods for creating SDK wrapper proxies for Realtime and Rest clients.
pubsub-adapter/build.gradle.kts Expanded testImplementation dependencies for testing libraries and added a custom test task configuration.
pubsub-adapter/src/test/.../EmbeddedServer.kt Introduced an embedded HTTP server using NanoHTTPD with request and response handling classes.
pubsub-adapter/src/test/.../Utils.kt Added a new suspend function waitFor for asynchronous waiting with timeout.
pubsub-adapter/src/test/.../SdkWrapperAgentChannelParamTest.kt Created tests to verify agent information addition to Realtime channel parameters.
pubsub-adapter/src/test/.../SdkWrapperAgentHeaderTest.kt Added tests to validate agent headers in requests made through wrapper SDK clients.
pubsub-adapter/src/test/.../ChannelUtils.kt Added an extension property channelOptions to ChannelBase for easier access to channel options.

Sequence Diagram(s)

sequenceDiagram
    participant Client as AblyRest/AblyRealtime Client
    participant Method as createShallowCopy Method
    participant Constructor as Protected Constructor
    Client->>Method: Call createShallowCopy(httpCore, http)
    Method->>Constructor: Invoke constructor(underlyingClient, httpCore, http)
    Constructor-->>Method: Return shallow copy instance
    Method-->>Client: Return new instance
Loading
sequenceDiagram
    participant SDK as SDK/User
    participant Adapter as RealtimeClientAdapter
    participant Underlying as AblyRealtime javaClient
    SDK->>Adapter: Call createWrapperSdkProxy(options)
    Adapter->>Underlying: Inject SDK agents into HttpCore
    Adapter->>Underlying: Create shallow copy with modified HTTP core
    Adapter-->>SDK: Return WrapperRealtimeClient instance
Loading

Suggested reviewers

  • sacOO7

Poem

Oh, I’m a rabbit, hopping along,
Crafting shallow copies as I sing my song.
New constructors and wrappers pave the way,
For HTTP and proxy play all day.
With a twitch of my nose and a joyful leap,
My code hops forward, promises to keep!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

- we introduced several package-private methods that make it possible to use custom http module to invoke Rest API

We want to be able to distinguish requests made inside the wrapper from those made by the core PubSub SDK without the wrapper, allowing us to track agents across wrapper SDKs such as the Chat SDK or Asset Tracking. To achieve this, we introduce special proxy Realtime and Rest clients that inject additional agents parameters into the underlying SDK.
@ttypic ttypic force-pushed the CHAT-5063/add-sdk-wrapper-kotlin branch from 9dd1d06 to 585e5d5 Compare February 13, 2025 14:00
@github-actions github-actions bot temporarily deployed to staging/pull/1066/features February 13, 2025 14:01 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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 6

🧹 Nitpick comments (21)
pubsub-adapter/src/main/kotlin/com/ably/http/HttpMethod.kt (2)

3-9: Add KDoc documentation to the enum class.

Consider adding documentation to explain the purpose and usage of this enum class, especially since it's part of a public API.

+/**
+ * Represents HTTP methods supported by the Ably REST API.
+ * Used by the wrapper SDK proxy to make HTTP requests to Ably's servers.
+ */
 enum class HttpMethod(private val method: String) {

3-12: Consider enhancing the enum implementation.

The implementation looks good! Here are some suggestions to make it more robust:

  1. Consider implementing a common interface or marking the enum as sealed for better type safety
  2. Consider adding support for other HTTP methods like HEAD, OPTIONS that might be needed in the future
  3. Consider adding a companion object with a method to parse string representations safely

Here's an example implementation:

/**
 * Represents HTTP methods supported by the Ably REST API.
 * Used by the wrapper SDK proxy to make HTTP requests to Ably's servers.
 */
sealed class HttpMethod(private val method: String) {
    object Get : HttpMethod("GET")
    object Post : HttpMethod("POST")
    object Put : HttpMethod("PUT")
    object Delete : HttpMethod("DELETE")
    object Patch : HttpMethod("PATCH")
    // Add more methods as needed
    
    override fun toString() = method
    
    companion object {
        /**
         * Safely creates an HttpMethod from its string representation.
         * @param method The HTTP method string (case-insensitive)
         * @return The corresponding HttpMethod or null if not supported
         */
        fun fromString(method: String): HttpMethod? = when (method.uppercase()) {
            "GET" -> Get
            "POST" -> Post
            "PUT" -> Put
            "DELETE" -> Delete
            "PATCH" -> Patch
            else -> null
        }
    }
}
pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/WrapperRealtimeClient.kt (1)

21-36: Consider robust error handling for history retrieval.

Currently, calls to javaChannel.history and javaChannel.historyAsync are delegated directly. For improved resilience, consider how you might handle or propagate errors (e.g., network timeouts or permission issues) more gracefully using your wrapper.

pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt (2)

42-63: Consider clarifying concurrency and error handling for attach/detach.
When multiple threads invoke attach or detach concurrently, certain race conditions might arise if the underlying implementation does not handle synchronization. Documenting or enforcing concurrency strategies (e.g., making the calls idempotent) within the contract would help consumers.


64-137: Unify event subscription overloads and enhance parameter clarity.
This interface provides multiple subscribe overloads, as well as multiple publish overloads. While they are flexible, consider introducing a single or vararg-based approach for event names to reduce repetition. Similarly, clarifying the concurrency model (e.g., which thread publishes messages) can help developers build robust integrations.

pubsub-adapter/src/main/kotlin/com/ably/pubsub/Client.kt (2)

17-44: Align doc references with Kotlin conventions.
These doc lines reference “{@link AutoCloseable}” in a JavaDoc style. For a Kotlin interface, consider using KDoc formatting (e.g., [AutoCloseable]) to better align with Kotlin standards. This improves readability for Kotlin consumers.


73-174: Evaluate replacing sync/async methods with coroutines or reactive approaches.
Currently, the interface exposes synchronous methods (stats, request) and asynchronous counterparts (statsAsync, requestAsync). While this is fine, a unified coroutine- or reactive-based approach (e.g., using Flow, suspend functions, or RxJava Observables) could simplify usage. If you prefer the current approach, ensure thorough coverage in documentation and tests to help consumers handle both patterns effectively.

pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimePresence.kt (1)

37-74: Consolidate subscribe overloads for better maintainability.
Similar to RealtimeChannel, the subscribe overloads accept single or multiple actions. If you frequently change presence-subscription logic, consider standardizing the method signatures (e.g., a vararg of actions or a single method with optional parameters) to reduce overhead in code maintenance.

pubsub-adapter/src/main/kotlin/com/ably/query/TimeUnit.kt (1)

1-16: Consider adding a default value constant.

The documentation mentions that if omitted, the unit defaults to "minute". Consider adding a companion object with a default value for easier access:

 enum class TimeUnit(private val unit: String) {
   Minute("minute"),
   Hour("hour"),
   Day("day"),
   Month("month"),
   ;

   override fun toString() = unit
+
+  companion object {
+    val DEFAULT = Minute
+  }
 }
pubsub-adapter/src/main/kotlin/com/ably/pubsub/WrapperSdkProxy.kt (1)

3-3: Consider validating agent map entries.

The WrapperSdkProxyOptions data class accepts any string key-value pairs. Consider adding validation or documentation for expected agent keys and values.

-data class WrapperSdkProxyOptions(val agents: Map<String, String>)
+data class WrapperSdkProxyOptions(val agents: Map<String, String>) {
+  init {
+    require(agents.all { (key, value) -> key.isNotBlank() && value.isNotBlank() }) {
+      "Agent keys and values must not be blank"
+    }
+  }
+}
pubsub-adapter/src/main/kotlin/io/ably/lib/Utils.kt (1)

7-16: Add input validation for parameters.

The utility functions accept parameters without validation. Consider adding checks for:

  • Negative timestamps
  • Non-positive limits
  • Blank client/connection IDs

Apply this diff to add validation:

 fun buildStatsParams(
   start: Long?,
   end: Long?,
   limit: Int,
   orderBy: OrderBy,
   unit: TimeUnit,
 ) = buildList {
+  require(limit > 0) { "Limit must be positive" }
+  start?.let { require(it >= 0) { "Start timestamp must not be negative" } }
+  end?.let { require(it >= 0) { "End timestamp must not be negative" } }
+  end?.let { start?.let { s -> require(it >= s) { "End must not be before start" } } }
   addAll(buildHistoryParams(start, end, limit, orderBy))
   add(Param("unit", unit.toString()))
 }

 fun buildHistoryParams(
   start: Long?,
   end: Long?,
   limit: Int,
   orderBy: OrderBy,
 ) = buildList {
+  require(limit > 0) { "Limit must be positive" }
+  start?.let { require(it >= 0) { "Start timestamp must not be negative" } }
+  end?.let { require(it >= 0) { "End timestamp must not be negative" } }
+  end?.let { start?.let { s -> require(it >= s) { "End must not be before start" } } }
   start?.let { add(Param("start", it)) }
   end?.let { add(Param("end", it)) }
   add(Param("limit", limit))
   add(Param("direction", orderBy.direction))
 }

 fun buildRestPresenceParams(
   limit: Int,
   clientId: String?,
   connectionId: String?,
 ) = buildList {
+  require(limit > 0) { "Limit must be positive" }
+  clientId?.let { require(it.isNotBlank()) { "ClientId must not be blank" } }
+  connectionId?.let { require(it.isNotBlank()) { "ConnectionId must not be blank" } }
   add(Param("limit", limit))
   clientId?.let { add(Param("clientId", it)) }
   connectionId?.let { add(Param("connectionId", it)) }
 }

Also applies to: 18-28, 30-38

pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelsAdapter.kt (1)

7-20: Consider abstracting common adapter code.

The implementation is nearly identical to RestChannelsAdapter. Consider creating a common base class or interface to reduce code duplication.

Example abstraction:

abstract class BaseChannelsAdapter<T, C>(protected val javaChannels: C) : Channels<T> where C : AblyBase.Channels {
    override fun contains(name: String): Boolean = javaChannels.containsKey(name)
    override fun release(name: String) = javaChannels.release(name)
    protected abstract fun wrapChannel(channel: io.ably.lib.types.Channel): T
    
    override fun get(name: String): T = wrapChannel(javaChannels.get(name))
    override fun get(name: String, options: ChannelOptions): T =
        wrapChannel(javaChannels.get(name, options))
    
    override fun iterator(): Iterator<T> = iterator {
        javaChannels.entrySet().forEach { yield(wrapChannel(it.value)) }
    }
}

internal class RestChannelsAdapter(javaChannels: AblyBase.Channels) :
    BaseChannelsAdapter<RestChannel, AblyBase.Channels>(javaChannels) {
    override fun wrapChannel(channel: io.ably.lib.types.Channel) = RestChannelAdapter(channel)
}

internal class RealtimeChannelsAdapter(javaChannels: AblyRealtime.Channels) :
    BaseChannelsAdapter<RealtimeChannel, AblyRealtime.Channels>(javaChannels) {
    override fun wrapChannel(channel: io.ably.lib.types.Channel) = RealtimeChannelAdapter(channel)
}
pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestChannel.kt (1)

17-17: Fix incorrect package reference in KDoc.

The reference to io.ably.types.Data should be io.ably.lib.types.Data.

-   * @param data the message payload; see [io.ably.types.Data] for details of supported data types.
+   * @param data the message payload; see [io.ably.lib.types.Data] for details of supported data types.
pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestPresenceAdapter.kt (1)

21-26: Consider using a data class for async method parameters.

The async methods have long parameter lists which could be encapsulated in a data class for better maintainability.

Example:

data class HistoryParams(
    val start: Long?,
    val end: Long?,
    val limit: Int,
    val orderBy: OrderBy
)

fun historyAsync(
    callback: Callback<AsyncPaginatedResult<PresenceMessage>>,
    params: HistoryParams
)

Also applies to: 31-38

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

38-44: Enhance constructor documentation.

While the constructor implementation is correct, consider adding @param documentation for better clarity:

 /**
  * Constructor implementation to be able to have shallow copy of the client,
  * allowing us to modify certain fields while implementing a proxy for the Realtime/Rest SDK wrapper
+ *
+ * @param underlyingClient The original client to copy from
+ * @param httpCore The HTTP core implementation to use
+ * @param http The HTTP implementation to use
  */
pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt (1)

57-63: Consider adding cancellation support for async operation.

For better resource management, consider adding a way to cancel the async operation if the result is no longer needed.

 fun historyAsync(
   callback: Callback<AsyncPaginatedResult<Message>>,
   start: Long? = null,
   end: Long? = null,
   limit: Int = 100,
   orderBy: OrderBy = OrderBy.NewestFirst,
-  )
+  ): Cancellable
lib/src/main/java/io/ably/lib/http/Http.java (1)

29-31: Add null check for httpCore parameter.

The exchangeHttpCore method should validate the input parameter to prevent NullPointerException.

 public Http exchangeHttpCore(HttpCore httpCore) {
+    if (httpCore == null) {
+        throw new IllegalArgumentException("httpCore cannot be null");
+    }
     return new Http(asyncHttp.exchangeHttpCore(httpCore), new SyncHttpScheduler(httpCore));
 }
pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimePresenceAdapter.kt (1)

11-18: Optimize parameter list construction for single parameter case.

The buildList construction could be optimized when only waitForSync parameter is present (the common case).

 override fun get(clientId: String?, connectionId: String?, waitForSync: Boolean): List<PresenceMessage> {
+    if (clientId == null && connectionId == null) {
+        return javaPresence.get(Param(Presence.GET_WAITFORSYNC, waitForSync)).toList()
+    }
     val params = buildList {
       clientId?.let { add(Param(Presence.GET_CLIENTID, it)) }
       connectionId?.let { add(Param(Presence.GET_CONNECTIONID, it)) }
       add(Param(Presence.GET_WAITFORSYNC, waitForSync))
     }
     return javaPresence.get(*params.toTypedArray()).toList()
 }
pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelAdapter.kt (3)

40-45: Optimize array conversion for event names.

The eventNames list is converted to array twice - once for subscribe and once for unsubscribe. Consider storing the array to avoid duplicate conversion.

 override fun subscribe(eventNames: List<String>, listener: ChannelBase.MessageListener): Subscription {
-    javaChannel.subscribe(eventNames.toTypedArray(), listener)
+    val events = eventNames.toTypedArray()
+    javaChannel.subscribe(events, listener)
     return Subscription {
-      javaChannel.unsubscribe(eventNames.toTypedArray(), listener)
+      javaChannel.unsubscribe(events, listener)
     }
 }

52-54: Add error handling for empty message list.

The publish method should validate that the message list is not empty before publishing.

 override fun publish(messages: List<Message>, listener: CompletionListener?) =
+    if (messages.isEmpty()) {
+        listener?.onError(ErrorInfo("Cannot publish empty message list", 40000))
+    } else {
     javaChannel.publish(messages.toTypedArray(), listener)
+    }

55-57: Verify history parameters before querying.

The history method should validate that the start time is not after the end time when both are provided.

 override fun history(start: Long?, end: Long?, limit: Int, orderBy: OrderBy): PaginatedResult<Message> =
+    if (start != null && end != null && start > end) {
+        throw IllegalArgumentException("Start time cannot be after end time")
+    }
     javaChannel.history(buildHistoryParams(start, end, limit, orderBy).toTypedArray())
🛑 Comments failed to post (6)
lib/src/main/java/io/ably/lib/http/AsyncHttpScheduler.java (1)

27-34: 💡 Verification agent

🧩 Analysis chain

Consider restricting access to internal clients.

While the method is documented as internal, its public access could lead to unintended usage. Consider using package-private access or moving it to an internal package.

Let's verify the usage pattern of this method:


🏁 Script executed:

#!/bin/bash
# Search for usages of exchangeHttpCore to ensure it's only used by proxy/wrapper implementations
rg "exchangeHttpCore" --type java

Length of output: 400


Restrict the access level of exchangeHttpCore to prevent unintended external use.

The method is intended for internal use only, yet its public modifier could allow external access. Our search confirms that the only invocations occur within the library—specifically, in lib/src/main/java/io/ably/lib/http/Http.java. Consider changing the modifier from public to package-private (or moving it to an internal package) to align with its documented internal purpose.

pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeClient.kt (1)

5-11: ⚠️ Potential issue

Fix documentation about AutoCloseable.

The documentation mentions that the class implements AutoCloseable, but the interface doesn't actually implement it. Either:

  1. Add AutoCloseable to the interface, or
  2. Remove the AutoCloseable reference from the documentation

If option 1 is chosen, apply this diff:

-interface RealtimeClient : Client {
+interface RealtimeClient : Client, AutoCloseable {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

/**
 * A client that extends the functionality of the {@link Client} and provides additional realtime-specific features.
 *
 * This class implements {@link AutoCloseable} so you can use it in
 * try-with-resources constructs and have the JDK close it for you.
 */
interface RealtimeClient : Client, AutoCloseable {
pubsub-adapter/src/main/kotlin/com/ably/pubsub/WrapperSdkProxy.kt (1)

15-19: ⚠️ Potential issue

Add runtime safety checks for type casting.

The extension functions use unsafe type casts which could fail at runtime if the client doesn't implement SdkWrapperCompatible. Consider adding runtime checks or using safe casts.

Apply this diff to add runtime safety:

 fun RealtimeClient.createWrapperSdkProxy(options: WrapperSdkProxyOptions): RealtimeClient =
-  (this as SdkWrapperCompatible<*>).createWrapperSdkProxy(options) as RealtimeClient
+  when (this) {
+    is SdkWrapperCompatible<*> -> createWrapperSdkProxy(options) as? RealtimeClient
+      ?: throw IllegalStateException("Failed to create RealtimeClient proxy")
+    else -> throw IllegalStateException("Client does not implement SdkWrapperCompatible")
+  }

 fun RestClient.createWrapperSdkProxy(options: WrapperSdkProxyOptions): RestClient =
-  (this as SdkWrapperCompatible<*>).createWrapperSdkProxy(options) as RestClient
+  when (this) {
+    is SdkWrapperCompatible<*> -> createWrapperSdkProxy(options) as? RestClient
+      ?: throw IllegalStateException("Failed to create RestClient proxy")
+    else -> throw IllegalStateException("Client does not implement SdkWrapperCompatible")
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

fun RealtimeClient.createWrapperSdkProxy(options: WrapperSdkProxyOptions): RealtimeClient =
  when (this) {
    is SdkWrapperCompatible<*> -> createWrapperSdkProxy(options) as? RealtimeClient
      ?: throw IllegalStateException("Failed to create RealtimeClient proxy")
    else -> throw IllegalStateException("Client does not implement SdkWrapperCompatible")
  }

fun RestClient.createWrapperSdkProxy(options: WrapperSdkProxyOptions): RestClient =
  when (this) {
    is SdkWrapperCompatible<*> -> createWrapperSdkProxy(options) as? RestClient
      ?: throw IllegalStateException("Failed to create RestClient proxy")
    else -> throw IllegalStateException("Client does not implement SdkWrapperCompatible")
  }
pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt (1)

38-43: 💡 Verification agent

🧩 Analysis chain

Add parameter validation for limit.

The documentation states that a limit greater than 1,000 is invalid, but there's no validation mentioned. Consider adding validation in the implementing classes.


🏁 Script executed:

#!/bin/bash
# Search for existing limit validations in channel implementations
rg -A 5 "limit.*1000|1000.*limit" --type kotlin

Length of output: 4917


Introduce limit parameter validation in Channel implementations.

The interface method for history currently accepts a limit without enforcing the documented maximum of 1000. Similar documentation in Presence and RestPresence indicates that values exceeding 1000 are invalid. Please add validation logic in the concrete implementations of this interface to ensure that the limit parameter does not exceed 1000, either by clamping the value or by raising an appropriate error if an invalid limit is provided.

pubsub-adapter/src/main/kotlin/com/ably/pubsub/Presence.kt (1)

26-31: 💡 Verification agent

❓ Verification inconclusive

Add limit validation in implementing classes.

The documentation states that the maximum limit is 1000, but there's no validation enforcing this constraint. While interfaces typically don't contain implementation details, implementing classes should validate this limit.


🏁 Script executed:

#!/bin/bash
# Search for existing limit validations in implementing classes
rg -A 2 "limit: Int" | grep -B 2 "require|check|assert|throw.*limit"

Length of output: 70


Add validation for the maximum history limit in all implementing classes

The interface in pubsub-adapter/src/main/kotlin/com/ably/pubsub/Presence.kt (lines 26-31) defines the history method with a default limit of 100, and the documentation specifies that the allowed maximum is 1000. However, our searches indicate that none of the implementing classes currently enforce this limit (e.g., via a require(limit <= 1000) check).

  • File: pubsub-adapter/src/main/kotlin/com/ably/pubsub/Presence.kt (interface definition for history)
  • Action: Ensure that each class implementing the history method adds a validation check (for instance, require(limit <= 1000)) to enforce the maximum limit described in the documentation.
lib/src/main/java/io/ably/lib/http/HttpCore.java (1)

324-326: ⚠️ Potential issue

Guard against potential NPE in wrapper SDK agents.

The wrapperSDKAgents field is not initialized in the primary constructor, which could lead to a NullPointerException in collectRequestHeaders. Consider initializing it as an empty map.

-    private Map<String, String> wrapperSDKAgents;
+    private Map<String, String> wrapperSDKAgents = new HashMap<>();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

public class HttpCore {
    // Other field declarations

-    private Map<String, String> wrapperSDKAgents;
+    private Map<String, String> wrapperSDKAgents = new HashMap<>();

    // Constructor(s) and other methods

    private void collectRequestHeaders() {
        Map<String, String> additionalAgents = new HashMap<>(options.agents);
        additionalAgents.putAll(wrapperSDKAgents);
        requestHeaders.put(Defaults.ABLY_AGENT_HEADER, AgentHeaderCreator.create(additionalAgents, platformAgentProvider));
    }

    // Other methods
}

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/realtime/ChannelBase.java (1)

1194-1203: Consider adding documentation for the HTTP parameter.

The implementation correctly uses the provided HTTP context. Consider adding documentation to explain the purpose of the http parameter and its relationship to wrapper SDK proxies.

+    /**
+     * Internal implementation of history retrieval.
+     * @param http The HTTP context to use for the request, allowing for wrapper SDK proxy support
+     * @param params The history request parameters
+     * @return A result request object for retrieving the history
+     */
     private BasePaginatedQuery.ResultRequest<Message> historyImpl(Http http, Param[] params) {
lib/src/main/java/io/ably/lib/http/HttpCore.java (1)

474-481: Align method visibility with its intended usage.

The method is documented as [Internal Method] but is declared as public. Consider making it package-private to match its intended usage and prevent unintended external use.

-    public HttpCore injectWrapperSdkAgents(Map<String, String> wrapperSDKAgents) {
+    HttpCore injectWrapperSdkAgents(Map<String, String> wrapperSDKAgents) {
         return new HttpCore(this, wrapperSDKAgents);
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9dd1d06 and 585e5d5.

📒 Files selected for processing (18)
  • android/src/main/java/io/ably/lib/rest/AblyRest.java (2 hunks)
  • gradle/libs.versions.toml (2 hunks)
  • java/src/main/java/io/ably/lib/rest/AblyRest.java (2 hunks)
  • lib/src/main/java/io/ably/lib/http/AsyncHttpScheduler.java (1 hunks)
  • lib/src/main/java/io/ably/lib/http/Http.java (1 hunks)
  • lib/src/main/java/io/ably/lib/http/HttpCore.java (4 hunks)
  • lib/src/main/java/io/ably/lib/http/HttpScheduler.java (1 hunks)
  • lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java (3 hunks)
  • lib/src/main/java/io/ably/lib/realtime/ChannelBase.java (3 hunks)
  • lib/src/main/java/io/ably/lib/realtime/Presence.java (4 hunks)
  • lib/src/main/java/io/ably/lib/rest/AblyBase.java (1 hunks)
  • lib/src/main/java/io/ably/lib/rest/ChannelBase.java (10 hunks)
  • lib/src/main/java/io/ably/lib/util/AgentHeaderCreator.java (1 hunks)
  • pubsub-adapter/src/main/kotlin/com/ably/pubsub/WrapperSdkProxy.kt (1 hunks)
  • pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeClientAdapter.kt (3 hunks)
  • pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/WrapperRealtimeClient.kt (1 hunks)
  • pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt (3 hunks)
  • pubsub-adapter/src/main/kotlin/io/ably/lib/rest/WrapperRestClient.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
  • lib/src/main/java/io/ably/lib/util/AgentHeaderCreator.java
  • lib/src/main/java/io/ably/lib/http/AsyncHttpScheduler.java
  • lib/src/main/java/io/ably/lib/http/Http.java
  • java/src/main/java/io/ably/lib/rest/AblyRest.java
  • pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt
  • android/src/main/java/io/ably/lib/rest/AblyRest.java
  • lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java
  • gradle/libs.versions.toml
  • pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeClientAdapter.kt
  • lib/src/main/java/io/ably/lib/realtime/Presence.java
  • pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/WrapperRealtimeClient.kt
  • pubsub-adapter/src/main/kotlin/com/ably/pubsub/WrapperSdkProxy.kt
  • lib/src/main/java/io/ably/lib/http/HttpScheduler.java
  • lib/src/main/java/io/ably/lib/rest/AblyBase.java
🧰 Additional context used
🪛 GitHub Actions: JavaDoc
pubsub-adapter/src/main/kotlin/io/ably/lib/rest/WrapperRestClient.kt

[error] 32-32: 'publish' overrides nothing.


[error] 36-36: 'publishAsync' overrides nothing.

🪛 GitHub Actions: Integration Test
lib/src/main/java/io/ably/lib/http/HttpCore.java

[error] 324-324: NullPointerException in collectRequestHeaders method: Cannot invoke "java.util.Map.size()" because "m" is null.

🔇 Additional comments (12)
lib/src/main/java/io/ably/lib/rest/ChannelBase.java (4)

50-140: LGTM! Well-structured implementation of HTTP context support for publish methods.

The changes maintain existing functionality while cleanly introducing HTTP context support. The implementation preserves important features like message encoding, client ID validation, and idempotent publishing.


151-177: LGTM! Clean implementation of HTTP context support for history methods.

The changes follow the same pattern as publish methods, maintaining existing functionality while adding HTTP context support. The implementation preserves important features like request ID generation and message body handling.


200-237: LGTM! Consistent implementation of HTTP context support for presence get methods.

The changes maintain the same pattern as other methods, preserving existing functionality while adding HTTP context support. The implementation maintains important features like request ID generation and presence message handling.


260-303: LGTM! Well-implemented HTTP context support for presence history methods.

The changes follow the consistent pattern seen throughout the file, maintaining existing functionality while adding HTTP context support. The implementation preserves important features like request ID generation and presence message handling.

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

13-13: LGTM!

The import for Http is correctly added and properly ordered.


1153-1159: LGTM! Well-structured implementation.

The changes maintain backward compatibility while adding support for custom HTTP contexts. The delegation pattern is implemented correctly.


1186-1192: LGTM! Consistent implementation.

The async implementation follows the same pattern as the synchronous version, maintaining backward compatibility while supporting custom HTTP contexts.

pubsub-adapter/src/main/kotlin/io/ably/lib/rest/WrapperRestClient.kt (5)

15-18: LGTM! Well-structured proxy implementation.

The WrapperRestClient class effectively implements the proxy pattern using Kotlin's delegation, while properly injecting the HTTP module for agent tracking. This aligns well with the PR's objective of distinguishing wrapper SDK requests.


20-25: LGTM! Clean channel wrapper implementation.

The WrapperRestChannels class correctly maintains the proxy chain by wrapping returned channels, ensuring consistent HTTP module propagation.


53-76: LGTM! Comprehensive presence wrapper implementation.

The WrapperRestPresence class properly handles all presence-related operations while maintaining consistent HTTP module usage for agent tracking.


1-14: LGTM! Well-organized imports and file structure.

The file is well-structured with proper package declaration and organized imports. The implementation effectively supports the PR's goal of distinguishing wrapper SDK requests through consistent HTTP module injection.


27-51: ⚠️ Potential issue

Fix method override declarations.

The JavaDoc pipeline is reporting that publish and publishAsync methods are not properly overriding their interface methods. Add the override modifier to these methods:

-  fun publish(name: String, data: Any) = javaChannel.publish(httpModule, name, data)
+  override fun publish(name: String, data: Any) = javaChannel.publish(httpModule, name, data)

-  fun publish(messages: List<Message>) = javaChannel.publish(httpModule, messages.toTypedArray())
+  override fun publish(messages: List<Message>) = javaChannel.publish(httpModule, messages.toTypedArray())

-  fun publishAsync(name: String, data: Any, listener: CompletionListener) = javaChannel.publishAsync(httpModule, name, data, listener)
+  override fun publishAsync(name: String, data: Any, listener: CompletionListener) = javaChannel.publishAsync(httpModule, name, data, listener)

-  fun publishAsync(messages: List<Message>, listener: CompletionListener) = javaChannel.publishAsync(httpModule, messages.toTypedArray(), listener)
+  override fun publishAsync(messages: List<Message>, listener: CompletionListener) = javaChannel.publishAsync(httpModule, messages.toTypedArray(), listener)

Likely an incorrect or invalid review comment.

🧰 Tools
🪛 GitHub Actions: JavaDoc

[error] 32-32: 'publish' overrides nothing.


[error] 36-36: 'publishAsync' overrides nothing.

lib/src/main/java/io/ably/lib/http/HttpCore.java Outdated Show resolved Hide resolved
lib/src/main/java/io/ably/lib/http/HttpCore.java Outdated Show resolved Hide resolved
lib/src/main/java/io/ably/lib/http/HttpCore.java Outdated Show resolved Hide resolved
Special `SdkWrapperCompatible` introduced, allowing us to hide `createWrapperSdkProxy` from the public interface and use extension function instead
Add tests to verify agent propagation in the WrapperSdkProxy clients
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

♻️ Duplicate comments (2)
lib/src/main/java/io/ably/lib/http/HttpCore.java (2)

116-126: ⚠️ Potential issue

Add parameter validation and defensive copying in the constructor.

The constructor should validate input parameters and create a defensive copy of the mutable map to prevent potential issues:

  1. Null parameters could lead to NPE
  2. Direct reference to the mutable map could lead to unexpected modifications

Apply this diff to fix the issues:

     private HttpCore(HttpCore underlyingHttpCore, Map<String, String> dynamicAgents) {
+        if (underlyingHttpCore == null) {
+            throw new IllegalArgumentException("underlyingHttpCore cannot be null");
+        }
+        if (dynamicAgents == null) {
+            throw new IllegalArgumentException("dynamicAgents cannot be null");
+        }
         this.options = underlyingHttpCore.options;
         this.auth = underlyingHttpCore.auth;
         this.platformAgentProvider = underlyingHttpCore.platformAgentProvider;
         this.scheme = underlyingHttpCore.scheme;
         this.port = underlyingHttpCore.port;
         this.hosts = underlyingHttpCore.hosts;
         this.proxyAuth = underlyingHttpCore.proxyAuth;
         this.engine = underlyingHttpCore.engine;
-        this.dynamicAgents = dynamicAgents;
+        this.dynamicAgents = new HashMap<>(dynamicAgents);
     }

332-335: ⚠️ Potential issue

Add null checks to prevent NullPointerException in header collection.

The code assumes both options.agents and dynamicAgents are non-null, which can lead to NPE.

Apply this diff to fix the issues:

-        Map<String, String> additionalAgents = new HashMap<>();
-        if (options.agents != null) additionalAgents.putAll(options.agents);
-        if (dynamicAgents != null) additionalAgents.putAll(dynamicAgents);
+        Map<String, String> additionalAgents = new HashMap<>();
+        if (options.agents != null) {
+            additionalAgents.putAll(options.agents);
+        }
+        if (dynamicAgents != null) {
+            additionalAgents.putAll(dynamicAgents);
+        }
         requestHeaders.put(Defaults.ABLY_AGENT_HEADER, AgentHeaderCreator.create(additionalAgents, platformAgentProvider));
🧹 Nitpick comments (5)
pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/WrapperRealtimeClient.kt (1)

77-87: Consider using copy() for more maintainable property copying.

The current implementation manually copies each property, which could become error-prone if new properties are added to ChannelOptions in the future.

Consider using Kotlin's data class copy mechanism if possible:

-private fun ChannelOptions.injectAgents(agents: Map<String, String>): ChannelOptions {
-    val options = ChannelOptions()
-    options.params = (this.params ?: mapOf()) + mapOf(
-        "agent" to agents.map { "${it.key}/${it.value}" }.joinToString(" "),
-    )
-    options.modes = modes
-    options.cipherParams = cipherParams
-    options.attachOnSubscribe = attachOnSubscribe
-    options.encrypted = encrypted
-    return options
-}
+private fun ChannelOptions.injectAgents(agents: Map<String, String>): ChannelOptions =
+    copy(
+        params = (params ?: mapOf()) + mapOf(
+            "agent" to agents.map { "${it.key}/${it.value}" }.joinToString(" ")
+        )
+    )

If ChannelOptions is not a data class, consider making it one to leverage this pattern.

pubsub-adapter/src/test/kotlin/com/ably/Utils.kt (1)

8-17: Add documentation and improve error handling.

The waitFor function needs documentation to explain its behavior, and could benefit from improved error handling and flexibility.

Consider these improvements:

+/**
+ * Polls a condition until it returns true or timeout is reached.
+ * @param timeoutInMs Maximum time to wait in milliseconds
+ * @param block Condition to check. Should return true when the wait condition is met.
+ * @throws TimeoutCancellationException if the condition is not met within the timeout
+ * @throws Exception if the block throws an exception
+ */
 suspend fun waitFor(timeoutInMs: Long = 10_000, block: suspend () -> Boolean) {
   withContext(Dispatchers.Default) {
     withTimeout(timeoutInMs) {
+      var lastError: Throwable? = null
       do {
-        val success = block()
+        val success = try {
+          block()
+        } catch (e: Exception) {
+          lastError = e
+          false
+        }
+        if (!success && lastError != null) {
+          throw lastError
+        }
         delay(100)
       } while (!success)
     }
   }
 }

Additionally, consider making the delay configurable to support different polling scenarios:

-suspend fun waitFor(timeoutInMs: Long = 10_000, block: suspend () -> Boolean)
+suspend fun waitFor(
+  timeoutInMs: Long = 10_000,
+  delayMs: Long = 100,
+  block: suspend () -> Boolean
+)
pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt (1)

67-72:

❓ Verification inconclusive

Add documentation for createWrapperSdkProxy method.

The method would benefit from documentation explaining its purpose and behavior.

Add KDoc:

+  /**
+   * Creates a proxy instance of the REST client that injects additional agent information
+   * into HTTP requests. This is useful for tracking requests made through wrapper SDKs.
+   *
+   * @param options Configuration options containing agent information to inject
+   * @return A new RestClient instance with the specified agent information
+   */
   override fun createWrapperSdkProxy(options: WrapperSdkProxyOptions): RestClient {
     val httpCoreWithAgents = javaClient.httpCore.injectDynamicAgents(options.agents)
     val httpModule = javaClient.http.exchangeHttpCore(httpCoreWithAgents)
     val javaClientWithInjectedAgents = javaClient.createShallowCopy(httpCoreWithAgents, httpModule)
     return WrapperRestClient(javaClientWithInjectedAgents, httpModule)
   }

Let's verify the existence of the required extension functions:


🏁 Script executed:

#!/bin/bash
# Search for the required extension function declarations
echo "Searching for extension functions..."
rg -t kotlin "fun.*HttpCore\.injectDynamicAgents" -A 2
rg -t kotlin "fun.*Http\.exchangeHttpCore" -A 2
rg -t kotlin "fun.*AblyRest\.createShallowCopy" -A 2

Length of output: 244


Action Required: Verify Extension Function Existence & Add Documentation

It appears that the earlier search did not produce any output. Please manually verify that the following extension functions:

  • HttpCore.injectDynamicAgents
  • Http.exchangeHttpCore
  • AblyRest.createShallowCopy

actually exist in the codebase. Once confirmed, update the createWrapperSdkProxy method in pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt with the following KDoc to improve clarity:

+  /**
+   * Creates a proxy instance of the REST client that injects additional agent information
+   * into HTTP requests. This proxy is particularly useful for enhancing request tracking
+   * when operating within a wrapper SDK environment.
+   *
+   * @param options Configuration options containing agent information to inject.
+   * @return A new RestClient instance with the specified agent information.
+   */

After verifying these extension functions and applying the documentation, please ensure the changes align with the intended design.


🏁 Script executed:

#!/bin/bash
echo "Re-verifying the extension functions..."

echo "Searching for 'injectDynamicAgents' extension function:"
rg -t kotlin "fun .*injectDynamicAgents" -N

echo "Searching for 'exchangeHttpCore' extension function:"
rg -t kotlin "fun .*exchangeHttpCore" -N

echo "Searching for 'createShallowCopy' extension function:"
rg -t kotlin "fun .*createShallowCopy" -N

Length of output: 592


Action Required: Verify Extension Functions & Update Documentation

The proposed KDoc update for the createWrapperSdkProxy method is a great improvement for clarity. Please add the following KDoc above the method in pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt:

  /**
   * Creates a proxy instance of the REST client that injects additional agent information
   * into HTTP requests. This proxy is particularly useful for enhancing request tracking
   * when operating within a wrapper SDK environment.
   *
   * @param options Configuration options containing agent information to inject.
   * @return A new RestClient instance with the specified agent information.
   */

However, the automated searches for the extension functions (injectDynamicAgents, exchangeHttpCore, and createShallowCopy) did not return any results. This could mean one of two things:

  • The extension functions might be declared using a different pattern or file structure.
  • They might require manual verification.

Next Steps:

  • Manually verify that the extension functions are defined in the codebase. These functions are critical since they are used in the proxy creation process.
  • If any of these functions are missing or misnamed, please update the references accordingly.
pubsub-adapter/src/test/kotlin/com/ably/pubsub/SdkWrapperAgentChannelParamTest.kt (1)

84-89: Consider using test configuration for credentials.

The hardcoded key "xxxxx:yyyyyyy" should be moved to test configuration.

-  val options = ClientOptions("xxxxx:yyyyyyy").apply {
+  val options = ClientOptions(TestConfig.VALID_API_KEY).apply {
pubsub-adapter/src/test/kotlin/com/ably/pubsub/SdkWrapperAgentHeaderTest.kt (1)

177-201: Consider improving test configuration.

  1. Move hardcoded credentials to test configuration
  2. Consider making the port configurable or using a random available port
-  val options = ClientOptions("xxxxx:yyyyyyy").apply {
+  val options = ClientOptions(TestConfig.VALID_API_KEY).apply {
-    port = PORT
+    port = TestConfig.getAvailablePort()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 585e5d5 and 122a729.

📒 Files selected for processing (13)
  • gradle/libs.versions.toml (2 hunks)
  • lib/src/main/java/io/ably/lib/http/HttpCore.java (4 hunks)
  • pubsub-adapter/build.gradle.kts (1 hunks)
  • pubsub-adapter/src/main/kotlin/com/ably/pubsub/WrapperSdkProxy.kt (1 hunks)
  • pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeClientAdapter.kt (3 hunks)
  • pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/WrapperRealtimeClient.kt (1 hunks)
  • pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt (3 hunks)
  • pubsub-adapter/src/main/kotlin/io/ably/lib/rest/WrapperRestClient.kt (1 hunks)
  • pubsub-adapter/src/test/kotlin/com/ably/EmbeddedServer.kt (1 hunks)
  • pubsub-adapter/src/test/kotlin/com/ably/Utils.kt (1 hunks)
  • pubsub-adapter/src/test/kotlin/com/ably/pubsub/SdkWrapperAgentChannelParamTest.kt (1 hunks)
  • pubsub-adapter/src/test/kotlin/com/ably/pubsub/SdkWrapperAgentHeaderTest.kt (1 hunks)
  • pubsub-adapter/src/test/kotlin/io/ably/lib/realtime/ChannelUtils.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • gradle/libs.versions.toml
  • pubsub-adapter/src/main/kotlin/com/ably/pubsub/WrapperSdkProxy.kt
🧰 Additional context used
🪛 detekt (1.23.7)
pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/WrapperRealtimeClient.kt

[warning] 32-32: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check (29)
  • GitHub Check: check (24)
  • GitHub Check: check-rest
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check (21)
  • GitHub Check: check
  • GitHub Check: check-realtime
  • GitHub Check: check (19)
🔇 Additional comments (18)
pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/WrapperRealtimeClient.kt (3)

12-19: LGTM! Well-structured implementation using Kotlin delegation.

The class effectively wraps the Java client while maintaining the ability to inject custom agent parameters, which aligns with the PR's objective.


42-59: LGTM! Clean implementation of channel wrapper.

The class effectively wraps the channel operations while properly integrating the custom HTTP module for history queries.


61-75: LGTM! Clean implementation of presence wrapper.

The class effectively wraps the presence operations while properly integrating the custom HTTP module for history queries.

pubsub-adapter/src/test/kotlin/io/ably/lib/realtime/ChannelUtils.kt (1)

5-6: LGTM! Well-designed extension property.

The extension property provides a clean and type-safe way to access channel options from the ChannelBase class.

pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeClientAdapter.kt (2)

18-18: LGTM! Clean interface implementation.

The addition of SdkWrapperCompatible<RealtimeClient> interface aligns with the PR objective of distinguishing wrapper SDK requests.


70-75: LGTM! Well-structured proxy creation.

The implementation follows a clean pattern:

  1. Injects agents into httpCore
  2. Exchanges httpCore in http module
  3. Creates a shallow copy with modified components
pubsub-adapter/src/main/kotlin/io/ably/lib/rest/WrapperRestClient.kt (3)

15-18: LGTM! Elegant use of delegation pattern.

The implementation cleanly delegates to RestClientAdapter while overriding channels to use the wrapper version.


27-51: LGTM! Consistent HTTP module injection.

The implementation correctly injects the HTTP module into all channel operations, maintaining consistency across the API surface.


53-76: LGTM! Well-structured presence wrapper.

The presence wrapper maintains consistency with the channel wrapper pattern and correctly handles HTTP module injection.

pubsub-adapter/src/test/kotlin/com/ably/pubsub/SdkWrapperAgentChannelParamTest.kt (2)

18-39: LGTM! Comprehensive test coverage for basic channel creation.

The test properly verifies the presence and absence of agent information in the appropriate scenarios.


41-81: LGTM! Thorough verification of custom options scenario.

The test ensures that agent information coexists correctly with custom channel options.

pubsub-adapter/src/test/kotlin/com/ably/pubsub/SdkWrapperAgentHeaderTest.kt (2)

23-53: LGTM! Thorough verification of agent headers.

The test properly verifies the presence of agent information in headers for different types of requests.


151-174: LGTM! Clean server setup and teardown.

The embedded server setup provides good isolation for testing HTTP requests.

lib/src/main/java/io/ably/lib/http/HttpCore.java (2)

71-79: LGTM!

The field is well-documented with clear purpose and follows Java field declaration conventions.


483-490: LGTM!

The method is well-documented and has a clear, focused implementation.

pubsub-adapter/build.gradle.kts (3)

9-15: LGTM!

The test dependencies are well-structured and align with the PR objectives for testing wrapper SDK functionality:

  • Kotlin test for unit testing
  • NanoHTTPD for embedded server testing
  • Coroutines for async testing
  • Turbine for Flow testing

17-19: LGTM!

Standard JUnit platform configuration is correctly applied.


21-23: LGTM!

The custom test task is well-defined with useful logging functionality.

Base automatically changed from create-kotlin-wrapper to main February 17, 2025 13:10
@sacOO7
Copy link
Collaborator

sacOO7 commented Feb 18, 2025

Had a quick review of the PR, seems I also need to have spec PR https://github.com/ably/specification/pull/274/files as a reference. I will do more deeper review once I understand the context properly.

@lawrence-forooghian
Copy link
Collaborator

@sacOO7 the spec PR that you linked to is for the chat spec; the relevant one here is ably/specification#273 🙂

Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian left a comment

Choose a reason for hiding this comment

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

Took a first look at it and, whilst not understanding all of the details, I think it makes sense roughly. But have asked a question that should help my understanding 🙂

private val javaClient: AblyRealtime,
private val httpModule: Http,
private val agents: Map<String, String>,
) : RealtimeClient by RealtimeClientAdapter(javaClient) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does the request method need to be overridden here to inject the wrapper SDK agents, or is there some other mechanism that handles this that I haven't understood?

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