diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt index b266304f3..c24effdd5 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt @@ -172,6 +172,7 @@ internal open class DefaultGrpcExtension @Inject constructor( protoSourceSet.generateTask.set(bufGenerateTask) + // todo fix for common source sets tasks.withType().configureEach { // compileKotlin - main // compileTestKotlin - test @@ -276,7 +277,6 @@ internal open class DefaultGrpcExtension @Inject constructor( } options.put("debugOutput", "protoc-gen-kotlin-multiplatform.log") - options.put("messageMode", "interface") options.put("explicitApiModeEnabled", project.provider { project.the().explicitApi != ExplicitApiMode.Disabled }) diff --git a/grpc/grpc-core/api/grpc-core.api b/grpc/grpc-core/api/grpc-core.api index facf41e2f..94729739d 100644 --- a/grpc/grpc-core/api/grpc-core.api +++ b/grpc/grpc-core/api/grpc-core.api @@ -62,45 +62,35 @@ public final class kotlinx/rpc/grpc/Server$DefaultImpls { public static synthetic fun awaitTermination-VtjQ1oo$default (Lkotlinx/rpc/grpc/Server;JLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } -public abstract interface class kotlinx/rpc/grpc/Status { - public abstract fun getCause ()Ljava/lang/Throwable; - public abstract fun getCode ()Lkotlinx/rpc/grpc/Status$Code; - public abstract fun getDescription ()Ljava/lang/String; -} - -public final class kotlinx/rpc/grpc/Status$Code : java/lang/Enum { - public static final field ABORTED Lkotlinx/rpc/grpc/Status$Code; - public static final field ALREADY_EXISTS Lkotlinx/rpc/grpc/Status$Code; - public static final field CANCELLED Lkotlinx/rpc/grpc/Status$Code; - public static final field DATA_LOSS Lkotlinx/rpc/grpc/Status$Code; - public static final field DEADLINE_EXCEEDED Lkotlinx/rpc/grpc/Status$Code; - public static final field FAILED_PRECONDITION Lkotlinx/rpc/grpc/Status$Code; - public static final field INTERNAL Lkotlinx/rpc/grpc/Status$Code; - public static final field INVALID_ARGUMENT Lkotlinx/rpc/grpc/Status$Code; - public static final field NOT_FOUND Lkotlinx/rpc/grpc/Status$Code; - public static final field OK Lkotlinx/rpc/grpc/Status$Code; - public static final field OUT_OF_RANGE Lkotlinx/rpc/grpc/Status$Code; - public static final field PERMISSION_DENIED Lkotlinx/rpc/grpc/Status$Code; - public static final field RESOURCE_EXHAUSTED Lkotlinx/rpc/grpc/Status$Code; - public static final field UNAUTHENTICATED Lkotlinx/rpc/grpc/Status$Code; - public static final field UNAVAILABLE Lkotlinx/rpc/grpc/Status$Code; - public static final field UNIMPLEMENTED Lkotlinx/rpc/grpc/Status$Code; - public static final field UNKNOWN Lkotlinx/rpc/grpc/Status$Code; +public final class kotlinx/rpc/grpc/StatusCode : java/lang/Enum { + public static final field ABORTED Lkotlinx/rpc/grpc/StatusCode; + public static final field ALREADY_EXISTS Lkotlinx/rpc/grpc/StatusCode; + public static final field CANCELLED Lkotlinx/rpc/grpc/StatusCode; + public static final field DATA_LOSS Lkotlinx/rpc/grpc/StatusCode; + public static final field DEADLINE_EXCEEDED Lkotlinx/rpc/grpc/StatusCode; + public static final field FAILED_PRECONDITION Lkotlinx/rpc/grpc/StatusCode; + public static final field INTERNAL Lkotlinx/rpc/grpc/StatusCode; + public static final field INVALID_ARGUMENT Lkotlinx/rpc/grpc/StatusCode; + public static final field NOT_FOUND Lkotlinx/rpc/grpc/StatusCode; + public static final field OK Lkotlinx/rpc/grpc/StatusCode; + public static final field OUT_OF_RANGE Lkotlinx/rpc/grpc/StatusCode; + public static final field PERMISSION_DENIED Lkotlinx/rpc/grpc/StatusCode; + public static final field RESOURCE_EXHAUSTED Lkotlinx/rpc/grpc/StatusCode; + public static final field UNAUTHENTICATED Lkotlinx/rpc/grpc/StatusCode; + public static final field UNAVAILABLE Lkotlinx/rpc/grpc/StatusCode; + public static final field UNIMPLEMENTED Lkotlinx/rpc/grpc/StatusCode; + public static final field UNKNOWN Lkotlinx/rpc/grpc/StatusCode; public static fun getEntries ()Lkotlin/enums/EnumEntries; public final fun getValue ()I public final fun getValueAscii ()[B - public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/grpc/Status$Code; - public static fun values ()[Lkotlinx/rpc/grpc/Status$Code; -} - -public abstract interface class kotlinx/rpc/grpc/StatusRuntimeException { - public abstract fun getStatus ()Lkotlinx/rpc/grpc/Status; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/grpc/StatusCode; + public static fun values ()[Lkotlinx/rpc/grpc/StatusCode; } -public final class kotlinx/rpc/grpc/StatusRuntimeException_jvmKt { - public static final fun StatusRuntimeException (Lkotlinx/rpc/grpc/Status;)Lkotlinx/rpc/grpc/StatusRuntimeException; - public static final fun toJvm (Lkotlinx/rpc/grpc/StatusRuntimeException;)Lio/grpc/StatusRuntimeException; - public static final fun toKotlin (Lio/grpc/StatusRuntimeException;)Lkotlinx/rpc/grpc/StatusRuntimeException; +public final class kotlinx/rpc/grpc/Status_jvmKt { + public static final fun Status (Lkotlinx/rpc/grpc/StatusCode;Ljava/lang/String;Ljava/lang/Throwable;)Lio/grpc/Status; + public static synthetic fun Status$default (Lkotlinx/rpc/grpc/StatusCode;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)Lio/grpc/Status; + public static final fun getCode (Lio/grpc/Status;)Lkotlinx/rpc/grpc/StatusCode; } public abstract interface class kotlinx/rpc/grpc/descriptor/GrpcClientDelegate { diff --git a/grpc/grpc-core/api/grpc-core.klib.api b/grpc/grpc-core/api/grpc-core.klib.api index fb826cf3c..c923fb7a7 100644 --- a/grpc/grpc-core/api/grpc-core.klib.api +++ b/grpc/grpc-core/api/grpc-core.klib.api @@ -1,11 +1,41 @@ // Klib ABI Dump -// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] +// Targets: [macosArm64] // Rendering settings: // - Signature version: 2 // - Show manifest properties: true // - Show declarations: true // Library unique name: +final enum class kotlinx.rpc.grpc/StatusCode : kotlin/Enum { // kotlinx.rpc.grpc/StatusCode|null[0] + enum entry ABORTED // kotlinx.rpc.grpc/StatusCode.ABORTED|null[0] + enum entry ALREADY_EXISTS // kotlinx.rpc.grpc/StatusCode.ALREADY_EXISTS|null[0] + enum entry CANCELLED // kotlinx.rpc.grpc/StatusCode.CANCELLED|null[0] + enum entry DATA_LOSS // kotlinx.rpc.grpc/StatusCode.DATA_LOSS|null[0] + enum entry DEADLINE_EXCEEDED // kotlinx.rpc.grpc/StatusCode.DEADLINE_EXCEEDED|null[0] + enum entry FAILED_PRECONDITION // kotlinx.rpc.grpc/StatusCode.FAILED_PRECONDITION|null[0] + enum entry INTERNAL // kotlinx.rpc.grpc/StatusCode.INTERNAL|null[0] + enum entry INVALID_ARGUMENT // kotlinx.rpc.grpc/StatusCode.INVALID_ARGUMENT|null[0] + enum entry NOT_FOUND // kotlinx.rpc.grpc/StatusCode.NOT_FOUND|null[0] + enum entry OK // kotlinx.rpc.grpc/StatusCode.OK|null[0] + enum entry OUT_OF_RANGE // kotlinx.rpc.grpc/StatusCode.OUT_OF_RANGE|null[0] + enum entry PERMISSION_DENIED // kotlinx.rpc.grpc/StatusCode.PERMISSION_DENIED|null[0] + enum entry RESOURCE_EXHAUSTED // kotlinx.rpc.grpc/StatusCode.RESOURCE_EXHAUSTED|null[0] + enum entry UNAUTHENTICATED // kotlinx.rpc.grpc/StatusCode.UNAUTHENTICATED|null[0] + enum entry UNAVAILABLE // kotlinx.rpc.grpc/StatusCode.UNAVAILABLE|null[0] + enum entry UNIMPLEMENTED // kotlinx.rpc.grpc/StatusCode.UNIMPLEMENTED|null[0] + enum entry UNKNOWN // kotlinx.rpc.grpc/StatusCode.UNKNOWN|null[0] + + final val entries // kotlinx.rpc.grpc/StatusCode.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // kotlinx.rpc.grpc/StatusCode.entries.|#static(){}[0] + final val value // kotlinx.rpc.grpc/StatusCode.value|{}value[0] + final fun (): kotlin/Int // kotlinx.rpc.grpc/StatusCode.value.|(){}[0] + final val valueAscii // kotlinx.rpc.grpc/StatusCode.valueAscii|{}valueAscii[0] + final fun (): kotlin/ByteArray // kotlinx.rpc.grpc/StatusCode.valueAscii.|(){}[0] + + final fun valueOf(kotlin/String): kotlinx.rpc.grpc/StatusCode // kotlinx.rpc.grpc/StatusCode.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // kotlinx.rpc.grpc/StatusCode.values|values#static(){}[0] +} + abstract interface <#A: kotlin/Any> kotlinx.rpc.grpc.descriptor/GrpcDelegate { // kotlinx.rpc.grpc.descriptor/GrpcDelegate|null[0] abstract fun clientProvider(kotlinx.rpc.grpc/ManagedChannel): kotlinx.rpc.grpc.descriptor/GrpcClientDelegate // kotlinx.rpc.grpc.descriptor/GrpcDelegate.clientProvider|clientProvider(kotlinx.rpc.grpc.ManagedChannel){}[0] abstract fun definitionFor(#A): kotlinx.rpc.grpc/ServerServiceDefinition // kotlinx.rpc.grpc.descriptor/GrpcDelegate.definitionFor|definitionFor(1:0){}[0] @@ -48,52 +78,10 @@ abstract interface kotlinx.rpc.grpc/Server { // kotlinx.rpc.grpc/Server|null[0] abstract suspend fun awaitTermination(kotlin.time/Duration = ...): kotlinx.rpc.grpc/Server // kotlinx.rpc.grpc/Server.awaitTermination|awaitTermination(kotlin.time.Duration){}[0] } -abstract interface kotlinx.rpc.grpc/Status { // kotlinx.rpc.grpc/Status|null[0] - abstract val cause // kotlinx.rpc.grpc/Status.cause|{}cause[0] - abstract fun (): kotlin/Throwable? // kotlinx.rpc.grpc/Status.cause.|(){}[0] - abstract val code // kotlinx.rpc.grpc/Status.code|{}code[0] - abstract fun (): kotlinx.rpc.grpc/Status.Code // kotlinx.rpc.grpc/Status.code.|(){}[0] - abstract val description // kotlinx.rpc.grpc/Status.description|{}description[0] - abstract fun (): kotlin/String? // kotlinx.rpc.grpc/Status.description.|(){}[0] - - final enum class Code : kotlin/Enum { // kotlinx.rpc.grpc/Status.Code|null[0] - enum entry ABORTED // kotlinx.rpc.grpc/Status.Code.ABORTED|null[0] - enum entry ALREADY_EXISTS // kotlinx.rpc.grpc/Status.Code.ALREADY_EXISTS|null[0] - enum entry CANCELLED // kotlinx.rpc.grpc/Status.Code.CANCELLED|null[0] - enum entry DATA_LOSS // kotlinx.rpc.grpc/Status.Code.DATA_LOSS|null[0] - enum entry DEADLINE_EXCEEDED // kotlinx.rpc.grpc/Status.Code.DEADLINE_EXCEEDED|null[0] - enum entry FAILED_PRECONDITION // kotlinx.rpc.grpc/Status.Code.FAILED_PRECONDITION|null[0] - enum entry INTERNAL // kotlinx.rpc.grpc/Status.Code.INTERNAL|null[0] - enum entry INVALID_ARGUMENT // kotlinx.rpc.grpc/Status.Code.INVALID_ARGUMENT|null[0] - enum entry NOT_FOUND // kotlinx.rpc.grpc/Status.Code.NOT_FOUND|null[0] - enum entry OK // kotlinx.rpc.grpc/Status.Code.OK|null[0] - enum entry OUT_OF_RANGE // kotlinx.rpc.grpc/Status.Code.OUT_OF_RANGE|null[0] - enum entry PERMISSION_DENIED // kotlinx.rpc.grpc/Status.Code.PERMISSION_DENIED|null[0] - enum entry RESOURCE_EXHAUSTED // kotlinx.rpc.grpc/Status.Code.RESOURCE_EXHAUSTED|null[0] - enum entry UNAUTHENTICATED // kotlinx.rpc.grpc/Status.Code.UNAUTHENTICATED|null[0] - enum entry UNAVAILABLE // kotlinx.rpc.grpc/Status.Code.UNAVAILABLE|null[0] - enum entry UNIMPLEMENTED // kotlinx.rpc.grpc/Status.Code.UNIMPLEMENTED|null[0] - enum entry UNKNOWN // kotlinx.rpc.grpc/Status.Code.UNKNOWN|null[0] - - final val entries // kotlinx.rpc.grpc/Status.Code.entries|#static{}entries[0] - final fun (): kotlin.enums/EnumEntries // kotlinx.rpc.grpc/Status.Code.entries.|#static(){}[0] - final val value // kotlinx.rpc.grpc/Status.Code.value|{}value[0] - final fun (): kotlin/Int // kotlinx.rpc.grpc/Status.Code.value.|(){}[0] - final val valueAscii // kotlinx.rpc.grpc/Status.Code.valueAscii|{}valueAscii[0] - final fun (): kotlin/ByteArray // kotlinx.rpc.grpc/Status.Code.valueAscii.|(){}[0] - - final fun valueOf(kotlin/String): kotlinx.rpc.grpc/Status.Code // kotlinx.rpc.grpc/Status.Code.valueOf|valueOf#static(kotlin.String){}[0] - final fun values(): kotlin/Array // kotlinx.rpc.grpc/Status.Code.values|values#static(){}[0] - } -} - -abstract interface kotlinx.rpc.grpc/StatusRuntimeException { // kotlinx.rpc.grpc/StatusRuntimeException|null[0] - abstract val status // kotlinx.rpc.grpc/StatusRuntimeException.status|{}status[0] - abstract fun (): kotlinx.rpc.grpc/Status // kotlinx.rpc.grpc/StatusRuntimeException.status.|(){}[0] -} - abstract class <#A: kotlinx.rpc.grpc/ManagedChannelBuilder<#A>> kotlinx.rpc.grpc/ManagedChannelBuilder { // kotlinx.rpc.grpc/ManagedChannelBuilder|null[0] constructor () // kotlinx.rpc.grpc/ManagedChannelBuilder.|(){}[0] + + final fun usePlaintext(): #A // kotlinx.rpc.grpc/ManagedChannelBuilder.usePlaintext|usePlaintext(){}[0] } abstract class <#A: kotlinx.rpc.grpc/ServerBuilder<#A>> kotlinx.rpc.grpc/ServerBuilder { // kotlinx.rpc.grpc/ServerBuilder|null[0] @@ -107,7 +95,7 @@ abstract class kotlinx.rpc.grpc/HandlerRegistry { // kotlinx.rpc.grpc/HandlerReg constructor () // kotlinx.rpc.grpc/HandlerRegistry.|(){}[0] } -abstract class kotlinx.rpc.grpc/ManagedChannelPlatform { // kotlinx.rpc.grpc/ManagedChannelPlatform|null[0] +abstract class kotlinx.rpc.grpc/ManagedChannelPlatform : kotlinx.rpc.grpc.internal/GrpcChannel { // kotlinx.rpc.grpc/ManagedChannelPlatform|null[0] constructor () // kotlinx.rpc.grpc/ManagedChannelPlatform.|(){}[0] } @@ -135,11 +123,46 @@ final class kotlinx.rpc.grpc/GrpcServer : kotlinx.rpc.grpc/Server, kotlinx.rpc/R final suspend fun awaitTermination(kotlin.time/Duration): kotlinx.rpc.grpc/GrpcServer // kotlinx.rpc.grpc/GrpcServer.awaitTermination|awaitTermination(kotlin.time.Duration){}[0] } +final class kotlinx.rpc.grpc/GrpcTrailers { // kotlinx.rpc.grpc/GrpcTrailers|null[0] + constructor () // kotlinx.rpc.grpc/GrpcTrailers.|(){}[0] + + final fun merge(kotlinx.rpc.grpc/GrpcTrailers) // kotlinx.rpc.grpc/GrpcTrailers.merge|merge(kotlinx.rpc.grpc.GrpcTrailers){}[0] +} + final class kotlinx.rpc.grpc/ServerServiceDefinition { // kotlinx.rpc.grpc/ServerServiceDefinition|null[0] constructor () // kotlinx.rpc.grpc/ServerServiceDefinition.|(){}[0] + + final fun getMethods(): kotlin.collections/Collection> // kotlinx.rpc.grpc/ServerServiceDefinition.getMethods|getMethods(){}[0] + final fun getServiceDescriptor(): kotlinx.rpc.grpc.internal/ServiceDescriptor // kotlinx.rpc.grpc/ServerServiceDefinition.getServiceDescriptor|getServiceDescriptor(){}[0] } +final class kotlinx.rpc.grpc/Status { // kotlinx.rpc.grpc/Status|null[0] + constructor () // kotlinx.rpc.grpc/Status.|(){}[0] + + final fun getCause(): kotlin/Throwable? // kotlinx.rpc.grpc/Status.getCause|getCause(){}[0] + final fun getDescription(): kotlin/String? // kotlinx.rpc.grpc/Status.getDescription|getDescription(){}[0] +} + +final class kotlinx.rpc.grpc/StatusException : kotlin/Exception { // kotlinx.rpc.grpc/StatusException|null[0] + constructor (kotlinx.rpc.grpc/Status) // kotlinx.rpc.grpc/StatusException.|(kotlinx.rpc.grpc.Status){}[0] + constructor (kotlinx.rpc.grpc/Status, kotlinx.rpc.grpc/GrpcTrailers?) // kotlinx.rpc.grpc/StatusException.|(kotlinx.rpc.grpc.Status;kotlinx.rpc.grpc.GrpcTrailers?){}[0] + + final fun getStatus(): kotlinx.rpc.grpc/Status // kotlinx.rpc.grpc/StatusException.getStatus|getStatus(){}[0] + final fun getTrailers(): kotlinx.rpc.grpc/GrpcTrailers? // kotlinx.rpc.grpc/StatusException.getTrailers|getTrailers(){}[0] +} + +final class kotlinx.rpc.grpc/StatusRuntimeException : kotlin/RuntimeException { // kotlinx.rpc.grpc/StatusRuntimeException|null[0] + constructor (kotlinx.rpc.grpc/Status) // kotlinx.rpc.grpc/StatusRuntimeException.|(kotlinx.rpc.grpc.Status){}[0] + constructor (kotlinx.rpc.grpc/Status, kotlinx.rpc.grpc/GrpcTrailers?) // kotlinx.rpc.grpc/StatusRuntimeException.|(kotlinx.rpc.grpc.Status;kotlinx.rpc.grpc.GrpcTrailers?){}[0] + + final fun getStatus(): kotlinx.rpc.grpc/Status // kotlinx.rpc.grpc/StatusRuntimeException.getStatus|getStatus(){}[0] + final fun getTrailers(): kotlinx.rpc.grpc/GrpcTrailers? // kotlinx.rpc.grpc/StatusRuntimeException.getTrailers|getTrailers(){}[0] +} + +final val kotlinx.rpc.grpc/code // kotlinx.rpc.grpc/code|@kotlinx.rpc.grpc.Status{}code[0] + final fun (kotlinx.rpc.grpc/Status).(): kotlinx.rpc.grpc/StatusCode // kotlinx.rpc.grpc/code.|@kotlinx.rpc.grpc.Status(){}[0] + final fun kotlinx.rpc.grpc/GrpcClient(kotlin/String, kotlin/Function1, kotlin/Unit> = ...): kotlinx.rpc.grpc/GrpcClient // kotlinx.rpc.grpc/GrpcClient|GrpcClient(kotlin.String;kotlin.Function1,kotlin.Unit>){}[0] final fun kotlinx.rpc.grpc/GrpcClient(kotlin/String, kotlin/Int, kotlin/Function1, kotlin/Unit> = ...): kotlinx.rpc.grpc/GrpcClient // kotlinx.rpc.grpc/GrpcClient|GrpcClient(kotlin.String;kotlin.Int;kotlin.Function1,kotlin.Unit>){}[0] final fun kotlinx.rpc.grpc/GrpcServer(kotlin/Int, kotlin/Function1, kotlin/Unit> = ..., kotlin/Function1 = ...): kotlinx.rpc.grpc/GrpcServer // kotlinx.rpc.grpc/GrpcServer|GrpcServer(kotlin.Int;kotlin.Function1,kotlin.Unit>;kotlin.Function1){}[0] -final fun kotlinx.rpc.grpc/StatusRuntimeException(kotlinx.rpc.grpc/Status): kotlinx.rpc.grpc/StatusRuntimeException // kotlinx.rpc.grpc/StatusRuntimeException|StatusRuntimeException(kotlinx.rpc.grpc.Status){}[0] +final fun kotlinx.rpc.grpc/Status(kotlinx.rpc.grpc/StatusCode, kotlin/String? = ..., kotlin/Throwable? = ...): kotlinx.rpc.grpc/Status // kotlinx.rpc.grpc/Status|Status(kotlinx.rpc.grpc.StatusCode;kotlin.String?;kotlin.Throwable?){}[0] diff --git a/grpc/grpc-core/build.gradle.kts b/grpc/grpc-core/build.gradle.kts index e2df67fd9..e522e26e9 100644 --- a/grpc/grpc-core/build.gradle.kts +++ b/grpc/grpc-core/build.gradle.kts @@ -36,6 +36,7 @@ kotlin { commonTest { dependencies { implementation(libs.kotlin.test) + implementation(libs.coroutines.test) } } diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.kt new file mode 100644 index 000000000..e8f2fb903 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +@Suppress("RedundantConstructorKeyword") +public expect class GrpcTrailers constructor() { + public fun merge(trailers: GrpcTrailers) +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt index c7ea10b53..ea0d015ec 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt @@ -6,12 +6,13 @@ package kotlinx.rpc.grpc +import kotlinx.rpc.grpc.internal.GrpcChannel import kotlin.time.Duration /** * Same as [ManagedChannel], but is platform-exposed. */ -public expect abstract class ManagedChannelPlatform +public expect abstract class ManagedChannelPlatform : GrpcChannel /** * A virtual connection to a conceptual endpoint, to perform RPCs. @@ -66,7 +67,9 @@ public interface ManagedChannel { /** * Builder class for [ManagedChannel]. */ -public expect abstract class ManagedChannelBuilder> +public expect abstract class ManagedChannelBuilder> { + public fun usePlaintext(): T +} internal expect fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> internal expect fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*> diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.kt index c69b95c10..80c6b5846 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.kt @@ -6,7 +6,20 @@ package kotlinx.rpc.grpc +import kotlinx.rpc.grpc.internal.ServerMethodDefinition +import kotlinx.rpc.grpc.internal.ServiceDescriptor +import kotlinx.rpc.internal.utils.InternalRpcApi + /** * Definition of a service to be exposed via a Server. */ -public expect class ServerServiceDefinition +public expect class ServerServiceDefinition { + public fun getServiceDescriptor(): ServiceDescriptor + public fun getMethods(): Collection> +} + +@InternalRpcApi +public expect fun serverServiceDefinition( + serviceDescriptor: ServiceDescriptor, + methods: Collection> +): ServerServiceDefinition diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/Status.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/Status.kt index 212d46b6d..65cf7a054 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/Status.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/Status.kt @@ -7,7 +7,7 @@ package kotlinx.rpc.grpc /** - * Defines the status of an operation by providing a standard [Code] in conjunction with an + * Defines the status of an operation by providing a standard [StatusCode] in conjunction with an * optional descriptive message. * * For clients, every remote call will return a status on completion. @@ -15,7 +15,7 @@ package kotlinx.rpc.grpc * status may be propagated to blocking stubs as a [RuntimeException] or to a listener as an * explicit parameter. * - * Similarly, servers can report a status by throwing [StatusRuntimeException] + * Similarly, servers can report a status by throwing [StatusException] * or by passing the status to a callback. * * Utility functions are provided to convert a status to an exception and to extract them @@ -25,30 +25,33 @@ package kotlinx.rpc.grpc * can be found at * [doc/statuscodes.md](https://github.com/grpc/grpc/blob/master/doc/statuscodes.md) */ -public interface Status { - public val code: Code - public val description: String? - public val cause: Throwable? +public expect class Status { + public fun getDescription(): String? + public fun getCause(): Throwable? +} + +public expect fun Status(code: StatusCode, description: String? = null, cause: Throwable? = null): Status + +public expect val Status.code: StatusCode - public enum class Code(public val value: Int) { - OK(0), - CANCELLED(1), - UNKNOWN(2), - INVALID_ARGUMENT(3), - DEADLINE_EXCEEDED(4), - NOT_FOUND(5), - ALREADY_EXISTS(6), - PERMISSION_DENIED(7), - RESOURCE_EXHAUSTED(8), - FAILED_PRECONDITION(9), - ABORTED(10), - OUT_OF_RANGE(11), - UNIMPLEMENTED(12), - INTERNAL(13), - UNAVAILABLE(14), - DATA_LOSS(15), - UNAUTHENTICATED(16); +public enum class StatusCode(public val value: Int) { + OK(0), + CANCELLED(1), + UNKNOWN(2), + INVALID_ARGUMENT(3), + DEADLINE_EXCEEDED(4), + NOT_FOUND(5), + ALREADY_EXISTS(6), + PERMISSION_DENIED(7), + RESOURCE_EXHAUSTED(8), + FAILED_PRECONDITION(9), + ABORTED(10), + OUT_OF_RANGE(11), + UNIMPLEMENTED(12), + INTERNAL(13), + UNAVAILABLE(14), + DATA_LOSS(15), + UNAUTHENTICATED(16); - public val valueAscii: ByteArray = value.toString().encodeToByteArray() - } + public val valueAscii: ByteArray = value.toString().encodeToByteArray() } diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/StatusException.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/StatusException.kt new file mode 100644 index 000000000..0cc1fd20b --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/StatusException.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +/** + * [Status] in Exception form, for propagating Status information via exceptions. + */ +public expect class StatusException : Exception { + public constructor(status: Status) + public constructor(status: Status, trailers: GrpcTrailers?) + + public fun getStatus(): Status + public fun getTrailers(): GrpcTrailers? +} + +public expect class StatusRuntimeException : RuntimeException { + public constructor(status: Status) + public constructor(status: Status, trailers: GrpcTrailers?) + + public fun getStatus(): Status + public fun getTrailers(): GrpcTrailers? +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.kt deleted file mode 100644 index f816ef45e..000000000 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.rpc.grpc - -/** - * [Status] in RuntimeException form, for propagating [Status] information via exceptions. - */ -public interface StatusRuntimeException { - /** - * The status code as a [Status] object. - */ - public val status: Status -} - -/** - * Constructor function for the [StatusRuntimeException] class. - */ -public expect fun StatusRuntimeException(status: Status) : StatusRuntimeException diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.kt new file mode 100644 index 000000000..7984c0c3f --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.grpc.GrpcTrailers +import kotlinx.rpc.grpc.Status +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public expect abstract class ClientCall { + @InternalRpcApi + public abstract class Listener { + public open fun onHeaders(headers: GrpcTrailers) + public open fun onMessage(message: Message) + public open fun onClose(status: Status, trailers: GrpcTrailers) + public open fun onReady() + } + + public abstract fun start(responseListener: Listener, headers: GrpcTrailers) + public abstract fun request(numMessages: Int) + public abstract fun cancel(message: String?, cause: Throwable?) + public abstract fun halfClose() + public abstract fun sendMessage(message: Request) + public open fun isReady(): Boolean +} + +@InternalRpcApi +public expect fun clientCallListener( + onHeaders: (headers: GrpcTrailers) -> Unit, + onMessage: (message: Message) -> Unit, + onClose: (status: Status, trailers: GrpcTrailers) -> Unit, + onReady: () -> Unit, +): ClientCall.Listener diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.kt new file mode 100644 index 000000000..3e96b6230 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public expect class GrpcCallOptions + +@InternalRpcApi +public expect val GrpcDefaultCallOptions: GrpcCallOptions diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.kt new file mode 100644 index 000000000..165f41733 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public expect abstract class GrpcChannel { + public abstract fun newCall( + methodDescriptor: MethodDescriptor, + callOptions: GrpcCallOptions, + ): ClientCall + + public abstract fun authority(): String +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.kt new file mode 100644 index 000000000..2ec2e4487 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlin.coroutines.CoroutineContext + +internal expect class GrpcContext +internal expect val CurrentGrpcContext: GrpcContext + +internal expect class GrpcContextElement : CoroutineContext.Element { + companion object Key : CoroutineContext.Key { + fun current(): GrpcContextElement + } + + override val key: CoroutineContext.Key +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.kt new file mode 100644 index 000000000..728c804ad --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.io.Source +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public expect abstract class InputStream + +@InternalRpcApi +public interface MessageCodec { + public fun encode(value: T): Source + public fun decode(stream: Source): T +} + +@InternalRpcApi +public expect class MethodDescriptor { + public fun getFullMethodName(): String + public fun getServiceName(): String? + public fun getRequestMarshaller(): Marshaller + public fun getResponseMarshaller(): Marshaller + public fun getSchemaDescriptor(): Any? + public fun isIdempotent(): Boolean + public fun isSafe(): Boolean + public fun isSampledToLocalTracing(): Boolean + + public interface Marshaller { + public fun stream(value: T): InputStream + public fun parse(stream: InputStream): T + } +} + +@InternalRpcApi +internal expect val MethodDescriptor<*, *>.type: MethodType + +@InternalRpcApi +public enum class MethodType { + UNARY, + CLIENT_STREAMING, + SERVER_STREAMING, + BIDI_STREAMING, + UNKNOWN, +} + +@InternalRpcApi +public expect fun methodDescriptor( + fullMethodName: String, + requestCodec: MessageCodec, + responseCodec: MessageCodec, + type: MethodType, + schemaDescriptor: Any?, + idempotent: Boolean, + safe: Boolean, + sampledToLocalTracing: Boolean, +): MethodDescriptor + +public val MethodType.clientSendsOneMessage: Boolean get() { + return this == MethodType.UNARY || this == MethodType.SERVER_STREAMING +} + +public val MethodType.serverSendsOneMessage: Boolean get() { + return this == MethodType.SERVER_STREAMING || this == MethodType.CLIENT_STREAMING +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.kt new file mode 100644 index 000000000..8b390e756 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.grpc.GrpcTrailers +import kotlinx.rpc.grpc.Status +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public expect fun interface ServerCallHandler { + public fun startCall(call: ServerCall, headers: GrpcTrailers): ServerCall.Listener +} + +@InternalRpcApi +public expect abstract class ServerCall { + @InternalRpcApi + public abstract class Listener { + public open fun onMessage(message: Request) + public open fun onHalfClose() + public open fun onCancel() + public open fun onComplete() + public open fun onReady() + } + + public abstract fun request(numMessages: Int) + public abstract fun sendHeaders(headers: GrpcTrailers) + public abstract fun sendMessage(message: Response) + public abstract fun close(status: Status, trailers: GrpcTrailers) + + public open fun isReady(): Boolean + public abstract fun isCancelled(): Boolean + + public abstract fun getMethodDescriptor(): MethodDescriptor +} + +@InternalRpcApi +public expect fun serverCallListener( + state: State, + onMessage: (State ,message: Message) -> Unit, + onHalfClose: (State) -> Unit, + onCancel: (State) -> Unit, + onComplete: (State) -> Unit, + onReady: (State) -> Unit, +): ServerCall.Listener diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.kt new file mode 100644 index 000000000..f6a837375 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public expect class ServerMethodDefinition { + public fun getMethodDescriptor(): MethodDescriptor + public fun getServerCallHandler(): ServerCallHandler +} + +public expect fun serverMethodDefinition( + descriptor: MethodDescriptor, + handler: ServerCallHandler, +): ServerMethodDefinition diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.kt new file mode 100644 index 000000000..0d7b558a9 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public expect class ServiceDescriptor { + public fun getName(): String + public fun getMethods(): Collection> + public fun getSchemaDescriptor(): Any? +} + +@InternalRpcApi +public expect fun serviceDescriptor( + name: String, + methods: Collection>, + schemaDescriptor: Any? = null, +): ServiceDescriptor diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/suspendClientCalls.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/suspendClientCalls.kt new file mode 100644 index 000000000..20317e199 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/suspendClientCalls.kt @@ -0,0 +1,267 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.onFailure +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.single +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.rpc.grpc.GrpcTrailers +import kotlinx.rpc.grpc.Status +import kotlinx.rpc.grpc.StatusCode +import kotlinx.rpc.grpc.StatusException +import kotlinx.rpc.grpc.code +import kotlinx.rpc.internal.utils.InternalRpcApi + +// heavily inspired by +// https://github.com/grpc/grpc-kotlin/blob/master/stub/src/main/java/io/grpc/kotlin/ClientCalls.kt + +@InternalRpcApi +public suspend fun unaryRpc( + channel: GrpcChannel, + descriptor: MethodDescriptor, + request: Request, + callOptions: GrpcCallOptions = GrpcDefaultCallOptions, + trailers: GrpcTrailers = GrpcTrailers(), +): Response { + val type = descriptor.type + require(type == MethodType.UNARY) { + "Expected a unary RPC method, but got $descriptor" + } + + return rpcImpl( + channel = channel, + descriptor = descriptor, + callOptions = callOptions, + headers = trailers, + request = ClientRequest.Unary(request) + ).singleOrStatus("request", descriptor) +} + +@InternalRpcApi +public fun serverStreamingRpc( + channel: GrpcChannel, + descriptor: MethodDescriptor, + request: Request, + callOptions: GrpcCallOptions = GrpcDefaultCallOptions, + headers: GrpcTrailers = GrpcTrailers(), +): Flow { + val type = descriptor.type + require(type == MethodType.SERVER_STREAMING) { + "Expected a server streaming RPC method, but got $type" + } + + return rpcImpl( + channel = channel, + descriptor = descriptor, + callOptions = callOptions, + headers = headers, + request = ClientRequest.Unary(request) + ) +} + +@InternalRpcApi +public suspend fun clientStreamingRpc( + channel: GrpcChannel, + descriptor: MethodDescriptor, + requests: Flow, + callOptions: GrpcCallOptions = GrpcDefaultCallOptions, + headers: GrpcTrailers = GrpcTrailers(), +): Response { + val type = descriptor.type + require(type == MethodType.CLIENT_STREAMING) { + "Expected a client streaming RPC method, but got $type" + } + + return rpcImpl( + channel = channel, + descriptor = descriptor, + callOptions = callOptions, + headers = headers, + request = ClientRequest.Flowing(requests) + ).singleOrStatus("response", descriptor) +} + +@InternalRpcApi +public fun bidirectionalStreamingRpc( + channel: GrpcChannel, + descriptor: MethodDescriptor, + requests: Flow, + callOptions: GrpcCallOptions = GrpcDefaultCallOptions, + headers: GrpcTrailers = GrpcTrailers(), +): Flow { + val type = descriptor.type + check(type == MethodType.BIDI_STREAMING) { + "Expected a bidirectional streaming method, but got $type" + } + + return rpcImpl( + channel = channel, + descriptor = descriptor, + callOptions = callOptions, + headers = headers, + request = ClientRequest.Flowing(requests) + ) +} + +private sealed interface ClientRequest { + suspend fun sendTo( + clientCall: ClientCall, + ready: Ready, + ) + + class Unary(private val request: Request) : ClientRequest { + override suspend fun sendTo( + clientCall: ClientCall, + ready: Ready, + ) { + clientCall.sendMessage(request) + } + } + + class Flowing(private val requestFlow: Flow) : ClientRequest { + override suspend fun sendTo( + clientCall: ClientCall, + ready: Ready, + ) { + ready.suspendUntilReady() + requestFlow.collect { request -> + clientCall.sendMessage(request) + ready.suspendUntilReady() + } + } + } +} + +private fun rpcImpl( + channel: GrpcChannel, + descriptor: MethodDescriptor, + callOptions: GrpcCallOptions, + headers: GrpcTrailers, + request: ClientRequest, +): Flow = flow { + coroutineScope { + val handler = channel.newCall(descriptor, callOptions) + + /* + * We maintain a buffer of size 1 so onMessage never has to block: it only gets called after + * we request a response from the server, which only happens when responses is empty and + * there is room in the buffer. + */ + val responses = Channel(1) + val ready = Ready() + + handler.start(channelResponseListener(responses, ready), headers) + + val fullMethodName = descriptor.getFullMethodName() + val sender = launch(CoroutineName("grpc-send-message-$fullMethodName")) { + try { + request.sendTo(handler, ready) + handler.halfClose() + } catch (ex: Exception) { + handler.cancel("Collection of requests completed exceptionally", ex) + throw ex // propagate failure upward + } + } + + try { + handler.request(1) + for (response in responses) { + emit(response) + handler.request(1) + } + } catch (e: Exception) { + withContext(NonCancellable) { + sender.cancel("Collection of responses completed exceptionally", e) + sender.join() + // we want the sender to be done cancelling before we cancel the handler, or it might try + // sending to a dead call, which results in ugly exception messages + handler.cancel("Collection of responses completed exceptionally", e) + } + throw e + } + + if (!sender.isCompleted) { + sender.cancel("Collection of responses completed before collection of requests") + } + } +} + +private fun channelResponseListener( + responses: Channel, + ready: Ready, +) = clientCallListener( + onHeaders = { + // todo check what happens here + }, + onMessage = { message: Response -> + responses.trySend(message).onFailure { e -> + throw e ?: AssertionError("onMessage should never be called until responses is ready") + } + }, + onClose = { status: Status, trailers: GrpcTrailers -> + val cause = when { + status.code == StatusCode.OK -> null + status.getCause() is CancellationException -> status.getCause() + else -> StatusException(status, trailers) + } + + responses.close(cause = cause) + }, + onReady = { + ready.onReady() + }, +) + +// todo really needed? +internal fun Flow.singleOrStatusFlow( + expected: String, + descriptor: Any +): Flow = flow { + var found = false + collect { + if (!found) { + found = true + emit(it) + } else { + throw StatusException( + Status(StatusCode.INTERNAL, "Expected one $expected for $descriptor but received two") + ) + } + } + + if (!found) { + throw StatusException( + Status(StatusCode.INTERNAL, "Expected one $expected for $descriptor but received none") + ) + } +} + +internal suspend fun Flow.singleOrStatus( + expected: String, + descriptor: Any +): T = singleOrStatusFlow(expected, descriptor).single() + +internal class Ready { + // A CONFLATED channel never suspends to send, and two notifications of readiness are equivalent + // to one + private val channel = Channel(Channel.CONFLATED) + + fun onReady() { + channel.trySend(Unit) + } + + suspend fun suspendUntilReady() { + channel.receive() + } +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/suspendServerCalls.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/suspendServerCalls.kt new file mode 100644 index 000000000..f9a58ebf8 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/suspendServerCalls.kt @@ -0,0 +1,222 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.onFailure +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.rpc.grpc.GrpcTrailers +import kotlinx.rpc.grpc.Status +import kotlinx.rpc.grpc.StatusCode +import kotlinx.rpc.grpc.StatusException +import kotlinx.rpc.grpc.StatusRuntimeException +import kotlinx.rpc.internal.utils.InternalRpcApi +import kotlin.concurrent.atomics.AtomicBoolean +import kotlin.concurrent.atomics.ExperimentalAtomicApi + +@InternalRpcApi +public fun CoroutineScope.unaryServerMethodDefinition( + descriptor: MethodDescriptor, + implementation: suspend (request: Request) -> Response, +): ServerMethodDefinition { + val type = descriptor.type + require(type == MethodType.UNARY) { + "Expected a unary method descriptor but got $descriptor" + } + + return serverMethodDefinition(descriptor) { requests -> + requests + .singleOrStatusFlow("request", descriptor) + .map { implementation(it) } + } +} + +@InternalRpcApi +public fun CoroutineScope.clientStreamingServerMethodDefinition( + descriptor: MethodDescriptor, + implementation: suspend (requests: Flow) -> Response, +): ServerMethodDefinition { + val type = descriptor.type + require(type == MethodType.CLIENT_STREAMING) { + "Expected a client streaming method descriptor but got $descriptor" + } + + return serverMethodDefinition(descriptor) { requests -> + flow { + val response = implementation(requests) + emit(response) + } + } +} + +@InternalRpcApi +public fun CoroutineScope.serverStreamingServerMethodDefinition( + descriptor: MethodDescriptor, + implementation: (request: Request) -> Flow, +): ServerMethodDefinition { + val type = descriptor.type + require(type == MethodType.SERVER_STREAMING) { + "Expected a server streaming method descriptor but got $descriptor" + } + + return serverMethodDefinition(descriptor) { requests -> + flow { + requests + .singleOrStatusFlow("request", descriptor) + .collect { req -> + implementation(req).collect { resp -> emit(resp) } + } + } + } +} + +@InternalRpcApi +public fun CoroutineScope.bidiStreamingServerMethodDefinition( + descriptor: MethodDescriptor, + implementation: (requests: Flow) -> Flow, +): ServerMethodDefinition { + val type = descriptor.type + check(type == MethodType.BIDI_STREAMING) { + "Expected a bidi streaming method descriptor but got $descriptor" + } + + return serverMethodDefinition(descriptor, implementation) +} + +private fun CoroutineScope.serverMethodDefinition( + descriptor: MethodDescriptor, + implementation: (Flow) -> Flow, +): ServerMethodDefinition = serverMethodDefinition(descriptor, serverCallHandler(implementation)) + +private fun CoroutineScope.serverCallHandler( + implementation: (Flow) -> Flow, +): ServerCallHandler = + ServerCallHandler { call, _ -> + serverCallListenerImpl(call, implementation) + } + +@OptIn(ExperimentalAtomicApi::class) +private fun CoroutineScope.serverCallListenerImpl( + handler: ServerCall, + implementation: (Flow) -> Flow, +): ServerCall.Listener { + val readiness = Ready() + val requestsChannel = Channel(1) + + val requestsStarted = AtomicBoolean(false) // enforces read-once + + val requests = flow { + check(requestsStarted.compareAndSet(expectedValue = false, newValue = true)) { + "requests flow can only be collected once" + } + + handler.request(1) + try { + for (request in requestsChannel) { + emit(request) + handler.request(1) + } + } catch (e: Exception) { + requestsChannel.cancel( + CancellationException("Exception thrown while collecting requests", e) + ) + handler.request(1) // make sure we don't cause backpressure + throw e + } + } + + val rpcJob = launch(GrpcContextElement.current()) { + val mutex = Mutex() + val headersSent = AtomicBoolean(false) // enforces only sending headers once + val failure = runCatching { + implementation(requests).collect { + // once we have a response message, check if we've sent headers yet - if not, do so + if (headersSent.compareAndSet(expectedValue = false, newValue = true)) { + mutex.withLock { + handler.sendHeaders(GrpcTrailers()) + } + } + readiness.suspendUntilReady() + mutex.withLock { handler.sendMessage(it) } + } + }.exceptionOrNull() + // check headers again once we're done collecting the response flow - if we received + // no elements or threw an exception, then we wouldn't have sent them + if (failure == null && headersSent.compareAndSet(expectedValue = false, newValue = true)) { + mutex.withLock { + handler.sendHeaders(GrpcTrailers()) + } + } + + val closeStatus = when (failure) { + null -> Status(StatusCode.OK) + is CancellationException -> Status(StatusCode.CANCELLED, cause = failure) + is StatusException -> failure.getStatus() + is StatusRuntimeException -> failure.getStatus() + else -> Status(StatusCode.UNKNOWN, cause = failure) + } + + val trailers = failure?.let { + when (it) { + is StatusException -> { + it.getTrailers() + } + + is StatusRuntimeException -> { + it.getTrailers() + } + + else -> { + null + } + } + } ?: GrpcTrailers() + + mutex.withLock { handler.close(closeStatus, trailers) } + } + + return serverCallListener( + state = ServerCallListenerState(), + onCancel = { + rpcJob.cancel("Cancellation received from client") + }, + onMessage = { state, message: Request -> + if (state.isReceiving) { + val result = requestsChannel.trySend(message) + state.isReceiving = result.isSuccess + result.onFailure { ex -> + if (ex !is CancellationException) { + throw StatusException( + Status(StatusCode.INTERNAL, "onMessage should never be called until requests is ready"), + ) + } + } + } + + if (!state.isReceiving) { + handler.request(1) // do not exert backpressure + } + }, + onHalfClose = { + requestsChannel.close() + }, + onReady = { + readiness.onReady() + }, + onComplete = {} + ) +} + +private class ServerCallListenerState { + var isReceiving = true +} diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/RawClientServerTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/RawClientServerTest.kt new file mode 100644 index 000000000..5f1008b86 --- /dev/null +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/RawClientServerTest.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.io.Buffer +import kotlinx.io.Source +import kotlinx.io.readString +import kotlinx.io.writeString +import kotlinx.rpc.grpc.internal.GrpcChannel +import kotlinx.rpc.grpc.internal.MessageCodec +import kotlinx.rpc.grpc.internal.MethodDescriptor +import kotlinx.rpc.grpc.internal.MethodType +import kotlinx.rpc.grpc.internal.ServerMethodDefinition +import kotlinx.rpc.grpc.internal.bidiStreamingServerMethodDefinition +import kotlinx.rpc.grpc.internal.bidirectionalStreamingRpc +import kotlinx.rpc.grpc.internal.clientStreamingRpc +import kotlinx.rpc.grpc.internal.clientStreamingServerMethodDefinition +import kotlinx.rpc.grpc.internal.methodDescriptor +import kotlinx.rpc.grpc.internal.serverStreamingRpc +import kotlinx.rpc.grpc.internal.serverStreamingServerMethodDefinition +import kotlinx.rpc.grpc.internal.serviceDescriptor +import kotlinx.rpc.grpc.internal.unaryRpc +import kotlinx.rpc.grpc.internal.unaryServerMethodDefinition +import kotlin.test.Test +import kotlin.test.assertEquals + +private const val PORT = 8082 + +class RawClientServerTest { + @Test + fun unaryCall() = runTest( + methodName = "unary", + type = MethodType.UNARY, + methodDefinition = { descriptor -> + unaryServerMethodDefinition(descriptor) { it + it } + }, + ) { channel, descriptor -> + val response = unaryRpc(channel, descriptor, "Hello") + + assertEquals("HelloHello", response) + } + + @Test + fun serverStreamingCall() = runTest( + methodName = "serverStreaming", + type = MethodType.SERVER_STREAMING, + methodDefinition = { descriptor -> + serverStreamingServerMethodDefinition(descriptor) { + flowOf(it, it) + } + } + ) { channel, descriptor -> + val response = serverStreamingRpc(channel, descriptor, "Hello") + + assertEquals(listOf("Hello", "Hello"), response.toList()) + } + + @Test + fun clientStreamingCall() = runTest( + methodName = "clientStreaming", + type = MethodType.CLIENT_STREAMING, + methodDefinition = { descriptor -> + clientStreamingServerMethodDefinition(descriptor) { + it.toList().joinToString(separator = "") + } + } + ) { channel, descriptor -> + val response = clientStreamingRpc(channel, descriptor, flowOf("Hello", "World")) + + assertEquals("HelloWorld", response) + } + + @Test + fun bidirectionalStreamingCall() = runTest( + methodName = "bidirectionalStreaming", + type = MethodType.BIDI_STREAMING, + methodDefinition = { descriptor -> + bidiStreamingServerMethodDefinition(descriptor) { + it.map { str -> str + str } + } + } + ) { channel, descriptor -> + val response = bidirectionalStreamingRpc(channel, descriptor, flowOf("Hello", "World")) + .toList() + + assertEquals(listOf("HelloHello", "WorldWorld"), response) + } + + private fun runTest( + methodName: String, + type: MethodType, + methodDefinition: CoroutineScope.(MethodDescriptor) -> ServerMethodDefinition, + block: suspend (GrpcChannel, MethodDescriptor) -> Unit, + ) = kotlinx.coroutines.test.runTest { + val clientChannel = ManagedChannelBuilder("localhost", PORT).apply { + usePlaintext() + }.buildChannel() + + val descriptor = methodDescriptor( + fullMethodName = "${SERVICE_NAME}/$methodName", + requestCodec = simpleCodec, + responseCodec = simpleCodec, + type = type, + schemaDescriptor = Unit, + idempotent = true, + safe = true, + sampledToLocalTracing = true, + ) + + val methods = listOf(descriptor) + + val builder = ServerBuilder(PORT).addService( + serverServiceDefinition( + serviceDescriptor = serviceDescriptor( + name = SERVICE_NAME, + methods = methods, + schemaDescriptor = Unit, + ), + methods = methods.map { methodDefinition(it) }, + ) + ) + val server = Server(builder) + server.start() + + block(clientChannel.platformApi, descriptor) + } + + companion object { + private const val SERVICE_NAME = "TestService" + + private val simpleCodec = object : MessageCodec { + override fun encode(value: String): Source { + return Buffer().apply { writeString(value) } + } + + override fun decode(stream: Source): String { + return stream.readString() + } + } + } +} diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt index 49f33c1ad..f12e932fa 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt @@ -68,4 +68,4 @@ class ProtosTest { assertEquals(msg.listString, decoded?.listString) } -} \ No newline at end of file +} diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.js.kt deleted file mode 100644 index 11f7045d3..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.js.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.rpc.grpc - -/** - * Constructor function for the [StatusRuntimeException] class. - */ -public actual fun StatusRuntimeException(status: Status): StatusRuntimeException { - error("JS target is not supported in gRPC") -} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.jvm.kt new file mode 100644 index 000000000..090e3c718 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.jvm.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual typealias GrpcTrailers = io.grpc.Metadata diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.jvm.kt index 4f14dce6a..295608efb 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.jvm.kt @@ -2,11 +2,20 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - package kotlinx.rpc.grpc -/** - * Definition of a service to be exposed via a Server. - */ +import kotlinx.rpc.grpc.internal.ServerMethodDefinition +import kotlinx.rpc.grpc.internal.ServiceDescriptor +import kotlinx.rpc.internal.utils.InternalRpcApi + public actual typealias ServerServiceDefinition = io.grpc.ServerServiceDefinition + +@InternalRpcApi +public actual fun serverServiceDefinition( + serviceDescriptor: ServiceDescriptor, + methods: Collection>, +): ServerServiceDefinition { + return io.grpc.ServerServiceDefinition.builder(serviceDescriptor).apply { + methods.forEach { addMethod(it) } + }.build() +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/Status.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/Status.jvm.kt index b0072d177..888aae224 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/Status.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/Status.jvm.kt @@ -6,59 +6,55 @@ package kotlinx.rpc.grpc -internal fun Status.toJvm(): io.grpc.Status { - val code = when (code) { - Status.Code.OK -> io.grpc.Status.Code.OK - Status.Code.CANCELLED -> io.grpc.Status.Code.CANCELLED - Status.Code.UNKNOWN -> io.grpc.Status.Code.UNKNOWN - Status.Code.INVALID_ARGUMENT -> io.grpc.Status.Code.INVALID_ARGUMENT - Status.Code.DEADLINE_EXCEEDED -> io.grpc.Status.Code.DEADLINE_EXCEEDED - Status.Code.NOT_FOUND -> io.grpc.Status.Code.NOT_FOUND - Status.Code.ALREADY_EXISTS -> io.grpc.Status.Code.ALREADY_EXISTS - Status.Code.PERMISSION_DENIED -> io.grpc.Status.Code.PERMISSION_DENIED - Status.Code.RESOURCE_EXHAUSTED -> io.grpc.Status.Code.RESOURCE_EXHAUSTED - Status.Code.FAILED_PRECONDITION -> io.grpc.Status.Code.FAILED_PRECONDITION - Status.Code.ABORTED -> io.grpc.Status.Code.ABORTED - Status.Code.OUT_OF_RANGE -> io.grpc.Status.Code.OUT_OF_RANGE - Status.Code.UNIMPLEMENTED -> io.grpc.Status.Code.UNIMPLEMENTED - Status.Code.INTERNAL -> io.grpc.Status.Code.INTERNAL - Status.Code.UNAVAILABLE -> io.grpc.Status.Code.UNAVAILABLE - Status.Code.DATA_LOSS -> io.grpc.Status.Code.DATA_LOSS - Status.Code.UNAUTHENTICATED -> io.grpc.Status.Code.UNAUTHENTICATED +internal fun StatusCode.toJvm(): io.grpc.Status.Code { + return when (this) { + StatusCode.OK -> io.grpc.Status.Code.OK + StatusCode.CANCELLED -> io.grpc.Status.Code.CANCELLED + StatusCode.UNKNOWN -> io.grpc.Status.Code.UNKNOWN + StatusCode.INVALID_ARGUMENT -> io.grpc.Status.Code.INVALID_ARGUMENT + StatusCode.DEADLINE_EXCEEDED -> io.grpc.Status.Code.DEADLINE_EXCEEDED + StatusCode.NOT_FOUND -> io.grpc.Status.Code.NOT_FOUND + StatusCode.ALREADY_EXISTS -> io.grpc.Status.Code.ALREADY_EXISTS + StatusCode.PERMISSION_DENIED -> io.grpc.Status.Code.PERMISSION_DENIED + StatusCode.RESOURCE_EXHAUSTED -> io.grpc.Status.Code.RESOURCE_EXHAUSTED + StatusCode.FAILED_PRECONDITION -> io.grpc.Status.Code.FAILED_PRECONDITION + StatusCode.ABORTED -> io.grpc.Status.Code.ABORTED + StatusCode.OUT_OF_RANGE -> io.grpc.Status.Code.OUT_OF_RANGE + StatusCode.UNIMPLEMENTED -> io.grpc.Status.Code.UNIMPLEMENTED + StatusCode.INTERNAL -> io.grpc.Status.Code.INTERNAL + StatusCode.UNAVAILABLE -> io.grpc.Status.Code.UNAVAILABLE + StatusCode.DATA_LOSS -> io.grpc.Status.Code.DATA_LOSS + StatusCode.UNAUTHENTICATED -> io.grpc.Status.Code.UNAUTHENTICATED } - - return io.grpc.Status.fromCode(code) - .withDescription(description) - .withCause(cause) } -@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") -internal fun io.grpc.Status.toKotlin(): Status { - val code = when (code) { - io.grpc.Status.Code.OK -> Status.Code.OK - io.grpc.Status.Code.CANCELLED -> Status.Code.CANCELLED - io.grpc.Status.Code.UNKNOWN -> Status.Code.UNKNOWN - io.grpc.Status.Code.INVALID_ARGUMENT -> Status.Code.INVALID_ARGUMENT - io.grpc.Status.Code.DEADLINE_EXCEEDED -> Status.Code.DEADLINE_EXCEEDED - io.grpc.Status.Code.NOT_FOUND -> Status.Code.NOT_FOUND - io.grpc.Status.Code.ALREADY_EXISTS -> Status.Code.ALREADY_EXISTS - io.grpc.Status.Code.PERMISSION_DENIED -> Status.Code.PERMISSION_DENIED - io.grpc.Status.Code.RESOURCE_EXHAUSTED -> Status.Code.RESOURCE_EXHAUSTED - io.grpc.Status.Code.FAILED_PRECONDITION -> Status.Code.FAILED_PRECONDITION - io.grpc.Status.Code.ABORTED -> Status.Code.ABORTED - io.grpc.Status.Code.OUT_OF_RANGE -> Status.Code.OUT_OF_RANGE - io.grpc.Status.Code.UNIMPLEMENTED -> Status.Code.UNIMPLEMENTED - io.grpc.Status.Code.INTERNAL -> Status.Code.INTERNAL - io.grpc.Status.Code.UNAVAILABLE -> Status.Code.UNAVAILABLE - io.grpc.Status.Code.DATA_LOSS -> Status.Code.DATA_LOSS - io.grpc.Status.Code.UNAUTHENTICATED -> Status.Code.UNAUTHENTICATED +public actual typealias Status = io.grpc.Status + +public actual val Status.code: StatusCode + get() = when (this.code) { + io.grpc.Status.Code.OK -> StatusCode.OK + io.grpc.Status.Code.CANCELLED -> StatusCode.CANCELLED + io.grpc.Status.Code.UNKNOWN -> StatusCode.UNKNOWN + io.grpc.Status.Code.INVALID_ARGUMENT -> StatusCode.INVALID_ARGUMENT + io.grpc.Status.Code.DEADLINE_EXCEEDED -> StatusCode.DEADLINE_EXCEEDED + io.grpc.Status.Code.NOT_FOUND -> StatusCode.NOT_FOUND + io.grpc.Status.Code.ALREADY_EXISTS -> StatusCode.ALREADY_EXISTS + io.grpc.Status.Code.PERMISSION_DENIED -> StatusCode.PERMISSION_DENIED + io.grpc.Status.Code.RESOURCE_EXHAUSTED -> StatusCode.RESOURCE_EXHAUSTED + io.grpc.Status.Code.FAILED_PRECONDITION -> StatusCode.FAILED_PRECONDITION + io.grpc.Status.Code.ABORTED -> StatusCode.ABORTED + io.grpc.Status.Code.OUT_OF_RANGE -> StatusCode.OUT_OF_RANGE + io.grpc.Status.Code.UNIMPLEMENTED -> StatusCode.UNIMPLEMENTED + io.grpc.Status.Code.INTERNAL -> StatusCode.INTERNAL + io.grpc.Status.Code.UNAVAILABLE -> StatusCode.UNAVAILABLE + io.grpc.Status.Code.DATA_LOSS -> StatusCode.DATA_LOSS + io.grpc.Status.Code.UNAUTHENTICATED -> StatusCode.UNAUTHENTICATED } - return JvmStatus(code, description, cause) +public actual fun Status( + code: StatusCode, + description: String?, + cause: Throwable?, +): Status { + return Status.fromCode(code.toJvm()).withDescription(description).withCause(cause) } - -internal class JvmStatus( - override val code: Status.Code, - override val description: String? = null, - override val cause: Throwable? = null, -): Status diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/StatusException.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/StatusException.jvm.kt new file mode 100644 index 000000000..499f08b25 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/StatusException.jvm.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +public actual typealias StatusException = io.grpc.StatusException + +public actual typealias StatusRuntimeException = io.grpc.StatusRuntimeException diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.jvm.kt deleted file mode 100644 index 4113bafc2..000000000 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.jvm.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.rpc.grpc - -/** - * Constructor function for the [StatusRuntimeException] class. - */ -public actual fun StatusRuntimeException(status: Status): StatusRuntimeException { - return io.grpc.StatusRuntimeException(status.toJvm()).toKotlin() -} - -internal class JvmStatusRuntimeException(override val status: Status) : StatusRuntimeException - -public fun io.grpc.StatusRuntimeException.toKotlin(): StatusRuntimeException { - return JvmStatusRuntimeException(status.toKotlin()) -} - -public fun StatusRuntimeException.toJvm(): io.grpc.StatusRuntimeException { - return io.grpc.StatusRuntimeException(status.toJvm()) -} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.jvm.kt new file mode 100644 index 000000000..bd3dde915 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.jvm.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import io.grpc.Metadata +import io.grpc.ClientCall +import kotlinx.rpc.grpc.GrpcTrailers +import kotlinx.rpc.grpc.Status +import kotlinx.rpc.internal.utils.InternalRpcApi + +internal actual typealias ClientCall = ClientCall + +@InternalRpcApi +public actual inline fun clientCallListener( + crossinline onHeaders: (headers: GrpcTrailers) -> Unit, + crossinline onMessage: (message: Message) -> Unit, + crossinline onClose: (status: Status, trailers: GrpcTrailers) -> Unit, + crossinline onReady: () -> Unit, +): ClientCall.Listener { + return object : ClientCall.Listener() { + override fun onHeaders(headers: Metadata) { + onHeaders(headers) + } + + override fun onMessage(message: Message) { + onMessage(message) + } + + override fun onClose(status: Status, trailers: Metadata) { + onClose(status, trailers) + } + + override fun onReady() { + onReady() + } + } +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.jvm.kt new file mode 100644 index 000000000..927daeaa9 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.jvm.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual typealias GrpcCallOptions = io.grpc.CallOptions + +@InternalRpcApi +public actual val GrpcDefaultCallOptions: GrpcCallOptions + get() = GrpcCallOptions.DEFAULT diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.jvm.kt new file mode 100644 index 000000000..15e2ed5fc --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.jvm.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual typealias GrpcChannel = io.grpc.Channel diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.kt new file mode 100644 index 000000000..4309b1c6f --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import io.grpc.Context +import kotlinx.coroutines.ThreadContextElement +import kotlin.coroutines.CoroutineContext + +internal actual typealias GrpcContext = Context + +internal actual val CurrentGrpcContext: GrpcContext + get() = GrpcContext.current() + +internal actual class GrpcContextElement(private val grpcContext: GrpcContext) : ThreadContextElement { + actual companion object Key : CoroutineContext.Key { + actual fun current(): GrpcContextElement = GrpcContextElement(CurrentGrpcContext) + } + + actual override val key: CoroutineContext.Key + get() = GrpcContextElement + + override fun restoreThreadContext(context: CoroutineContext, oldState: GrpcContext) { + grpcContext.detach(oldState) + } + + override fun updateThreadContext(context: CoroutineContext): GrpcContext { + return grpcContext.attach() + } +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.jvm.kt new file mode 100644 index 000000000..497b966f6 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.jvm.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.io.asInputStream +import kotlinx.io.asSource +import kotlinx.io.buffered +import kotlinx.rpc.internal.utils.InternalRpcApi + +internal actual typealias InputStream = java.io.InputStream + +internal actual typealias MethodDescriptor = io.grpc.MethodDescriptor + +internal actual val MethodDescriptor<*, *>.type: MethodType + get() = when (this.type) { + io.grpc.MethodDescriptor.MethodType.UNARY -> MethodType.UNARY + io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING -> MethodType.CLIENT_STREAMING + io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING -> MethodType.SERVER_STREAMING + io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING -> MethodType.BIDI_STREAMING + io.grpc.MethodDescriptor.MethodType.UNKNOWN -> MethodType.UNKNOWN + } + +internal val MethodType.asJvm: io.grpc.MethodDescriptor.MethodType + get() = when (this) { + MethodType.UNARY -> io.grpc.MethodDescriptor.MethodType.UNARY + MethodType.CLIENT_STREAMING -> io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING + MethodType.SERVER_STREAMING -> io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING + MethodType.BIDI_STREAMING -> io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING + MethodType.UNKNOWN -> io.grpc.MethodDescriptor.MethodType.UNKNOWN + } + +private fun MessageCodec.toJvm(): io.grpc.MethodDescriptor.Marshaller { + return object : io.grpc.MethodDescriptor.Marshaller { + override fun stream(value: T): InputStream { + return encode(value).asInputStream() + } + + override fun parse(stream: InputStream): T { + return decode(stream.asSource().buffered()) + } + } +} + +@InternalRpcApi +public actual fun methodDescriptor( + fullMethodName: String, + requestCodec: MessageCodec, + responseCodec: MessageCodec, + type: MethodType, + schemaDescriptor: Any?, + idempotent: Boolean, + safe: Boolean, + sampledToLocalTracing: Boolean, +): MethodDescriptor { + return MethodDescriptor.newBuilder() + .setFullMethodName(fullMethodName) + .setRequestMarshaller(requestCodec.toJvm()) + .setResponseMarshaller(responseCodec.toJvm()) + .setType(type.asJvm) + .setSchemaDescriptor(schemaDescriptor) + .setIdempotent(idempotent) + .setSafe(safe) + .setSampledToLocalTracing(sampledToLocalTracing) + .build() +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.jvm.kt new file mode 100644 index 000000000..b441ba852 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.jvm.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import io.grpc.ServerCall +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual typealias ServerCallHandler = io.grpc.ServerCallHandler + +@InternalRpcApi +public actual typealias ServerCall = ServerCall + +@InternalRpcApi +public actual inline fun serverCallListener( + state: State, + crossinline onMessage: (State, message: Message) -> Unit, + crossinline onHalfClose: (State) -> Unit, + crossinline onCancel: (State) -> Unit, + crossinline onComplete: (State) -> Unit, + crossinline onReady: (State) -> Unit, +): ServerCall.Listener { + return object : ServerCall.Listener() { + override fun onMessage(message: Message) { + onMessage(state, message) + } + + override fun onHalfClose() { + onHalfClose(state) + } + + override fun onCancel() { + onCancel(state) + } + + override fun onComplete() { + onComplete(state) + } + + override fun onReady() { + onReady(state) + } + } +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.jvm.kt new file mode 100644 index 000000000..ca9d37f27 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.jvm.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual typealias ServerMethodDefinition = io.grpc.ServerMethodDefinition + +@InternalRpcApi +public actual fun serverMethodDefinition( + descriptor: MethodDescriptor, + handler: ServerCallHandler, +): ServerMethodDefinition { + return io.grpc.ServerMethodDefinition.create(descriptor, handler) +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.jvm.kt new file mode 100644 index 000000000..b5dfefadc --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.jvm.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual typealias ServiceDescriptor = io.grpc.ServiceDescriptor + +@InternalRpcApi +public actual fun serviceDescriptor( + name: String, + methods: Collection>, + schemaDescriptor: Any?, +): ServiceDescriptor { + return io.grpc.ServiceDescriptor.newBuilder(name) + .apply { + methods.forEach { addMethod(it) } + } + .setSchemaDescriptor(schemaDescriptor) + .build() +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.native.kt new file mode 100644 index 000000000..9cfb2249d --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/GrpcTrailers.native.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +@Suppress(names = ["RedundantConstructorKeyword"]) +public actual class GrpcTrailers actual constructor() { + public actual fun merge(trailers: GrpcTrailers) {} +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt index 41c05518d..7ad772253 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt @@ -6,15 +6,21 @@ package kotlinx.rpc.grpc +import kotlinx.rpc.grpc.internal.GrpcChannel + /** * Same as [ManagedChannel], but is platform-exposed. */ -public actual abstract class ManagedChannelPlatform +public actual abstract class ManagedChannelPlatform : GrpcChannel() /** * Builder class for [ManagedChannel]. */ -public actual abstract class ManagedChannelBuilder> +public actual abstract class ManagedChannelBuilder> { + public actual fun usePlaintext(): T { + TODO("Not yet implemented") + } +} internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel { error("Native target is not supported in gRPC") diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.native.kt index 0ac0a6e29..2d5da178c 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.native.kt @@ -2,11 +2,26 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - package kotlinx.rpc.grpc -/** - * Definition of a service to be exposed via a Server. - */ -public actual class ServerServiceDefinition +import kotlinx.rpc.grpc.internal.ServerMethodDefinition +import kotlinx.rpc.grpc.internal.ServiceDescriptor +import kotlinx.rpc.internal.utils.InternalRpcApi + +public actual class ServerServiceDefinition { + public actual fun getServiceDescriptor(): ServiceDescriptor { + TODO("Not yet implemented") + } + + public actual fun getMethods(): Collection> { + TODO("Not yet implemented") + } +} + +@InternalRpcApi +public actual fun serverServiceDefinition( + serviceDescriptor: ServiceDescriptor, + methods: Collection>, +): ServerServiceDefinition { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/Status.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/Status.native.kt new file mode 100644 index 000000000..bd7103740 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/Status.native.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +public actual class Status { + public actual fun getDescription(): String? { + TODO("Not yet implemented") + } + + public actual fun getCause(): Throwable? { + TODO("Not yet implemented") + } +} + +public actual val Status.code: StatusCode + get() = TODO("Not yet implemented") + +public actual fun Status( + code: StatusCode, + description: String?, + cause: Throwable?, +): Status { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/StatusException.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/StatusException.native.kt new file mode 100644 index 000000000..6c8739a96 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/StatusException.native.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +public actual class StatusException : Exception { + public actual fun getStatus(): Status { + TODO("Not yet implemented") + } + + public actual fun getTrailers(): GrpcTrailers? { + TODO("Not yet implemented") + } + + public actual constructor(status: Status) { + TODO("Not yet implemented") + } + + public actual constructor(status: Status, trailers: GrpcTrailers?) { + TODO("Not yet implemented") + } +} + +public actual class StatusRuntimeException : RuntimeException { + public actual fun getStatus(): Status { + TODO("Not yet implemented") + } + + public actual fun getTrailers(): GrpcTrailers? { + TODO("Not yet implemented") + } + + public actual constructor(status: Status) { + TODO("Not yet implemented") + } + + public actual constructor(status: Status, trailers: GrpcTrailers?) { + TODO("Not yet implemented") + } +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.native.kt deleted file mode 100644 index 38c279a8a..000000000 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.native.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.rpc.grpc - -/** - * Constructor function for the [StatusRuntimeException] class. - */ -public actual fun StatusRuntimeException(status: Status): StatusRuntimeException { - error("Native target is not supported in gRPC") -} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.native.kt new file mode 100644 index 000000000..4f2b24850 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.native.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.grpc.GrpcTrailers +import kotlinx.rpc.grpc.Status +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual abstract class ClientCall { + public actual abstract fun start( + responseListener: Listener, + headers: GrpcTrailers, + ) + + public actual abstract fun request(numMessages: Int) + public actual abstract fun cancel(message: String?, cause: Throwable?) + public actual abstract fun halfClose() + public actual abstract fun sendMessage(message: Request) + public actual open fun isReady(): Boolean { + TODO("Not yet implemented") + } + + @InternalRpcApi + public actual abstract class Listener { + public actual open fun onHeaders(headers: GrpcTrailers) { + } + + public actual open fun onClose(status: Status, trailers: GrpcTrailers) { + } + + public actual open fun onMessage(message: Message) { + } + + public actual open fun onReady() { + } + } +} + +@InternalRpcApi +public actual fun clientCallListener( + onHeaders: (headers: GrpcTrailers) -> Unit, + onMessage: (message: Message) -> Unit, + onClose: (status: Status, trailers: GrpcTrailers) -> Unit, + onReady: () -> Unit, +): ClientCall.Listener { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.native.kt new file mode 100644 index 000000000..28b09cf76 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcCallOptions.native.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual class GrpcCallOptions + +@InternalRpcApi +public actual val GrpcDefaultCallOptions: GrpcCallOptions + get() = TODO("Not yet implemented") diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.native.kt new file mode 100644 index 000000000..3a1e78d32 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.native.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual abstract class GrpcChannel { + public actual abstract fun newCall( + methodDescriptor: MethodDescriptor, + callOptions: GrpcCallOptions, + ): ClientCall + + public actual abstract fun authority(): String +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.native.kt new file mode 100644 index 000000000..076073f08 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcContext.native.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlin.coroutines.CoroutineContext + +internal actual class GrpcContext + +internal actual val CurrentGrpcContext: GrpcContext + get() = TODO("Not yet implemented") + +internal actual class GrpcContextElement : CoroutineContext.Element { + actual override val key: CoroutineContext.Key + get() = TODO("Not yet implemented") + + actual companion object Key : CoroutineContext.Key { + actual fun current(): GrpcContextElement { + TODO("Not yet implemented") + } + } +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.native.kt new file mode 100644 index 000000000..9df3fcd91 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/MethodDescriptor.native.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual abstract class InputStream + +@InternalRpcApi +internal actual val MethodDescriptor<*, *>.type: MethodType + get() = TODO("Not yet implemented") + +@InternalRpcApi +public actual class MethodDescriptor { + public actual fun getFullMethodName(): String { + TODO("Not yet implemented") + } + + public actual fun getServiceName(): String? { + TODO("Not yet implemented") + } + + public actual fun getRequestMarshaller(): Marshaller { + TODO("Not yet implemented") + } + + public actual fun getResponseMarshaller(): Marshaller { + TODO("Not yet implemented") + } + + public actual fun getSchemaDescriptor(): Any? { + TODO("Not yet implemented") + } + + public actual fun isIdempotent(): Boolean { + TODO("Not yet implemented") + } + + public actual fun isSafe(): Boolean { + TODO("Not yet implemented") + } + + public actual fun isSampledToLocalTracing(): Boolean { + TODO("Not yet implemented") + } + + public actual interface Marshaller { + public actual fun stream(value: T): InputStream + public actual fun parse(stream: InputStream): T + } +} + +@InternalRpcApi +public actual fun methodDescriptor( + fullMethodName: String, + requestCodec: MessageCodec, + responseCodec: MessageCodec, + type: MethodType, + schemaDescriptor: Any?, + idempotent: Boolean, + safe: Boolean, + sampledToLocalTracing: Boolean, +): MethodDescriptor { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.native.kt new file mode 100644 index 000000000..29b06aede --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServerCall.native.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.grpc.GrpcTrailers +import kotlinx.rpc.grpc.Status +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual fun interface ServerCallHandler { + public actual fun startCall( + call: ServerCall, + headers: GrpcTrailers, + ): ServerCall.Listener +} + +@InternalRpcApi +public actual abstract class ServerCall { + public actual abstract fun request(numMessages: Int) + public actual abstract fun sendHeaders(headers: GrpcTrailers) + public actual abstract fun sendMessage(message: Response) + public actual abstract fun close(status: Status, trailers: GrpcTrailers) + + public actual open fun isReady(): Boolean { + TODO("Not yet implemented") + } + + public actual abstract fun isCancelled(): Boolean + public actual abstract fun getMethodDescriptor(): MethodDescriptor + + @InternalRpcApi + public actual abstract class Listener { + public actual open fun onMessage(message: Request) {} + public actual open fun onHalfClose() {} + public actual open fun onCancel() {} + public actual open fun onComplete() {} + public actual open fun onReady() {} + } +} + +@InternalRpcApi +public actual fun serverCallListener( + state: State, + onMessage: (State, message: Message) -> Unit, + onHalfClose: (State) -> Unit, + onCancel: (State) -> Unit, + onComplete: (State) -> Unit, + onReady: (State) -> Unit, +): ServerCall.Listener { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.native.kt new file mode 100644 index 000000000..3da552b02 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServerMethodDefinition.native.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual class ServerMethodDefinition { + public actual fun getMethodDescriptor(): MethodDescriptor { + TODO("Not yet implemented") + } + + public actual fun getServerCallHandler(): ServerCallHandler { + TODO("Not yet implemented") + } +} + +public actual fun serverMethodDefinition( + descriptor: MethodDescriptor, + handler: ServerCallHandler, +): ServerMethodDefinition { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.native.kt new file mode 100644 index 000000000..bf2415a08 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ServiceDescriptor.native.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi +public actual class ServiceDescriptor { + public actual fun getName(): String { + TODO("Not yet implemented") + } + + public actual fun getMethods(): Collection> { + TODO("Not yet implemented") + } + + public actual fun getSchemaDescriptor(): Any? { + TODO("Not yet implemented") + } +} + +@InternalRpcApi +public actual fun serviceDescriptor( + name: String, + methods: Collection>, + schemaDescriptor: Any?, +): ServiceDescriptor { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.wasmJs.kt deleted file mode 100644 index 95f9e22af..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/StatusRuntimeException.wasmJs.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.rpc.grpc - -/** - * Constructor function for the [StatusRuntimeException] class. - */ -public actual fun StatusRuntimeException(status: Status): StatusRuntimeException { - error("WasmJS target is not supported in gRPC") -}