Kotlin / kotlinx-rpc

Add asynchronous RPC services to your multiplatform applications.
https://kotlin.github.io/kotlinx-rpc/
Apache License 2.0
744 stars 17 forks source link

Recompile and publish with Ktor 3.0.0 to avoid a bug #215

Closed lsafer-meemer closed 1 week ago

lsafer-meemer commented 3 weeks ago

Describe the bug Ktor 3.0.0 introduce a binary incompatible change:

https://github.com/ktorio/ktor/compare/3.0.0-rc-2...main#diff-45f4dbe0240789d9647138f3e4ae66e95565a2bb987f4e5b70314c9371bf6a46L27

@JvmSynthetic
public inline fun <reified T : Any> AttributeKey(name: String): AttributeKey<T> =
//    AttributeKey(name, typeOf<T>()) <-- this removed
    AttributeKey(name, typeInfo<T>()) // <-- this added

public data class AttributeKey<T : Any> @JvmOverloads constructor(
    public val name: String,
//    private val type: KType = typeOf<Any>(), <-- this removed
    private val type: TypeInfo = typeInfo<Any>(), // <-- this added
) {

To Reproduce Steps to reproduce the behavior:

  1. Kotlin version
  2. Gradle version
  3. OS (Or at least KMP platform)
  4. Minimal reproducer in code
  5. Error description
  6. And so on

Expected behavior

Additional context

Just recompile and publish a new version

lsafer-meemer commented 3 weeks ago

I found a workaround:

add these files anywhere in src of server:

// kotlinx.rpc.krpc.ktor.server.RPC.kt

@file:Suppress("PackageDirectoryMismatch")

package kotlinx.rpc.krpc.ktor.server

import io.ktor.server.application.*
import io.ktor.server.websocket.*
import io.ktor.util.*
import kotlinx.rpc.krpc.RPCConfigBuilder

internal val RPCServerPluginAttributesKey = AttributeKey<RPCConfigBuilder.Server>("RPCServerPluginAttributesKey")

/**
 * Ktor server plugin that allows to configure RPC globally for all mounted servers.
 */
public val RPC: ApplicationPlugin<RPCConfigBuilder.Server> = createApplicationPlugin(
    name = "RPC",
    createConfiguration = { RPCConfigBuilder.Server() },
) {
    application.install(WebSockets)
    application.attributes.put(RPCServerPluginAttributesKey, pluginConfig)
}

add these files anywhere in src of client:

// kotlinx.rpc.krpc.ktor.client.KtorClientDsl.kt

@file:Suppress("PackageDirectoryMismatch")

package kotlinx.rpc.krpc.ktor.client

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.*
import io.ktor.util.*
import kotlinx.rpc.RPCClient
import kotlinx.rpc.krpc.RPCConfigBuilder
import kotlinx.rpc.krpc.rpcClientConfig

private val RPCRequestConfigAttributeKey = AttributeKey<RPCConfigBuilder.Client.() -> Unit>(
    name = "RPCRequestConfigAttributeKey"
)

/**
 * Extension function for the [HttpRequestBuilder] that allows to configure RPC for the call.
 * Usually used with the [rpc] functions.
 * Overrides [RPC] plugin configuration.
 *
 * @param configBuilder The function that configures RPC.
 */
public fun HttpRequestBuilder.rpcConfig(configBuilder: RPCConfigBuilder.Client.() -> Unit = {}) {
    attributes.put(RPCRequestConfigAttributeKey, configBuilder)
}

/**
 * Configures [RPCClient] for the following path. Provides means for additional configuration via [block].
 * Note that the [WebSockets] plugin is required for these calls.
 *
 * @param urlString The URL to use for the request.
 * @param block Optional configuration for the
 * @return An instance of [RPCClient] that is configured to send messages to the server.
 */
public suspend fun HttpClient.rpc(
    urlString: String,
    block: HttpRequestBuilder.() -> Unit = {},
): KtorRPCClient {
    return rpc {
        url(urlString)
        block()
    }
}

/**
 * Configures [RPCClient] for the following path. Provides means for additional configuration via [block].
 * Note that the [WebSockets] plugin is required for these calls.
 *
 * @param block Optional configuration for the
 * @return An instance of [RPCClient] that is configured to send messages to the server.
 */
public suspend fun HttpClient.rpc(
    block: HttpRequestBuilder.() -> Unit = {},
): KtorRPCClient {
    pluginOrNull(WebSockets)
        ?: error("RPC for client requires $WebSockets plugin to be installed firstly")

    var requestConfigBuilder: RPCConfigBuilder.Client.() -> Unit = {}
    val session = webSocketSession {
        block()

        attributes.getOrNull(RPCRequestConfigAttributeKey)?.let {
            requestConfigBuilder = it
        }
    }

    val pluginConfigBuilder = attributes.getOrNull(RPCClientPluginAttributesKey)
    val rpcConfig = pluginConfigBuilder?.apply(requestConfigBuilder)?.build()
        ?: rpcClientConfig(requestConfigBuilder)

    return KtorRPCClientImpl(session, rpcConfig)
}
// kotlinx.rpc.krpc.ktor.client.KtorRPCClient.kt

@file:Suppress("PackageDirectoryMismatch")

package kotlinx.rpc.krpc.ktor.client

import io.ktor.websocket.*
import kotlinx.rpc.RPCClient
import kotlinx.rpc.internal.utils.InternalRPCApi
import kotlinx.rpc.krpc.RPCConfig
import kotlinx.rpc.krpc.client.KRPCClient
import kotlinx.rpc.krpc.ktor.KtorTransport

/**
 * [RPCClient] implementation for Ktor, containing [webSocketSession] object,
 * that is used to maintain connection.
 */
public interface KtorRPCClient : RPCClient {
    public val webSocketSession: WebSocketSession
}

@OptIn(InternalRPCApi::class)
internal class KtorRPCClientImpl(
    override val webSocketSession: WebSocketSession,
    config: RPCConfig.Client,
) : KRPCClient(config, KtorTransport(webSocketSession)), KtorRPCClient
// kotlinx.rpc.krpc.ktor.client.RPC.kt

@file:Suppress("PackageDirectoryMismatch")

package kotlinx.rpc.krpc.ktor.client

import io.ktor.client.*
import io.ktor.client.plugins.api.*
import io.ktor.client.plugins.websocket.*
import io.ktor.util.*
import kotlinx.rpc.krpc.RPCConfigBuilder

internal val RPCClientPluginAttributesKey = AttributeKey<RPCConfigBuilder.Client>("RPCClientPluginAttributesKey")

/**
 * Ktor client plugin that allows to configure RPC globally for all instances obtained via [rpc] functions.
 */
public val RPC: ClientPlugin<RPCConfigBuilder.Client> = createClientPlugin("RPC", { RPCConfigBuilder.Client() }) {
    client.attributes.put(RPCClientPluginAttributesKey, pluginConfig)
}

/**
 * Installs [WebSockets] and [RPC] client plugins
 */
public fun HttpClientConfig<*>.installRPC(
    configure: RPCConfigBuilder.Client.() -> Unit = {}
) {
    install(WebSockets)
    install(RPC, configure)
}
rasharab commented 3 weeks ago

Just hit this today

lsaferhuman commented 3 weeks ago

This is me @lsafer-meemer from my phone.

forgot to add @file:JvmName in the workaround files. (essential for the workaround to work)

lsafer-meemer commented 2 weeks ago

@Mr3zee any updates on this?

Mr3zee commented 2 weeks ago

Oh, hey! Thank you for the heads up, I will provide an update this week!

Mr3zee commented 1 week ago

https://github.com/Kotlin/kotlinx-rpc/releases/tag/0.4.0 contains the update