From 49db8e18778ff7bac2a5e0f0268d28e43963f4cc Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Tue, 29 Jul 2025 16:14:27 +0200 Subject: [PATCH 1/6] grpc-native: First message generation Signed-off-by: Johannes Zottele --- .../rpc/proto/DefaultProtoSourceSet.kt | 8 +- .../kotlinx/rpc/grpc/{internal => pb}/KTag.kt | 12 +- .../kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt | 87 ++++ .../kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt | 56 +++ .../rpc/grpc/{internal => pb}/WireSize.kt | 2 +- .../rpc/grpc/{internal => pb}/readPacked.kt | 0 .../rpc/grpc/internal/WireCodecTest.kt | 2 + .../grpc/{internal => pb}/WireDecoder.js.kt | 3 +- .../grpc/{internal => pb}/WireEncoder.js.kt | 2 +- .../rpc/grpc/{internal => pb}/WireSize.js.kt | 2 +- .../grpc/{internal => pb}/readPacked.js.kt | 0 .../grpc/{internal => pb}/WireDecoder.jvm.kt | 2 +- .../grpc/{internal => pb}/WireEncoder.jvm.kt | 2 +- .../rpc/grpc/{internal => pb}/WireSize.jvm.kt | 2 +- .../grpc/{internal => pb}/readPacked.jvm.kt | 0 .../{internal => pb}/WireDecoder.native.kt | 10 +- .../{internal => pb}/WireEncoder.native.kt | 3 +- .../grpc/{internal => pb}/WireSize.native.kt | 2 +- .../kotlinx/rpc/grpc/pb/TestAllPrimitive.kt | 38 ++ .../grpc-core/src/nativeTest/proto/mini.proto | 39 ++ .../{internal => pb}/WireDecoder.wasmJs.kt | 3 +- .../{internal => pb}/WireEncoder.wasmJs.kt | 2 +- .../grpc/{internal => pb}/WireSize.wasmJs.kt | 2 +- .../{internal => pb}/readPacked.wasmJs.kt | 0 .../kotlinx/rpc/protobuf/CodeGenerator.kt | 29 +- .../protobuf/ModelToKotlinCommonGenerator.kt | 395 ++++++++++++++++++ ...erator.kt => ModelToKotlinJvmGenerator.kt} | 19 +- .../rpc/protobuf/ProtoToModelInterpreter.kt | 6 + .../kotlinx/rpc/protobuf/RpcProtobufPlugin.kt | 2 +- .../rpc/protobuf/model/FieldDeclaration.kt | 51 ++- 30 files changed, 724 insertions(+), 57 deletions(-) rename grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/KTag.kt (78%) create mode 100644 grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt create mode 100644 grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt rename grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireSize.kt (97%) rename grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/readPacked.kt (100%) rename grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireDecoder.js.kt (80%) rename grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireEncoder.js.kt (87%) rename grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireSize.js.kt (95%) rename grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/readPacked.js.kt (100%) rename grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireDecoder.jvm.kt (99%) rename grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireEncoder.jvm.kt (99%) rename grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireSize.jvm.kt (97%) rename grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/readPacked.jvm.kt (100%) rename grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireDecoder.native.kt (98%) rename grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireEncoder.native.kt (99%) rename grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireSize.native.kt (95%) create mode 100644 grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestAllPrimitive.kt create mode 100644 grpc/grpc-core/src/nativeTest/proto/mini.proto rename grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireDecoder.wasmJs.kt (80%) rename grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireEncoder.wasmJs.kt (87%) rename grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireSize.wasmJs.kt (95%) rename grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/{internal => pb}/readPacked.wasmJs.kt (100%) create mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt rename protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/{ModelToKotlinGenerator.kt => ModelToKotlinJvmGenerator.kt} (97%) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt index fc33bca6e..93f17d4e7 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt @@ -8,11 +8,7 @@ import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.util.findOrCreate import kotlinx.rpc.util.withKotlinJvmExtension import kotlinx.rpc.util.withKotlinKmpExtension -import org.gradle.api.Action -import org.gradle.api.GradleException -import org.gradle.api.NamedDomainObjectFactory -import org.gradle.api.NamedDomainObjectProvider -import org.gradle.api.Project +import org.gradle.api.* import org.gradle.api.file.SourceDirectorySet import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property @@ -104,7 +100,7 @@ internal fun Project.createProtoExtensions() { findOrCreateAndConfigure("jvmTest", null) sourceSets.configureEach { - if (name == "jvmMain" || name == "jvmTest") { + if (name == "jvmMain" || name == "jvmTest" || name == "nativeTest") { findOrCreateAndConfigure(name, this) } } diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/KTag.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt similarity index 78% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/KTag.kt rename to grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt index cd9102250..24b331fbf 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/KTag.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt @@ -2,11 +2,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.internal +package kotlinx.rpc.grpc.pb -import kotlinx.rpc.grpc.internal.KTag.Companion.K_TAG_TYPE_BITS - -internal enum class WireType { +public enum class WireType { VARINT, // 0 FIXED64, // 1 LENGTH_DELIMITED, // 2 @@ -15,13 +13,13 @@ internal enum class WireType { FIXED32, // 5 } -internal data class KTag(val fieldNr: Int, val wireType: WireType) { +public data class KTag(val fieldNr: Int, val wireType: WireType) { init { check(isValidFieldNr(fieldNr)) { "Invalid field number: $fieldNr" } } - companion object { + internal companion object { // Number of bits in a tag which identify the wire type. const val K_TAG_TYPE_BITS: Int = 3; @@ -31,7 +29,7 @@ internal data class KTag(val fieldNr: Int, val wireType: WireType) { } internal fun KTag.toRawKTag(): UInt { - return (fieldNr.toUInt() shl K_TAG_TYPE_BITS) or wireType.ordinal.toUInt() + return (fieldNr.toUInt() shl KTag.Companion.K_TAG_TYPE_BITS) or wireType.ordinal.toUInt() } internal fun KTag.Companion.fromOrNull(rawKTag: UInt): KTag? { diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt new file mode 100644 index 000000000..7ef050946 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt @@ -0,0 +1,87 @@ +/* + * 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.pb + +import kotlinx.io.Buffer +import kotlinx.rpc.internal.utils.InternalRpcApi + +/** + * A platform-specific decoder for wire format data. + * + * This decoder is used by first calling [readTag], than looking up the field based on the field number in the returned, + * tag and then calling the actual `read*()` method to read the value to the corresponding field. + * + * [hadError] indicates an error during decoding. While calling `read*()` is safe, the returned values + * are meaningless if [hadError] returns `true`. + * + * NOTE: If the [hadError] after a call to `read*()` returns `false`, it doesn't mean that the + * value is correctly decoded. E.g., the following test will pass: + * ```kt + * val fieldNr = 1 + * val buffer = Buffer() + * + * val encoder = WireEncoder(buffer) + * assertTrue(encoder.writeInt32(fieldNr, 12312)) + * encoder.flush() + * + * WireDecoder(buffer).use { decoder -> + * decoder.readTag() + * decoder.readBool() + * assertFalse(decoder.hasError()) + * } + * ``` + */ +@InternalRpcApi +public interface WireDecoder : AutoCloseable { + public fun hadError(): Boolean + + /** + * When the read tag is null, it indicates EOF and the parse may stop at this point. + */ + public fun readTag(): KTag? + public fun readBool(): Boolean + public fun readInt32(): Int + public fun readInt64(): Long + public fun readUInt32(): UInt + public fun readUInt64(): ULong + public fun readSInt32(): Int + public fun readSInt64(): Long + public fun readFixed32(): UInt + public fun readFixed64(): ULong + public fun readSFixed32(): Int + public fun readSFixed64(): Long + public fun readFloat(): Float + public fun readDouble(): Double + + public fun readEnum(): Int + public fun readString(): String + public fun readBytes(): ByteArray + public fun readPackedBool(): List + public fun readPackedInt32(): List + public fun readPackedInt64(): List + public fun readPackedSInt32(): List + public fun readPackedSInt64(): List + public fun readPackedUInt32(): List + public fun readPackedUInt64(): List + public fun readPackedFixed32(): List + public fun readPackedFixed64(): List + public fun readPackedSFixed32(): List + public fun readPackedSFixed64(): List + public fun readPackedFloat(): List + public fun readPackedDouble(): List + public fun readPackedEnum(): List +} + +/** + * Creates a platform-specific [WireDecoder]. + * + * This constructor takes a [Buffer] instead of a [kotlinx.io.Source] because + * the native implementation (`WireDecoderNative`) depends on [Buffer]'s internal structure. + * + * NOTE: Do not use the [source] buffer while the [WireDecoder] is still open. + * + * @param source The buffer containing the encoded wire-format data. + */ +internal expect fun WireDecoder(source: Buffer): WireDecoder \ No newline at end of file diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt new file mode 100644 index 000000000..d96164932 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt @@ -0,0 +1,56 @@ +/* + * 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.pb + +import kotlinx.io.Sink +import kotlinx.rpc.internal.utils.InternalRpcApi + +/** + * A platform-specific class that encodes values into protobuf's wire format. + * + * If one `write*()` method returns false, the encoding of the value failed + * and no further encodings can be performed on this [WireEncoder]. + * + * [flush] must be called to ensure that all data is written to the [Sink]. + */ +@InternalRpcApi +@OptIn(ExperimentalUnsignedTypes::class) +public interface WireEncoder { + public fun flush() + public fun writeBool(field: Int, value: Boolean): Boolean + public fun writeInt32(fieldNr: Int, value: Int): Boolean + public fun writeInt64(fieldNr: Int, value: Long): Boolean + public fun writeUInt32(fieldNr: Int, value: UInt): Boolean + public fun writeUInt64(fieldNr: Int, value: ULong): Boolean + public fun writeSInt32(fieldNr: Int, value: Int): Boolean + public fun writeSInt64(fieldNr: Int, value: Long): Boolean + public fun writeFixed32(fieldNr: Int, value: UInt): Boolean + public fun writeFixed64(fieldNr: Int, value: ULong): Boolean + public fun writeSFixed32(fieldNr: Int, value: Int): Boolean + public fun writeSFixed64(fieldNr: Int, value: Long): Boolean + public fun writeFloat(fieldNr: Int, value: Float): Boolean + public fun writeDouble(fieldNr: Int, value: Double): Boolean + public fun writeEnum(fieldNr: Int, value: Int): Boolean + public fun writeBytes(fieldNr: Int, value: ByteArray): Boolean + public fun writeString(fieldNr: Int, value: String): Boolean + public fun writePackedBool(fieldNr: Int, value: List, fieldSize: Int): Boolean + public fun writePackedInt32(fieldNr: Int, value: List, fieldSize: Int): Boolean + public fun writePackedInt64(fieldNr: Int, value: List, fieldSize: Int): Boolean + public fun writePackedUInt32(fieldNr: Int, value: List, fieldSize: Int): Boolean + public fun writePackedUInt64(fieldNr: Int, value: List, fieldSize: Int): Boolean + public fun writePackedSInt32(fieldNr: Int, value: List, fieldSize: Int): Boolean + public fun writePackedSInt64(fieldNr: Int, value: List, fieldSize: Int): Boolean + public fun writePackedFixed32(fieldNr: Int, value: List): Boolean + public fun writePackedFixed64(fieldNr: Int, value: List): Boolean + public fun writePackedSFixed32(fieldNr: Int, value: List): Boolean + public fun writePackedSFixed64(fieldNr: Int, value: List): Boolean + public fun writePackedFloat(fieldNr: Int, value: List): Boolean + public fun writePackedDouble(fieldNr: Int, value: List): Boolean + public fun writePackedEnum(fieldNr: Int, value: List, fieldSize: Int): Boolean = + writePackedInt32(fieldNr, value, fieldSize) +} + + +internal expect fun WireEncoder(sink: Sink): WireEncoder \ No newline at end of file diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt similarity index 97% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.kt rename to grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt index 161a4fe92..0b1d7e25a 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.pb internal object WireSize diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.kt similarity index 100% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt rename to grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.kt diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt index 577b90c42..896382a11 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt @@ -5,6 +5,8 @@ package kotlinx.rpc.grpc.internal import kotlinx.io.Buffer +import kotlinx.rpc.grpc.pb.* +import kotlin.experimental.ExperimentalNativeApi import kotlin.test.* enum class TestPlatform { diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.js.kt similarity index 80% rename from grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.js.kt rename to grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.js.kt index f56885157..4cff6ef95 100644 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.js.kt +++ b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.js.kt @@ -2,10 +2,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.internal +package kotlinx.rpc.grpc.pb import kotlinx.io.Buffer -import kotlinx.io.Source internal actual fun WireDecoder(source: Buffer): WireDecoder { TODO("Not yet implemented") diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.js.kt similarity index 87% rename from grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.js.kt rename to grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.js.kt index 00c0b3246..733950eba 100644 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.js.kt +++ b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.js.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.pb import kotlinx.io.Sink diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt similarity index 95% rename from grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.js.kt rename to grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt index e70b9bd00..251566247 100644 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.js.kt +++ b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.pb internal actual fun WireSize.int32(value: Int): UInt { TODO("Not yet implemented") diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.js.kt similarity index 100% rename from grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt rename to grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.js.kt diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt similarity index 99% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.jvm.kt rename to grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt index f8d5def10..c1b25290d 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.pb import com.google.protobuf.CodedInputStream import kotlinx.io.Buffer diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.jvm.kt similarity index 99% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.jvm.kt rename to grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.jvm.kt index e8542ac89..00fb1f869 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.jvm.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.pb import com.google.protobuf.ByteString import com.google.protobuf.CodedOutputStream diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt similarity index 97% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt rename to grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt index 3697c3d91..332d4847a 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.pb import com.google.protobuf.CodedOutputStream.computeInt32SizeNoTag import com.google.protobuf.CodedOutputStream.computeInt64SizeNoTag diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.jvm.kt similarity index 100% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt rename to grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.jvm.kt diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt similarity index 98% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.native.kt rename to grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt index dac16a33c..00cba88fb 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt @@ -2,11 +2,12 @@ * 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 +package kotlinx.rpc.grpc.pb import kotlinx.cinterop.* import kotlinx.collections.immutable.persistentListOf import kotlinx.io.Buffer +import kotlinx.rpc.grpc.internal.ZeroCopyInputSource import libprotowire.* import kotlin.experimental.ExperimentalNativeApi import kotlin.math.min @@ -65,12 +66,7 @@ internal class WireDecoderNative(private val source: Buffer) : WireDecoder { override fun readTag(): KTag? { val tag = pw_decoder_read_tag(raw) - if (tag == 0u) return null.withError() - val kTag = KTag.fromOrNull(tag) - if (kTag == null) { - hadError = true - } - return kTag + return KTag.fromOrNull(tag) } override fun readBool(): Boolean = memScoped { diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.native.kt similarity index 99% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.native.kt rename to grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.native.kt index 9fd4b22a2..261a8b281 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.native.kt @@ -2,10 +2,11 @@ * 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 +package kotlinx.rpc.grpc.pb import kotlinx.cinterop.* import kotlinx.io.Sink +import kotlinx.rpc.grpc.internal.writeFully import libprotowire.* import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt similarity index 95% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.native.kt rename to grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt index 432479129..cbbcaf18d 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt @@ -4,7 +4,7 @@ @file:OptIn(ExperimentalForeignApi::class) -package kotlinx.rpc.grpc.internal +package kotlinx.rpc.grpc.pb import kotlinx.cinterop.ExperimentalForeignApi import libprotowire.* diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestAllPrimitive.kt b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestAllPrimitive.kt new file mode 100644 index 000000000..837884880 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestAllPrimitive.kt @@ -0,0 +1,38 @@ +/* + * 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.pb + +import kotlinx.io.Buffer +import kotlinx.rpc.grpc.test.PrimitiveTest +import kotlinx.rpc.grpc.test.decodeWith +import kotlinx.rpc.grpc.test.encodeWith +import kotlinx.rpc.grpc.test.invoke +import kotlin.test.Test +import kotlin.test.assertEquals + +class TestAllPrimitive { + + + @Test + fun testAllPrimitiveProto() { + val msg = PrimitiveTest { + double = 3.0 + int32Opt = 12 + } + + val buffer = Buffer() + val encoder = WireEncoder(buffer) + + msg.encodeWith(encoder) + encoder.flush() + + val decoded = WireDecoder(buffer).use { + PrimitiveTest.decodeWith(it) + } + + assertEquals(msg.double, decoded?.double) + assertEquals(msg.int32Opt, decoded?.int32Opt) + } +} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeTest/proto/mini.proto b/grpc/grpc-core/src/nativeTest/proto/mini.proto new file mode 100644 index 000000000..472caae0a --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/mini.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +option optimize_for = LITE_RUNTIME; + +message PrimitiveTest { + double double = 1; + float float = 2; + int32 int32 = 3; + int64 int64 = 4; + uint32 uint32 = 5; + uint64 uint64 = 6; + sint32 sint32 = 7; + sint64 sint64 = 8; + fixed32 fixed32 = 9; + fixed64 fixed64 = 10; + sfixed32 sfixed32 = 11; + sfixed64 sfixed64 = 12; + bool bool = 13; + string string = 14; + bytes bytes = 15; + optional double doubleOpt = 111; + optional float floatOpt = 112; + optional int32 int32Opt = 113; + optional int64 int64Opt = 114; + optional uint32 uint32Opt = 115; + optional uint64 uint64Opt = 116; + optional sint32 sint32Opt = 117; + optional sint64 sint64Opt = 118; + optional fixed32 fixed32Opt = 119; + optional fixed64 fixed64Opt = 1110; + optional sfixed32 sfixed32Opt = 1111; + optional sfixed64 sfixed64Opt = 1112; + optional bool boolOpt = 1113; + optional string stringOpt = 1114; + optional bytes bytesOpt = 1115; +} + diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.wasmJs.kt similarity index 80% rename from grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.wasmJs.kt rename to grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.wasmJs.kt index f56885157..4cff6ef95 100644 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.wasmJs.kt +++ b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.wasmJs.kt @@ -2,10 +2,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.internal +package kotlinx.rpc.grpc.pb import kotlinx.io.Buffer -import kotlinx.io.Source internal actual fun WireDecoder(source: Buffer): WireDecoder { TODO("Not yet implemented") diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.wasmJs.kt similarity index 87% rename from grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.wasmJs.kt rename to grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.wasmJs.kt index 00c0b3246..733950eba 100644 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.wasmJs.kt +++ b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.wasmJs.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.pb import kotlinx.io.Sink diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt similarity index 95% rename from grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.wasmJs.kt rename to grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt index e70b9bd00..251566247 100644 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.wasmJs.kt +++ b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.pb internal actual fun WireSize.int32(value: Int): UInt { TODO("Not yet implemented") diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.wasmJs.kt similarity index 100% rename from grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt rename to grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.wasmJs.kt diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt index 4969e5392..076c924a4 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt @@ -88,10 +88,37 @@ open class CodeGenerator( ifBlock: (CodeGenerator.() -> Unit), elseBlock: (CodeGenerator.() -> Unit)? = null, ) { - scope("${prefix}if ($condition)", nlAfterClosed = false, suffix = if (elseBlock != null) " else" else "", block = ifBlock) + scope( + "${prefix}if ($condition)", + nlAfterClosed = false, + suffix = if (elseBlock != null) " else" else "", + block = ifBlock + ) scopeWithSuffix(block = elseBlock) } + internal fun whileBlock( + condition: String, + block: (CodeGenerator.() -> Unit), + ) { + scope("while ($condition)", block = block) + } + + internal fun whenBlock( + condition: String, + block: (CodeGenerator.() -> Unit), + ) { + scope("when ($condition)", block = block) + } + + internal fun whenCase( + condition: String, + block: (CodeGenerator.() -> Unit), + ) { + scope("$condition ->", block = block) + } + + private fun scopeWithSuffix( suffix: String = "", openingBracket: Boolean = true, diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt new file mode 100644 index 000000000..167e1fb32 --- /dev/null +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt @@ -0,0 +1,395 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("detekt.all") + +package kotlinx.rpc.protobuf + +import kotlinx.rpc.protobuf.CodeGenerator.DeclarationType +import kotlinx.rpc.protobuf.model.* +import org.slf4j.Logger + +private const val RPC_INTERNAL_PACKAGE_SUFFIX = "_rpc_internal" + +class ModelToKotlinCommonGenerator( + private val model: Model, + private val logger: Logger, + private val codeGenerationParameters: CodeGenerationParameters, +) { + fun generateKotlinFiles(): List { + return model.files.flatMap { it.generateKotlinFiles() } + } + + private fun FileDeclaration.generateKotlinFiles(): List { + additionalPublicImports.clear() + additionalInternalImports.clear() + + return listOf( + generatePublicKotlinFile(), + generateInternalKotlinFile(), + ) + } + + private var currentPackage: FqName = FqName.Package.Root + + private fun FileDeclaration.generatePublicKotlinFile(): FileGenerator { + currentPackage = packageName + + return file(codeGenerationParameters, logger = logger) { + filename = this@generatePublicKotlinFile.name + packageName = this@generatePublicKotlinFile.packageName.safeFullName() + packagePath = this@generatePublicKotlinFile.packageName.safeFullName() + + dependencies.forEach { dependency -> + importPackage(dependency.packageName.safeFullName()) + } + + fileOptIns = listOf("ExperimentalRpcApi::class", "InternalRpcApi::class") + + generatePublicDeclaredEntities(this@generatePublicKotlinFile) + + import("kotlinx.rpc.internal.utils.*") + import("kotlinx.coroutines.flow.*") + + additionalPublicImports.forEach { + import(it) + } + } + } + + private fun FileDeclaration.generateInternalKotlinFile(): FileGenerator { + currentPackage = packageName + + return file(codeGenerationParameters, logger = logger) { + filename = this@generateInternalKotlinFile.name + packageName = this@generateInternalKotlinFile.packageName.safeFullName() + packagePath = + this@generateInternalKotlinFile.packageName.safeFullName() + .packageNameSuffixed(RPC_INTERNAL_PACKAGE_SUFFIX) + + fileOptIns = listOf("ExperimentalRpcApi::class", "InternalRpcApi::class") + + dependencies.forEach { dependency -> + importPackage(dependency.packageName.safeFullName()) + } + + generateInternalDeclaredEntities(this@generateInternalKotlinFile) + + import("kotlinx.rpc.internal.utils.*") + import("kotlinx.coroutines.flow.*") + import("kotlinx.rpc.grpc.WireDecoder") + import("kotlinx.rpc.grpc.WireEncoder") + import("kotlinx.rpc.grpc.WireType") + + + additionalInternalImports.forEach { + import(it) + } + } + } + + private val additionalPublicImports = mutableSetOf() + private val additionalInternalImports = mutableSetOf() + + private fun CodeGenerator.generatePublicDeclaredEntities(fileDeclaration: FileDeclaration) { + fileDeclaration.messageDeclarations.forEach { generatePublicMessage(it) } + fileDeclaration.enumDeclarations.forEach { generatePublicEnum(it) } + fileDeclaration.serviceDeclarations.forEach { generatePublicService(it) } + } + + private fun CodeGenerator.generateInternalDeclaredEntities(fileDeclaration: FileDeclaration) { + fileDeclaration.messageDeclarations.forEach { generateInternalMessage(it) } + + fileDeclaration.messageDeclarations.forEach { + generateMessageConstructor(it) + generateMessageDecoder(it) + generateMessageEncoder(it) + } + } + + private fun MessageDeclaration.fields() = actualFields.map { + it.transformToFieldDeclaration() to it + } + + @Suppress("detekt.CyclomaticComplexMethod") + private fun CodeGenerator.generatePublicMessage(declaration: MessageDeclaration) { + clazz( + name = declaration.name.simpleName, + declarationType = DeclarationType.Interface, + ) { + declaration.fields().forEach { (fieldDeclaration, _) -> + code("val $fieldDeclaration") + newLine() + } + + newLine() + + declaration.oneOfDeclarations.forEach { oneOf -> + generateOneOfPublic(oneOf) + } + + declaration.nestedDeclarations.forEach { nested -> + generatePublicMessage(nested) + } + + declaration.enumDeclarations.forEach { enum -> + generatePublicEnum(enum) + } + + clazz("", modifiers = "companion", declarationType = DeclarationType.Object) + } + } + + @Suppress("detekt.CyclomaticComplexMethod") + private fun CodeGenerator.generateInternalMessage(declaration: MessageDeclaration) { + clazz( + name = "${declaration.name.simpleName}Builder", + declarationType = DeclarationType.Class, + superTypes = listOf(declaration.name.safeFullName()), + ) { + declaration.fields().forEach { (fieldDeclaration, field) -> + val value = when { + field.nullable -> { + "= null" + } + + field.type is FieldType.Reference -> { + additionalInternalImports.add("kotlin.properties.Delegates") + "by Delegates.notNull()" + } + + else -> { + "= ${field.type.defaultValue}" + } + } + + code("override var $fieldDeclaration $value") + newLine() + } + + declaration.nestedDeclarations.forEach { nested -> + generateInternalMessage(nested) + } + } + } + + private fun CodeGenerator.generateMessageConstructor(declaration: MessageDeclaration) = function( + name = "invoke", + modifiers = "operator", + args = "body: ${declaration.name.safeFullName("Builder")}.() -> Unit", + contextReceiver = "${declaration.name.safeFullName()}.Companion", + returnType = declaration.name.safeFullName(), + ) { + code("return ${declaration.name.safeFullName("Builder")}().apply(body)") + } + + private fun CodeGenerator.generateMessageDecoder(declaration: MessageDeclaration) = function( + name = "decodeWith", + args = "decoder: WireDecoder", + contextReceiver = "${declaration.name.safeFullName()}.Companion", + returnType = "${declaration.name.safeFullName()}?" + ) { + code("val msg = ${declaration.name.safeFullName("Builder")}()") + whileBlock("!decoder.hadError()") { + code("val tag = decoder.readTag() ?: break // EOF, we read the whole message") + whenBlock("tag.fieldNr") { + declaration.fields().forEach { (_, field) -> + whenCase("${field.number} if tag.wireType == WireType.${field.type.wireType.name}") { + code("msg.${field.name} = decoder.read${field.type.decodeEncodeFuncName()}()") + } + } + whenCase("else") { code("TODO(\"Handle unknown fields\")") } + } + } + ifBranch( + condition = "decoder.hadError()", + ifBlock = { code("return null") } + ) + + // TODO: Make a lists immutable + code("return msg") + } + + private fun CodeGenerator.generateMessageEncoder(declaration: MessageDeclaration) = function( + name = "encodeWith", + args = "encoder: WireEncoder", + contextReceiver = declaration.name.safeFullName(), + ) { + + declaration.fields().forEach { (_, field) -> + val encFuncName = field.type.decodeEncodeFuncName() + val fieldNr = field.number + val fieldName = field.name + if (field.nullable) { + scope("$fieldName?.also") { + code("encoder.write$encFuncName($fieldNr, it)") + } + } else { + code("encoder.write$encFuncName($fieldNr, $fieldName)") + } + } + } + + private fun FieldType.decodeEncodeFuncName(): String = when (this) { + FieldType.IntegralType.STRING -> "String" + FieldType.IntegralType.BYTES -> "Bytes" + FieldType.IntegralType.BOOL -> "Bool" + FieldType.IntegralType.FLOAT -> "Float" + FieldType.IntegralType.DOUBLE -> "Double" + FieldType.IntegralType.INT32 -> "Int32" + FieldType.IntegralType.INT64 -> "Int64" + FieldType.IntegralType.UINT32 -> "UInt32" + FieldType.IntegralType.UINT64 -> "UInt64" + FieldType.IntegralType.FIXED32 -> "Fixed32" + FieldType.IntegralType.FIXED64 -> "Fixed64" + FieldType.IntegralType.SINT32 -> "SInt32" + FieldType.IntegralType.SINT64 -> "SInt64" + FieldType.IntegralType.SFIXED32 -> "SFixed32" + FieldType.IntegralType.SFIXED64 -> "SFixed64" + is FieldType.List -> "Packed${value.decodeEncodeFuncName()}" + is FieldType.Map -> error("No encoding/decoding function for map types") + is FieldType.OneOf -> error("No encoding/decoding function for oneOf types") + is FieldType.Reference -> error("No encoding/decoding function for sub message types") + } + + private fun FieldDeclaration.transformToFieldDeclaration(): String { + return "${name}: ${typeFqName()}" + } + + private fun FieldDeclaration.typeFqName(): String { + return when (type) { + is FieldType.Reference -> { + val value by type.value + value.safeFullName() + } + + is FieldType.OneOf -> { + val value by type.value + value.safeFullName() + } + + is FieldType.IntegralType -> { + type.fqName.simpleName + } + + is FieldType.List -> { + val fqValue = when (val value = type.value) { + is FieldType.Reference -> value.value.value + is FieldType.IntegralType -> value.fqName + else -> error("Unsupported type: $value") + } + + "List<${fqValue.safeFullName()}>" + } + + is FieldType.Map -> { + val entry by type.entry + + val fqKey = when (val key = entry.key) { + is FieldType.Reference -> key.value.value + is FieldType.IntegralType -> key.fqName + else -> error("Unsupported type: $key") + } + + val fqValue = when (val value = entry.value) { + is FieldType.Reference -> value.value.value + is FieldType.IntegralType -> value.fqName + else -> error("Unsupported type: $value") + } + + "Map<${fqKey.safeFullName()}, ${fqValue.safeFullName()}>" + } + }.withNullability(nullable) + } + + private fun String.withNullability(nullable: Boolean): String { + return "$this${if (nullable) "?" else ""}" + } + + private fun CodeGenerator.generateOneOfPublic(declaration: OneOfDeclaration) { + val interfaceName = declaration.name.simpleName + + clazz(interfaceName, "sealed", declarationType = DeclarationType.Interface) { + declaration.variants.forEach { variant -> + clazz( + name = variant.name, + modifiers = "value", + constructorArgs = listOf("val value: ${variant.typeFqName()}"), + annotations = listOf("@JvmInline"), + superTypes = listOf(interfaceName), + ) + + additionalPublicImports.add("kotlin.jvm.JvmInline") + } + } + } + + private fun CodeGenerator.generatePublicEnum(declaration: EnumDeclaration) { + clazz(declaration.name.simpleName, modifiers = "enum") { + declaration.originalEntries.forEach { entry -> + code("${entry.name.simpleName},") + newLine() + } + code(";") + newLine() + + if (declaration.aliases.isNotEmpty()) { + newLine() + + clazz("", modifiers = "companion", declarationType = DeclarationType.Object) { + declaration.aliases.forEach { alias: EnumDeclaration.Alias -> + code( + "val ${alias.name.simpleName}: ${declaration.name.simpleName} " + + "= ${alias.original.name.simpleName}" + ) + } + } + } + } + } + + @Suppress("detekt.LongMethod") + private fun CodeGenerator.generatePublicService(service: ServiceDeclaration) { + code("@kotlinx.rpc.grpc.annotations.Grpc") + clazz(service.name.simpleName, declarationType = DeclarationType.Interface) { + service.methods.forEach { method -> + val inputType by method.inputType + val outputType by method.outputType + function( + name = method.name, + modifiers = if (method.serverStreaming) "" else "suspend", + args = "message: ${inputType.name.safeFullName().wrapInFlowIf(method.clientStreaming)}", + returnType = outputType.name.safeFullName().wrapInFlowIf(method.serverStreaming), + ) + } + } + } + + private fun String.wrapInFlowIf(condition: Boolean): String { + return if (condition) "Flow<$this>" else this + } + + private fun FqName.safeFullName(classSuffix: String = ""): String { + importRootDeclarationIfNeeded(this) + + return fullName(classSuffix) + } + + private fun importRootDeclarationIfNeeded( + declaration: FqName, + nameToImport: String = declaration.simpleName, + internalOnly: Boolean = false, + ) { + if (declaration.parent == FqName.Package.Root && currentPackage != FqName.Package.Root && nameToImport.isNotBlank()) { + additionalInternalImports.add(nameToImport) + if (!internalOnly) { + additionalPublicImports.add(nameToImport) + } + } + } +} + +private fun String.packageNameSuffixed(suffix: String): String { + return if (isEmpty()) suffix else "$this.$suffix" +} diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinJvmGenerator.kt similarity index 97% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt rename to protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinJvmGenerator.kt index ad392f394..48083b26e 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinJvmGenerator.kt @@ -9,11 +9,10 @@ package kotlinx.rpc.protobuf import kotlinx.rpc.protobuf.CodeGenerator.DeclarationType import kotlinx.rpc.protobuf.model.* import org.slf4j.Logger -import kotlin.getValue private const val RPC_INTERNAL_PACKAGE_SUFFIX = "_rpc_internal" -class ModelToKotlinGenerator( +class ModelToKotlinJvmGenerator( private val model: Model, private val logger: Logger, private val codeGenerationParameters: CodeGenerationParameters, @@ -250,7 +249,13 @@ class ModelToKotlinGenerator( scope("${field.name} = when") { oneOf.variants.forEach { variant -> - code("${hasFieldJavaMethod(variant)} -> $oneOfName.${variant.name}(this@toKotlin.${fieldJavaName(variant)}${variant.type.toKotlinCast()})") + code( + "${hasFieldJavaMethod(variant)} -> $oneOfName.${variant.name}(this@toKotlin.${ + fieldJavaName( + variant + ) + }${variant.type.toKotlinCast()})" + ) } code("else -> null") } @@ -268,6 +273,7 @@ class ModelToKotlinGenerator( } ) } + else -> { code("${field.name} = $getter") } @@ -286,7 +292,8 @@ class ModelToKotlinGenerator( } } - private fun hasFieldJavaMethod(field: FieldDeclaration): String = "has${field.name.replaceFirstChar { ch -> ch.uppercase() }}()" + private fun hasFieldJavaMethod(field: FieldDeclaration): String = + "has${field.name.replaceFirstChar { ch -> ch.uppercase() }}()" private fun setFieldCall(field: FieldDeclaration): String { val uppercaseName = field.name.replaceFirstChar { ch -> ch.uppercase() } @@ -309,6 +316,7 @@ class ModelToKotlinGenerator( val fq by value importRootDeclarationIfNeeded(fq, "toPlatform", true) } + is FieldType.List -> ".map { it${value.toPlatformCast()} }" is FieldType.Map -> { @@ -332,6 +340,7 @@ class ModelToKotlinGenerator( val fq by value importRootDeclarationIfNeeded(fq, "toKotlin", true) } + is FieldType.List -> ".map { it${value.toKotlinCast()} }" is FieldType.Map -> { @@ -681,6 +690,6 @@ class ModelToKotlinGenerator( } } -internal fun String.packageNameSuffixed(suffix: String): String { +private fun String.packageNameSuffixed(suffix: String): String { return if (isEmpty()) suffix else "$this.$suffix" } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt index c317e77a3..f101d1bd2 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt @@ -172,19 +172,23 @@ class ProtoToModelInterpreter( return FieldDeclaration( name = oneOfName.removePrefix("_").fullProtoNameToKotlin(), + number = number, type = fieldType, nullable = true, deprecated = options.deprecated, doc = null, + proto = this, ) } return FieldDeclaration( name = name.fullProtoNameToKotlin(), + number = number, type = fieldType(resolver), nullable = proto3Optional, deprecated = options.deprecated, doc = null, + proto = this, ) } @@ -278,10 +282,12 @@ class ProtoToModelInterpreter( variants = fields.map { field -> FieldDeclaration( name = field.name.fullProtoNameToKotlin(firstLetterUpper = true), + number = field.number, type = field.fieldType(fieldResolver), nullable = false, deprecated = field.options.deprecated, doc = null, + proto = field, ) } ) diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt index 2bd0b5c7e..91c5100f9 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt @@ -102,7 +102,7 @@ class RpcProtobufPlugin { private fun CodeGeneratorRequest.generateKotlinFiles(): List { val interpreter = ProtoToModelInterpreter(logger) val model = interpreter.interpretProtocRequest(this) - val fileGenerator = ModelToKotlinGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) + val fileGenerator = ModelToKotlinCommonGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) return fileGenerator.generateKotlinFiles() } } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt index 4e46e72dd..24977de67 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt @@ -4,51 +4,70 @@ package kotlinx.rpc.protobuf.model +import com.google.protobuf.DescriptorProtos + data class FieldDeclaration( val name: String, + val number: Int, val type: FieldType, val nullable: Boolean, val deprecated: Boolean, val doc: String?, + val proto: DescriptorProtos.FieldDescriptorProto ) +enum class WireType { + VARINT, + FIXED64, + LENGTH_DELIMITED, + START_GROUP, + END_GROUP, + FIXED32 +} + sealed interface FieldType { val defaultValue: String + val wireType: WireType data class List(val value: FieldType) : FieldType { override val defaultValue: String = "emptyList()" + override val wireType: WireType = value.wireType } data class Map(val entry: Lazy) : FieldType { override val defaultValue: String = "emptyMap()" + override val wireType: WireType = WireType.LENGTH_DELIMITED data class Entry(val key: FieldType, val value: FieldType) } data class Reference(val value: Lazy) : FieldType { override val defaultValue: String = "null" + override val wireType: WireType = WireType.LENGTH_DELIMITED } data class OneOf(val value: Lazy, val index: Int) : FieldType { override val defaultValue: String = "null" + override val wireType: WireType = WireType.LENGTH_DELIMITED } - enum class IntegralType(simpleName: String, override val defaultValue: String) : FieldType { - STRING("String", "\"\""), - BYTES("ByteArray", "byteArrayOf()"), - BOOL("Boolean", "false"), - FLOAT("Float", "0.0f"), - DOUBLE("Double", "0.0"), - INT32("Int", "0"), - INT64("Long", "0"), - UINT32("UInt", "0u"), - UINT64("ULong", "0u"), - FIXED32("UInt", "0u"), - FIXED64("ULong", "0u"), - SINT32("Int", "0"), - SINT64("Long", "0"), - SFIXED32("Int", "0"), - SFIXED64("Long", "0"); + enum class IntegralType(simpleName: String, override val defaultValue: String, override val wireType: WireType) : + FieldType { + STRING("String", "\"\"", WireType.LENGTH_DELIMITED), + BYTES("ByteArray", "byteArrayOf()", WireType.LENGTH_DELIMITED), + BOOL("Boolean", "false", WireType.VARINT), + FLOAT("Float", "0.0f", WireType.FIXED32), + DOUBLE("Double", "0.0", WireType.FIXED64), + INT32("Int", "0", WireType.VARINT), + INT64("Long", "0", WireType.VARINT), + UINT32("UInt", "0u", WireType.VARINT), + UINT64("ULong", "0u", WireType.VARINT), + FIXED32("UInt", "0u", WireType.FIXED32), + FIXED64("ULong", "0u", WireType.FIXED64), + SINT32("Int", "0", WireType.VARINT), + SINT64("Long", "0", WireType.VARINT), + SFIXED32("Int", "0", WireType.FIXED32), + SFIXED64("Long", "0", WireType.FIXED64); val fqName: FqName = FqName.Declaration(simpleName, FqName.Package.fromString("kotlin")) } From 4e545e029d4163404f770cf99d792cd891141c87 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Wed, 30 Jul 2025 09:56:37 +0200 Subject: [PATCH 2/6] grpc-native: Support for repeated fields in Message generation Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/pb/WireSize.kt | 12 +-- .../rpc/grpc/internal/WireCodecTest.kt | 4 +- .../kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt | 12 +-- .../kotlinx/rpc/grpc/pb/WireSize.native.kt | 12 +-- .../kotlinx/rpc/grpc/pb/TestAllPrimitive.kt | 38 -------- .../kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt | 57 +++++++++++ .../src/nativeTest/proto/all_primitives.proto | 21 ++++ .../src/nativeTest/proto/exclude/enum.proto | 16 +++ .../proto/exclude/image-recognizer.proto | 13 +++ .../src/nativeTest/proto/exclude/nested.proto | 46 +++++++++ .../src/nativeTest/proto/exclude/one_of.proto | 28 ++++++ .../nativeTest/proto/exclude/optional.proto | 11 +++ .../proto/exclude/primitive_service.proto | 9 ++ .../nativeTest/proto/exclude/reference.proto | 9 ++ .../proto/exclude/reference_package.proto | 12 +++ .../proto/exclude/reference_service.proto | 26 +++++ .../nativeTest/proto/exclude/streaming.proto | 9 ++ .../nativeTest/proto/exclude/test_map.proto | 10 ++ .../grpc-core/src/nativeTest/proto/mini.proto | 39 -------- .../src/nativeTest/proto/repeated.proto | 11 +++ .../kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt | 12 +-- .../protobuf/ModelToKotlinCommonGenerator.kt | 97 ++++++++++++++++--- .../rpc/protobuf/model/FieldDeclaration.kt | 44 +++++++-- 23 files changed, 423 insertions(+), 125 deletions(-) delete mode 100644 grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestAllPrimitive.kt create mode 100644 grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt create mode 100644 grpc/grpc-core/src/nativeTest/proto/all_primitives.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/enum.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/image-recognizer.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/nested.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/one_of.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/optional.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/primitive_service.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/reference.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/reference_package.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/reference_service.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/streaming.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/test_map.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/mini.proto create mode 100644 grpc/grpc-core/src/nativeTest/proto/repeated.proto diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt index 0b1d7e25a..c82ec44a1 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt @@ -6,12 +6,12 @@ package kotlinx.rpc.grpc.pb internal object WireSize -internal expect fun WireSize.int32(value: Int): UInt -internal expect fun WireSize.int64(value: Long): UInt -internal expect fun WireSize.uInt32(value: UInt): UInt -internal expect fun WireSize.uInt64(value: ULong): UInt -internal expect fun WireSize.sInt32(value: Int): UInt -internal expect fun WireSize.sInt64(value: Long): UInt +internal expect fun WireSize.int32(value: Int): Int +internal expect fun WireSize.int64(value: Long): Int +internal expect fun WireSize.uInt32(value: UInt): Int +internal expect fun WireSize.uInt64(value: ULong): Int +internal expect fun WireSize.sInt32(value: Int): Int +internal expect fun WireSize.sInt64(value: Long): Int internal fun WireSize.bool(value: Boolean) = int32(if (value) 1 else 0) internal fun WireSize.enum(value: Int) = int32(value) diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt index 896382a11..e54d2946b 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt @@ -734,13 +734,13 @@ class WireCodecTest { private fun runPackedVarTest( list: List, - sizeFn: (List) -> UInt, + sizeFn: (List) -> Int, write: WireEncoder.(Int, List, Int) -> Boolean, read: WireDecoder.() -> List?, ) { val buf = Buffer() with(WireEncoder(buf)) { - assertTrue(write(1, list, sizeFn(list).toInt())) + assertTrue(write(1, list, sizeFn(list))) flush() } WireDecoder(buf).use { dec -> diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt index 251566247..041674e94 100644 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt +++ b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt @@ -4,26 +4,26 @@ package kotlinx.rpc.grpc.pb -internal actual fun WireSize.int32(value: Int): UInt { +internal actual fun WireSize.int32(value: Int): Int { TODO("Not yet implemented") } -internal actual fun WireSize.int64(value: Long): UInt { +internal actual fun WireSize.int64(value: Long): Int { TODO("Not yet implemented") } -internal actual fun WireSize.uInt32(value: UInt): UInt { +internal actual fun WireSize.uInt32(value: UInt): Int { TODO("Not yet implemented") } -internal actual fun WireSize.uInt64(value: ULong): UInt { +internal actual fun WireSize.uInt64(value: ULong): Int { TODO("Not yet implemented") } -internal actual fun WireSize.sInt32(value: Int): UInt { +internal actual fun WireSize.sInt32(value: Int): Int { TODO("Not yet implemented") } -internal actual fun WireSize.sInt64(value: Long): UInt { +internal actual fun WireSize.sInt64(value: Long): Int { TODO("Not yet implemented") } \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt index cbbcaf18d..f286b3d99 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt @@ -9,10 +9,10 @@ package kotlinx.rpc.grpc.pb import kotlinx.cinterop.ExperimentalForeignApi import libprotowire.* -internal actual fun WireSize.int32(value: Int) = pw_size_int32(value) -internal actual fun WireSize.int64(value: Long) = pw_size_int64(value) -internal actual fun WireSize.uInt32(value: UInt) = pw_size_uint32(value) -internal actual fun WireSize.uInt64(value: ULong) = pw_size_uint64(value) -internal actual fun WireSize.sInt32(value: Int) = pw_size_sint32(value) -internal actual fun WireSize.sInt64(value: Long) = pw_size_sint64(value) +internal actual fun WireSize.int32(value: Int) = pw_size_int32(value).toInt() +internal actual fun WireSize.int64(value: Long) = pw_size_int64(value).toInt() +internal actual fun WireSize.uInt32(value: UInt) = pw_size_uint32(value).toInt() +internal actual fun WireSize.uInt64(value: ULong) = pw_size_uint64(value).toInt() +internal actual fun WireSize.sInt32(value: Int) = pw_size_sint32(value).toInt() +internal actual fun WireSize.sInt64(value: Long) = pw_size_sint64(value).toInt() diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestAllPrimitive.kt b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestAllPrimitive.kt deleted file mode 100644 index 837884880..000000000 --- a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestAllPrimitive.kt +++ /dev/null @@ -1,38 +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.pb - -import kotlinx.io.Buffer -import kotlinx.rpc.grpc.test.PrimitiveTest -import kotlinx.rpc.grpc.test.decodeWith -import kotlinx.rpc.grpc.test.encodeWith -import kotlinx.rpc.grpc.test.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -class TestAllPrimitive { - - - @Test - fun testAllPrimitiveProto() { - val msg = PrimitiveTest { - double = 3.0 - int32Opt = 12 - } - - val buffer = Buffer() - val encoder = WireEncoder(buffer) - - msg.encodeWith(encoder) - encoder.flush() - - val decoded = WireDecoder(buffer).use { - PrimitiveTest.decodeWith(it) - } - - assertEquals(msg.double, decoded?.double) - assertEquals(msg.int32Opt, decoded?.int32Opt) - } -} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt new file mode 100644 index 000000000..bb31af82e --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt @@ -0,0 +1,57 @@ +/* + * 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.pb + +import kotlinx.io.Buffer +import kotlinx.rpc.grpc.test.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class TestProtos { + + private fun decodeEncode( + msg: T, + enc: T.(WireEncoder) -> Unit, + dec: (WireDecoder) -> T? + ): T? { + val buffer = Buffer() + val encoder = WireEncoder(buffer) + + msg.enc(encoder) + encoder.flush() + + return WireDecoder(buffer).use { + dec(it) + } + } + + + @Test + fun testAllPrimitiveProto() { + val msg = AllPrimitives { + double = 3.0 + } + + val decoded = decodeEncode(msg, { encodeWith(it) }, AllPrimitives::decodeWith) + + assertEquals(msg.double, decoded?.double) + } + + @Test + fun testRepeatedProto() { + val msg = Repeated { + listFixed32 = listOf(1, 2, 3).map { it.toUInt() } + listInt32 = listOf(4, 5, 6) + listString = listOf("a", "b", "c") + } + + val decoded = decodeEncode(msg, { encodeWith(it) }, Repeated::decodeWith) + + assertEquals(msg.listInt32, decoded?.listInt32) + assertEquals(msg.listFixed32, decoded?.listFixed32) + assertEquals(msg.listString, decoded?.listString) + } + +} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeTest/proto/all_primitives.proto b/grpc/grpc-core/src/nativeTest/proto/all_primitives.proto new file mode 100644 index 000000000..be698f9e6 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/all_primitives.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +message AllPrimitives { + double double = 1; + float float = 2; + int32 int32 = 3; + int64 int64 = 4; + uint32 uint32 = 5; + uint64 uint64 = 6; + sint32 sint32 = 7; + sint64 sint64 = 8; + fixed32 fixed32 = 9; + fixed64 fixed64 = 10; + sfixed32 sfixed32 = 11; + sfixed64 sfixed64 = 12; + bool bool = 13; + string string = 14; + bytes bytes = 15; +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/enum.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/enum.proto new file mode 100644 index 000000000..83db7b7fe --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/enum.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +enum Enum { + option allow_alias = true; + ZERO = 0; + ONE = 1; + ONE_SECOND = 1; + TWO = 2 [deprecated = true]; + THREE = 3; +} + +message UsingEnum { + Enum enum = 1; +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/image-recognizer.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/image-recognizer.proto new file mode 100644 index 000000000..38dde9d68 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/image-recognizer.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +message Image { + bytes data = 1; +} + +message RecogniseResult { + int32 category = 1; +} + +service ImageRecognizer { + rpc recognize(Image) returns (RecogniseResult); +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/nested.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/nested.proto new file mode 100644 index 000000000..1c886d6e1 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/nested.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +message Nested { + message Inner1 { + message Inner11 { + optional Nested.Inner2.Inner21 reference21 = 1; + Nested.Inner1.Inner12 reference12 = 2; + Nested.Inner2.NestedEnum enum = 3; + } + + message Inner12 { + optional Inner12 recursion = 1; + } + + Inner11 inner11 = 1; + Inner12 inner22 = 2; + string string = 3; + optional Inner1 inner1 = 4; + } + + message Inner2 { + message Inner21 { + Nested.Inner1.Inner11 reference11 = 1; + Nested.Inner2.Inner22 reference22 = 2; + } + + message Inner22 { + NestedEnum enum = 1; + } + + enum NestedEnum { + ZERO = 0; + } + + Inner21 inner21 = 1; + Inner22 inner22 = 2; + string string = 3; + } + + Inner1 inner1 = 1; + Inner2 inner2 = 2; + string string = 3; + Inner2.NestedEnum enum = 4; +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/one_of.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/one_of.proto new file mode 100644 index 000000000..b096aa9d2 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/one_of.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +import "all_primitives.proto"; +import "reference_package.proto"; + +message OneOf { + oneof primitives { + string string_value = 1; + int32 int32 = 2; + bool bool = 3; + } + + oneof references { + Other other = 4; + References inner_references = 5; + } + + oneof mixed { + int64 int64 = 6; + AllPrimitives allPrimitives = 7; + } + + oneof single { + bytes bytes = 8; + } +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/optional.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/optional.proto new file mode 100644 index 000000000..7bed51cc5 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/optional.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +import 'reference_package.proto'; + +message OptionalTypes { + optional string name = 1; + optional int32 age = 2; + optional Other reference = 3; +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/primitive_service.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/primitive_service.proto new file mode 100644 index 000000000..aac406f19 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/primitive_service.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +import "all_primitives.proto"; + +service PrimitiveService { + rpc Echo(AllPrimitives) returns (AllPrimitives); +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/reference.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/reference.proto new file mode 100644 index 000000000..4a68c5189 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/reference.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +message Other { + string arg = 1; +} + +message References { + Other other = 2; +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/reference_package.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/reference_package.proto new file mode 100644 index 000000000..0f1f76cc8 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/reference_package.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +message Other { + int32 field = 1; +} + +message References { + string primitive = 1; + Other other = 2; +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/reference_service.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/reference_service.proto new file mode 100644 index 000000000..c3fd1a012 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/reference_service.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +import "reference.proto"; +import "reference_package.proto"; +import "enum.proto"; +import "optional.proto"; +import "repeated.proto"; +import "nested.proto"; +import "test_map.proto"; +import "one_of.proto"; + +service ReferenceTestService { + rpc Get(References) returns (kotlinx.rpc.grpc.test.References); + + rpc Enum(kotlinx.rpc.grpc.test.UsingEnum) returns (kotlinx.rpc.grpc.test.UsingEnum); + + rpc Optional(kotlinx.rpc.grpc.test.OptionalTypes) returns (kotlinx.rpc.grpc.test.OptionalTypes); + + rpc Repeated(kotlinx.rpc.grpc.test.Repeated) returns (kotlinx.rpc.grpc.test.Repeated); + + rpc Nested(kotlinx.rpc.grpc.test.Nested) returns (kotlinx.rpc.grpc.test.Nested); + + rpc Map(kotlinx.rpc.grpc.test.TestMap) returns (kotlinx.rpc.grpc.test.TestMap); + + rpc OneOf(kotlinx.rpc.grpc.test.OneOf) returns (kotlinx.rpc.grpc.test.OneOf); +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/streaming.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/streaming.proto new file mode 100644 index 000000000..042383a65 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/streaming.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +import "reference_package.proto"; + +service StreamingTestService { + rpc Server(kotlinx.rpc.grpc.test.References) returns (stream kotlinx.rpc.grpc.test.References); + rpc Client(stream kotlinx.rpc.grpc.test.References) returns (kotlinx.rpc.grpc.test.References); + rpc Bidi(stream kotlinx.rpc.grpc.test.References) returns (stream kotlinx.rpc.grpc.test.References); +} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/test_map.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/test_map.proto new file mode 100644 index 000000000..cbb3f8e61 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/exclude/test_map.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +import "reference_package.proto"; + +message TestMap { + map primitives = 1; + map references = 2; +} diff --git a/grpc/grpc-core/src/nativeTest/proto/mini.proto b/grpc/grpc-core/src/nativeTest/proto/mini.proto deleted file mode 100644 index 472caae0a..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/mini.proto +++ /dev/null @@ -1,39 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -option optimize_for = LITE_RUNTIME; - -message PrimitiveTest { - double double = 1; - float float = 2; - int32 int32 = 3; - int64 int64 = 4; - uint32 uint32 = 5; - uint64 uint64 = 6; - sint32 sint32 = 7; - sint64 sint64 = 8; - fixed32 fixed32 = 9; - fixed64 fixed64 = 10; - sfixed32 sfixed32 = 11; - sfixed64 sfixed64 = 12; - bool bool = 13; - string string = 14; - bytes bytes = 15; - optional double doubleOpt = 111; - optional float floatOpt = 112; - optional int32 int32Opt = 113; - optional int64 int64Opt = 114; - optional uint32 uint32Opt = 115; - optional uint64 uint64Opt = 116; - optional sint32 sint32Opt = 117; - optional sint64 sint64Opt = 118; - optional fixed32 fixed32Opt = 119; - optional fixed64 fixed64Opt = 1110; - optional sfixed32 sfixed32Opt = 1111; - optional sfixed64 sfixed64Opt = 1112; - optional bool boolOpt = 1113; - optional string stringOpt = 1114; - optional bytes bytesOpt = 1115; -} - diff --git a/grpc/grpc-core/src/nativeTest/proto/repeated.proto b/grpc/grpc-core/src/nativeTest/proto/repeated.proto new file mode 100644 index 000000000..3e7f99227 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/proto/repeated.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test; + +//import 'reference_package.proto'; + +message Repeated { + repeated fixed32 listFixed32 = 1 [packed = true]; + repeated int32 listInt32 = 2 [packed = false]; + repeated string listString = 3; +} diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt index 251566247..041674e94 100644 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt +++ b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt @@ -4,26 +4,26 @@ package kotlinx.rpc.grpc.pb -internal actual fun WireSize.int32(value: Int): UInt { +internal actual fun WireSize.int32(value: Int): Int { TODO("Not yet implemented") } -internal actual fun WireSize.int64(value: Long): UInt { +internal actual fun WireSize.int64(value: Long): Int { TODO("Not yet implemented") } -internal actual fun WireSize.uInt32(value: UInt): UInt { +internal actual fun WireSize.uInt32(value: UInt): Int { TODO("Not yet implemented") } -internal actual fun WireSize.uInt64(value: ULong): UInt { +internal actual fun WireSize.uInt64(value: ULong): Int { TODO("Not yet implemented") } -internal actual fun WireSize.sInt32(value: Int): UInt { +internal actual fun WireSize.sInt32(value: Int): Int { TODO("Not yet implemented") } -internal actual fun WireSize.sInt64(value: Long): UInt { +internal actual fun WireSize.sInt64(value: Long): Int { TODO("Not yet implemented") } \ No newline at end of file diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt index 167e1fb32..5923038ab 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt @@ -78,9 +78,7 @@ class ModelToKotlinCommonGenerator( import("kotlinx.rpc.internal.utils.*") import("kotlinx.coroutines.flow.*") - import("kotlinx.rpc.grpc.WireDecoder") - import("kotlinx.rpc.grpc.WireEncoder") - import("kotlinx.rpc.grpc.WireType") + import("kotlinx.rpc.grpc.pb.*") additionalInternalImports.forEach { @@ -194,11 +192,7 @@ class ModelToKotlinCommonGenerator( whileBlock("!decoder.hadError()") { code("val tag = decoder.readTag() ?: break // EOF, we read the whole message") whenBlock("tag.fieldNr") { - declaration.fields().forEach { (_, field) -> - whenCase("${field.number} if tag.wireType == WireType.${field.type.wireType.name}") { - code("msg.${field.name} = decoder.read${field.type.decodeEncodeFuncName()}()") - } - } + declaration.fields().forEach { (_, field) -> readMatchCase(field) } whenCase("else") { code("TODO(\"Handle unknown fields\")") } } } @@ -211,6 +205,30 @@ class ModelToKotlinCommonGenerator( code("return msg") } + private fun CodeGenerator.readMatchCase(field: FieldDeclaration) { + val encFuncName = field.type.decodeEncodeFuncName() + val assignment = "msg.${field.name} =" + when (val fieldType = field.type) { + is FieldType.IntegralType -> whenCase("${field.number} if tag.wireType == WireType.${field.type.wireType.name}") { + code("$assignment decoder.read$encFuncName()") + } + + is FieldType.List -> if (field.packed) { + whenCase("${field.number} if tag.wireType == WireType.LENGTH_DELIMITED") { + code("$assignment decoder.readPacked${fieldType.value.decodeEncodeFuncName()}()") + } + } else { + whenCase("${field.number} if tag.wireType == WireType.LENGTH_DELIMITED") { + code("(msg.${field.name} as ArrayList).add(decoder.read${fieldType.value.decodeEncodeFuncName()}())") + } + } + + is FieldType.Map -> TODO() + is FieldType.OneOf -> TODO() + is FieldType.Reference -> TODO() + } + } + private fun CodeGenerator.generateMessageEncoder(declaration: MessageDeclaration) = function( name = "encodeWith", args = "encoder: WireEncoder", @@ -218,16 +236,71 @@ class ModelToKotlinCommonGenerator( ) { declaration.fields().forEach { (_, field) -> - val encFuncName = field.type.decodeEncodeFuncName() - val fieldNr = field.number val fieldName = field.name if (field.nullable) { scope("$fieldName?.also") { - code("encoder.write$encFuncName($fieldNr, it)") + code(field.writeValue()) } + } else if (!field.hasPresence) { + ifBranch(condition = field.defaultCheck(), ifBlock = { + code(field.writeValue()) + }) } else { - code("encoder.write$encFuncName($fieldNr, $fieldName)") + code(field.writeValue()) + } + } + } + + private fun FieldDeclaration.writeValue(): String { + return when (val fieldType = type) { + is FieldType.IntegralType -> "encoder.write${type.decodeEncodeFuncName()}($number, $name)" + is FieldType.List -> when { + packed && packedFixedSize -> + "encoder.writePacked${fieldType.value.decodeEncodeFuncName()}($number, $name)" + + packed && !packedFixedSize -> + "encoder.writePacked${fieldType.value.decodeEncodeFuncName()}($number, $name, ${wireSizeCall(name)})" + + else -> + "$name.forEach { encoder.write${fieldType.value.decodeEncodeFuncName()}($number, it) }" + } + + is FieldType.Map -> TODO() + is FieldType.OneOf -> TODO() + is FieldType.Reference -> TODO() + } + } + + private fun FieldDeclaration.wireSizeCall(variable: String): String { + val sizeFunc = "WireSize.${type.decodeEncodeFuncName().replaceFirstChar { it.lowercase() }}($variable)" + return when (val fieldType = type) { + is FieldType.IntegralType -> when { + fieldType.wireType == WireType.FIXED32 -> "32" + fieldType.wireType == WireType.FIXED64 -> "64" + else -> sizeFunc + } + + is FieldType.List -> when { + isPackable && !packedFixedSize -> sizeFunc + else -> error("Unexpected use of size call for field: $name, type: $fieldType") + } + + is FieldType.Map -> TODO() + is FieldType.OneOf -> TODO() + is FieldType.Reference -> TODO() + } + } + + private fun FieldDeclaration.defaultCheck(): String { + return when (val fieldType = type) { + is FieldType.IntegralType -> when (fieldType) { + FieldType.IntegralType.BYTES, FieldType.IntegralType.STRING -> "$name.isNotEmpty()" + else -> "$name != ${fieldType.defaultValue}" } + + is FieldType.List -> "$name.isNotEmpty()" + + else -> TODO("Field: $name, type: $fieldType") } } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt index 24977de67..f76f321a9 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt @@ -14,7 +14,23 @@ data class FieldDeclaration( val deprecated: Boolean, val doc: String?, val proto: DescriptorProtos.FieldDescriptorProto -) +) { + val isRepeated = proto.label == DescriptorProtos.FieldDescriptorProto.Label.LABEL_REPEATED + val isExtension = proto.hasExtendee() + val containsOneOf = proto.hasOneofIndex() + + val hasPresence = if (isRepeated) false else + proto.proto3Optional || proto.type == DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE + || proto.type == DescriptorProtos.FieldDescriptorProto.Type.TYPE_GROUP + || isExtension || containsOneOf + + val isPackable = isRepeated && type.isPackable + + val packed = isPackable // TODO: must checked if this is also declared as [packed = true] (or proto3 auto packed) + + val packedFixedSize = type.wireType == WireType.FIXED64 || type.wireType == WireType.FIXED32 +} + enum class WireType { VARINT, @@ -29,9 +45,12 @@ sealed interface FieldType { val defaultValue: String val wireType: WireType + val isPackable: Boolean get() = false + data class List(val value: FieldType) : FieldType { - override val defaultValue: String = "emptyList()" + override val defaultValue: String = "arrayListOf()" override val wireType: WireType = value.wireType + override val isPackable: Boolean = value.isPackable } data class Map(val entry: Lazy) : FieldType { @@ -51,23 +70,28 @@ sealed interface FieldType { override val wireType: WireType = WireType.LENGTH_DELIMITED } - enum class IntegralType(simpleName: String, override val defaultValue: String, override val wireType: WireType) : + enum class IntegralType( + simpleName: String, + override val defaultValue: String, + override val wireType: WireType, + override val isPackable: Boolean = true + ) : FieldType { - STRING("String", "\"\"", WireType.LENGTH_DELIMITED), - BYTES("ByteArray", "byteArrayOf()", WireType.LENGTH_DELIMITED), + STRING("String", "\"\"", WireType.LENGTH_DELIMITED, false), + BYTES("ByteArray", "byteArrayOf()", WireType.LENGTH_DELIMITED, false), BOOL("Boolean", "false", WireType.VARINT), FLOAT("Float", "0.0f", WireType.FIXED32), DOUBLE("Double", "0.0", WireType.FIXED64), INT32("Int", "0", WireType.VARINT), - INT64("Long", "0", WireType.VARINT), + INT64("Long", "0L", WireType.VARINT), UINT32("UInt", "0u", WireType.VARINT), - UINT64("ULong", "0u", WireType.VARINT), + UINT64("ULong", "0uL", WireType.VARINT), FIXED32("UInt", "0u", WireType.FIXED32), - FIXED64("ULong", "0u", WireType.FIXED64), + FIXED64("ULong", "0uL", WireType.FIXED64), SINT32("Int", "0", WireType.VARINT), - SINT64("Long", "0", WireType.VARINT), + SINT64("Long", "0L", WireType.VARINT), SFIXED32("Int", "0", WireType.FIXED32), - SFIXED64("Long", "0", WireType.FIXED64); + SFIXED64("Long", "0L", WireType.FIXED64); val fqName: FqName = FqName.Declaration(simpleName, FqName.Package.fromString("kotlin")) } From 0090141b72e10fe62c4d24797213da9a97b6eea4 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Wed, 30 Jul 2025 14:11:34 +0200 Subject: [PATCH 3/6] grpc-native: Remove guards in generated kotlin Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt | 14 ++++++ .../src/nativeTest/proto/exclude/enum.proto | 16 ------- .../proto/exclude/image-recognizer.proto | 13 ------ .../src/nativeTest/proto/exclude/nested.proto | 46 ------------------- .../src/nativeTest/proto/exclude/one_of.proto | 28 ----------- .../nativeTest/proto/exclude/optional.proto | 11 ----- .../proto/exclude/primitive_service.proto | 9 ---- .../nativeTest/proto/exclude/reference.proto | 9 ---- .../proto/exclude/reference_package.proto | 12 ----- .../proto/exclude/reference_service.proto | 26 ----------- .../nativeTest/proto/exclude/streaming.proto | 9 ---- .../nativeTest/proto/exclude/test_map.proto | 10 ---- .../kotlinx/rpc/protobuf/CodeGenerator.kt | 5 +- .../protobuf/ModelToKotlinCommonGenerator.kt | 8 ++-- 14 files changed, 21 insertions(+), 195 deletions(-) delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/enum.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/image-recognizer.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/nested.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/one_of.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/optional.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/primitive_service.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/reference.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/reference_package.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/reference_service.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/streaming.proto delete mode 100644 grpc/grpc-core/src/nativeTest/proto/exclude/test_map.proto diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt index bb31af82e..20ea6fb30 100644 --- a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt +++ b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt @@ -31,7 +31,21 @@ class TestProtos { @Test fun testAllPrimitiveProto() { val msg = AllPrimitives { + int32 = 12 + int64 = 1234567890123456789L + uint32 = 12345u + uint64 = 1234567890123456789uL + sint32 = -12 + sint64 = -1234567890123456789L + fixed32 = 12345u + fixed64 = 1234567890123456789uL + sfixed32 = -12345 + sfixed64 = -1234567890123456789L + bool = true + float = 1.0f double = 3.0 + string = "test" + bytes = byteArrayOf(1, 2, 3) } val decoded = decodeEncode(msg, { encodeWith(it) }, AllPrimitives::decodeWith) diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/enum.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/enum.proto deleted file mode 100644 index 83db7b7fe..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/enum.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -enum Enum { - option allow_alias = true; - ZERO = 0; - ONE = 1; - ONE_SECOND = 1; - TWO = 2 [deprecated = true]; - THREE = 3; -} - -message UsingEnum { - Enum enum = 1; -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/image-recognizer.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/image-recognizer.proto deleted file mode 100644 index 38dde9d68..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/image-recognizer.proto +++ /dev/null @@ -1,13 +0,0 @@ -syntax = "proto3"; - -message Image { - bytes data = 1; -} - -message RecogniseResult { - int32 category = 1; -} - -service ImageRecognizer { - rpc recognize(Image) returns (RecogniseResult); -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/nested.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/nested.proto deleted file mode 100644 index 1c886d6e1..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/nested.proto +++ /dev/null @@ -1,46 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -message Nested { - message Inner1 { - message Inner11 { - optional Nested.Inner2.Inner21 reference21 = 1; - Nested.Inner1.Inner12 reference12 = 2; - Nested.Inner2.NestedEnum enum = 3; - } - - message Inner12 { - optional Inner12 recursion = 1; - } - - Inner11 inner11 = 1; - Inner12 inner22 = 2; - string string = 3; - optional Inner1 inner1 = 4; - } - - message Inner2 { - message Inner21 { - Nested.Inner1.Inner11 reference11 = 1; - Nested.Inner2.Inner22 reference22 = 2; - } - - message Inner22 { - NestedEnum enum = 1; - } - - enum NestedEnum { - ZERO = 0; - } - - Inner21 inner21 = 1; - Inner22 inner22 = 2; - string string = 3; - } - - Inner1 inner1 = 1; - Inner2 inner2 = 2; - string string = 3; - Inner2.NestedEnum enum = 4; -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/one_of.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/one_of.proto deleted file mode 100644 index b096aa9d2..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/one_of.proto +++ /dev/null @@ -1,28 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -import "all_primitives.proto"; -import "reference_package.proto"; - -message OneOf { - oneof primitives { - string string_value = 1; - int32 int32 = 2; - bool bool = 3; - } - - oneof references { - Other other = 4; - References inner_references = 5; - } - - oneof mixed { - int64 int64 = 6; - AllPrimitives allPrimitives = 7; - } - - oneof single { - bytes bytes = 8; - } -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/optional.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/optional.proto deleted file mode 100644 index 7bed51cc5..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/optional.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -import 'reference_package.proto'; - -message OptionalTypes { - optional string name = 1; - optional int32 age = 2; - optional Other reference = 3; -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/primitive_service.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/primitive_service.proto deleted file mode 100644 index aac406f19..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/primitive_service.proto +++ /dev/null @@ -1,9 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -import "all_primitives.proto"; - -service PrimitiveService { - rpc Echo(AllPrimitives) returns (AllPrimitives); -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/reference.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/reference.proto deleted file mode 100644 index 4a68c5189..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/reference.proto +++ /dev/null @@ -1,9 +0,0 @@ -syntax = "proto3"; - -message Other { - string arg = 1; -} - -message References { - Other other = 2; -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/reference_package.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/reference_package.proto deleted file mode 100644 index 0f1f76cc8..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/reference_package.proto +++ /dev/null @@ -1,12 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -message Other { - int32 field = 1; -} - -message References { - string primitive = 1; - Other other = 2; -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/reference_service.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/reference_service.proto deleted file mode 100644 index c3fd1a012..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/reference_service.proto +++ /dev/null @@ -1,26 +0,0 @@ -syntax = "proto3"; - -import "reference.proto"; -import "reference_package.proto"; -import "enum.proto"; -import "optional.proto"; -import "repeated.proto"; -import "nested.proto"; -import "test_map.proto"; -import "one_of.proto"; - -service ReferenceTestService { - rpc Get(References) returns (kotlinx.rpc.grpc.test.References); - - rpc Enum(kotlinx.rpc.grpc.test.UsingEnum) returns (kotlinx.rpc.grpc.test.UsingEnum); - - rpc Optional(kotlinx.rpc.grpc.test.OptionalTypes) returns (kotlinx.rpc.grpc.test.OptionalTypes); - - rpc Repeated(kotlinx.rpc.grpc.test.Repeated) returns (kotlinx.rpc.grpc.test.Repeated); - - rpc Nested(kotlinx.rpc.grpc.test.Nested) returns (kotlinx.rpc.grpc.test.Nested); - - rpc Map(kotlinx.rpc.grpc.test.TestMap) returns (kotlinx.rpc.grpc.test.TestMap); - - rpc OneOf(kotlinx.rpc.grpc.test.OneOf) returns (kotlinx.rpc.grpc.test.OneOf); -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/streaming.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/streaming.proto deleted file mode 100644 index 042383a65..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/streaming.proto +++ /dev/null @@ -1,9 +0,0 @@ -syntax = "proto3"; - -import "reference_package.proto"; - -service StreamingTestService { - rpc Server(kotlinx.rpc.grpc.test.References) returns (stream kotlinx.rpc.grpc.test.References); - rpc Client(stream kotlinx.rpc.grpc.test.References) returns (kotlinx.rpc.grpc.test.References); - rpc Bidi(stream kotlinx.rpc.grpc.test.References) returns (stream kotlinx.rpc.grpc.test.References); -} diff --git a/grpc/grpc-core/src/nativeTest/proto/exclude/test_map.proto b/grpc/grpc-core/src/nativeTest/proto/exclude/test_map.proto deleted file mode 100644 index cbb3f8e61..000000000 --- a/grpc/grpc-core/src/nativeTest/proto/exclude/test_map.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -import "reference_package.proto"; - -message TestMap { - map primitives = 1; - map references = 2; -} diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt index 076c924a4..006faee13 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt @@ -105,10 +105,11 @@ open class CodeGenerator( } internal fun whenBlock( - condition: String, + condition: String? = null, block: (CodeGenerator.() -> Unit), ) { - scope("when ($condition)", block = block) + val cond = condition?.let { " ($it)" } ?: "" + scope("when$cond", block = block) } internal fun whenCase( diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt index 5923038ab..3a1e20198 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt @@ -191,7 +191,7 @@ class ModelToKotlinCommonGenerator( code("val msg = ${declaration.name.safeFullName("Builder")}()") whileBlock("!decoder.hadError()") { code("val tag = decoder.readTag() ?: break // EOF, we read the whole message") - whenBlock("tag.fieldNr") { + whenBlock { declaration.fields().forEach { (_, field) -> readMatchCase(field) } whenCase("else") { code("TODO(\"Handle unknown fields\")") } } @@ -209,16 +209,16 @@ class ModelToKotlinCommonGenerator( val encFuncName = field.type.decodeEncodeFuncName() val assignment = "msg.${field.name} =" when (val fieldType = field.type) { - is FieldType.IntegralType -> whenCase("${field.number} if tag.wireType == WireType.${field.type.wireType.name}") { + is FieldType.IntegralType -> whenCase("tag.fieldNr == ${field.number} && tag.wireType == WireType.${field.type.wireType.name}") { code("$assignment decoder.read$encFuncName()") } is FieldType.List -> if (field.packed) { - whenCase("${field.number} if tag.wireType == WireType.LENGTH_DELIMITED") { + whenCase("tag.fieldNr == ${field.number} && tag.wireType == WireType.LENGTH_DELIMITED") { code("$assignment decoder.readPacked${fieldType.value.decodeEncodeFuncName()}()") } } else { - whenCase("${field.number} if tag.wireType == WireType.LENGTH_DELIMITED") { + whenCase("tag.fieldNr == ${field.number} && tag.wireType == WireType.LENGTH_DELIMITED") { code("(msg.${field.name} as ArrayList).add(decoder.read${fieldType.value.decodeEncodeFuncName()}())") } } From d5e9f00b9029e12c7a34c2a4247ff177286fb6fe Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Wed, 30 Jul 2025 15:38:15 +0200 Subject: [PATCH 4/6] grpc-native: Move internal decode/encoding into grpc.pb package and move common proto generation to commonTest Signed-off-by: Johannes Zottele --- .../rpc/proto/DefaultProtoSourceSet.kt | 2 +- grpc/grpc-core/build.gradle.kts | 21 ++++- .../kotlinx/rpc/grpc/internal/WireDecoder.kt | 85 ------------------- .../kotlinx/rpc/grpc/internal/WireEncoder.kt | 54 ------------ .../rpc/grpc/{pb => internal}/readPacked.kt | 2 +- .../kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt | 4 + .../kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt | 2 +- .../kotlin/kotlinx/rpc/grpc/pb/WireSize.kt | 32 +++---- .../kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt} | 10 +-- .../grpc/{internal => pb}/WireCodecTest.kt | 4 +- .../proto/all_primitives.proto | 4 +- .../proto/repeated.proto | 4 +- .../grpc/{pb => internal}/readPacked.js.kt | 0 .../grpc/{pb => internal}/readPacked.jvm.kt | 3 + .../kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt | 1 + .../kotlinx/rpc/grpc/pb/WireSize.jvm.kt | 33 +++---- .../kotlinx/rpc/grpc/pb/WireCodecTest.jvm.kt | 7 ++ .../rpc/grpc/internal/readPacked.native.kt | 2 + .../kotlinx/rpc/grpc/pb/WireDecoder.native.kt | 1 + .../kotlinx/rpc/grpc/pb/WireSize.native.kt | 12 +-- .../{internal => pb}/WireCodecTest.native.kt | 2 +- .../{pb => internal}/readPacked.wasmJs.kt | 0 .../kotlinx/rpc/protobuf/RpcProtobufPlugin.kt | 18 +++- 23 files changed, 102 insertions(+), 201 deletions(-) delete mode 100644 grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt delete mode 100644 grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt rename grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/{pb => internal}/readPacked.kt (96%) rename grpc/grpc-core/src/{nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt => commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt} (91%) rename grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireCodecTest.kt (99%) rename grpc/grpc-core/src/{nativeTest => commonTest}/proto/all_primitives.proto (83%) rename grpc/grpc-core/src/{nativeTest => commonTest}/proto/repeated.proto (75%) rename grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/{pb => internal}/readPacked.js.kt (100%) rename grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/{pb => internal}/readPacked.jvm.kt (87%) create mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.jvm.kt rename grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/{internal => pb}/WireCodecTest.native.kt (84%) rename grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/{pb => internal}/readPacked.wasmJs.kt (100%) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt index 93f17d4e7..165f8a914 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt @@ -100,7 +100,7 @@ internal fun Project.createProtoExtensions() { findOrCreateAndConfigure("jvmTest", null) sourceSets.configureEach { - if (name == "jvmMain" || name == "jvmTest" || name == "nativeTest") { + if (name == "jvmMain" || name == "jvmTest" || name == "nativeTest" || name == "commonTest") { findOrCreateAndConfigure(name, this) } } diff --git a/grpc/grpc-core/build.gradle.kts b/grpc/grpc-core/build.gradle.kts index 47fe51902..ac9270ffa 100644 --- a/grpc/grpc-core/build.gradle.kts +++ b/grpc/grpc-core/build.gradle.kts @@ -4,7 +4,6 @@ import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.proto.kotlinMultiplatform -import org.gradle.kotlin.dsl.withType import org.gradle.internal.extensions.stdlib.capitalized import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.tasks.CInteropProcess @@ -123,8 +122,8 @@ kotlin { ) } - val libUpbTask = "cinterop${libprotowire.name.capitalized()}${it.targetName.capitalized()}" - tasks.named(libUpbTask, CInteropProcess::class) { + val libProtowireTask = "cinterop${libprotowire.name.capitalized()}${it.targetName.capitalized()}" + tasks.named(libProtowireTask, CInteropProcess::class) { dependsOn(buildGrpcppCLib) } @@ -139,8 +138,15 @@ protoSourceSets { exclude("exclude/**") } } + + configureEach { + proto { + exclude("exclude/**") + } + } } + rpc { grpc { val globalRootDir: String by extra @@ -152,6 +158,15 @@ rpc { } project.tasks.withType().configureEach { + + // TODO: Remove this once we remove JVM generation + // Set compile for common(native) option + if (name.endsWith("CommonTest")) { + protocPlugins.kotlinMultiplatform { + options.set(options.getOrElse(emptyMap()) + mapOf("targetMode" to "common")) + } + } + if (name.endsWith("Test")) { dependsOn(gradle.includedBuild("protoc-gen").task(":jar")) } diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt deleted file mode 100644 index 64cbfd1d9..000000000 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt +++ /dev/null @@ -1,85 +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.internal - -import kotlinx.io.Buffer - -// TODO: Evaluate if this buffer size is suitable for all targets (KRPC-186) -// maximum buffer size to allocate as contiguous memory in bytes -internal const val MAX_PACKED_BULK_SIZE: Int = 1_000_000 - -/** - * A platform-specific decoder for wire format data. - * - * This decoder is used by first calling [readTag], than looking up the field based on the field number in the returned, - * tag and then calling the actual `read*()` method to read the value to the corresponding field. - * - * [hadError] indicates an error during decoding. While calling `read*()` is safe, the returned values - * are meaningless if [hadError] returns `true`. - * - * NOTE: If the [hadError] after a call to `read*()` returns `false`, it doesn't mean that the - * value is correctly decoded. E.g., the following test will pass: - * ```kt - * val fieldNr = 1 - * val buffer = Buffer() - * - * val encoder = WireEncoder(buffer) - * assertTrue(encoder.writeInt32(fieldNr, 12312)) - * encoder.flush() - * - * WireDecoder(buffer).use { decoder -> - * decoder.readTag() - * decoder.readBool() - * assertFalse(decoder.hasError()) - * } - * ``` - */ -internal interface WireDecoder : AutoCloseable { - fun hadError(): Boolean - fun readTag(): KTag? - fun readBool(): Boolean - fun readInt32(): Int - fun readInt64(): Long - fun readUInt32(): UInt - fun readUInt64(): ULong - fun readSInt32(): Int - fun readSInt64(): Long - fun readFixed32(): UInt - fun readFixed64(): ULong - fun readSFixed32(): Int - fun readSFixed64(): Long - fun readFloat(): Float - fun readDouble(): Double - - fun readEnum(): Int - fun readString(): String - fun readBytes(): ByteArray - fun readPackedBool(): List - fun readPackedInt32(): List - fun readPackedInt64(): List - fun readPackedSInt32(): List - fun readPackedSInt64(): List - fun readPackedUInt32(): List - fun readPackedUInt64(): List - fun readPackedFixed32(): List - fun readPackedFixed64(): List - fun readPackedSFixed32(): List - fun readPackedSFixed64(): List - fun readPackedFloat(): List - fun readPackedDouble(): List - fun readPackedEnum(): List -} - -/** - * Creates a platform-specific [WireDecoder]. - * - * This constructor takes a [Buffer] instead of a [kotlinx.io.Source] because - * the native implementation (`WireDecoderNative`) depends on [Buffer]'s internal structure. - * - * NOTE: Do not use the [source] buffer while the [WireDecoder] is still open. - * - * @param source The buffer containing the encoded wire-format data. - */ -internal expect fun WireDecoder(source: Buffer): WireDecoder diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt deleted file mode 100644 index ff5b6a725..000000000 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt +++ /dev/null @@ -1,54 +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.internal - -import kotlinx.io.Sink - -/** - * A platform-specific class that encodes values into protobuf's wire format. - * - * If one `write*()` method returns false, the encoding of the value failed - * and no further encodings can be performed on this [WireEncoder]. - * - * [flush] must be called to ensure that all data is written to the [Sink]. - */ -@OptIn(ExperimentalUnsignedTypes::class) -internal interface WireEncoder { - fun flush() - fun writeBool(fieldNr: Int, value: Boolean): Boolean - fun writeInt32(fieldNr: Int, value: Int): Boolean - fun writeInt64(fieldNr: Int, value: Long): Boolean - fun writeUInt32(fieldNr: Int, value: UInt): Boolean - fun writeUInt64(fieldNr: Int, value: ULong): Boolean - fun writeSInt32(fieldNr: Int, value: Int): Boolean - fun writeSInt64(fieldNr: Int, value: Long): Boolean - fun writeFixed32(fieldNr: Int, value: UInt): Boolean - fun writeFixed64(fieldNr: Int, value: ULong): Boolean - fun writeSFixed32(fieldNr: Int, value: Int): Boolean - fun writeSFixed64(fieldNr: Int, value: Long): Boolean - fun writeFloat(fieldNr: Int, value: Float): Boolean - fun writeDouble(fieldNr: Int, value: Double): Boolean - fun writeEnum(fieldNr: Int, value: Int): Boolean - fun writeBytes(fieldNr: Int, value: ByteArray): Boolean - fun writeString(fieldNr: Int, value: String): Boolean - fun writePackedBool(fieldNr: Int, value: List, fieldSize: Int): Boolean - fun writePackedInt32(fieldNr: Int, value: List, fieldSize: Int): Boolean - fun writePackedInt64(fieldNr: Int, value: List, fieldSize: Int): Boolean - fun writePackedUInt32(fieldNr: Int, value: List, fieldSize: Int): Boolean - fun writePackedUInt64(fieldNr: Int, value: List, fieldSize: Int): Boolean - fun writePackedSInt32(fieldNr: Int, value: List, fieldSize: Int): Boolean - fun writePackedSInt64(fieldNr: Int, value: List, fieldSize: Int): Boolean - fun writePackedFixed32(fieldNr: Int, value: List): Boolean - fun writePackedFixed64(fieldNr: Int, value: List): Boolean - fun writePackedSFixed32(fieldNr: Int, value: List): Boolean - fun writePackedSFixed64(fieldNr: Int, value: List): Boolean - fun writePackedFloat(fieldNr: Int, value: List): Boolean - fun writePackedDouble(fieldNr: Int, value: List): Boolean - fun writePackedEnum(fieldNr: Int, value: List, fieldSize: Int) = - writePackedInt32(fieldNr, value, fieldSize) -} - - -internal expect fun WireEncoder(sink: Sink): WireEncoder diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt similarity index 96% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.kt rename to grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt index 5eb26b278..a7c62e0a1 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt @@ -4,7 +4,7 @@ package kotlinx.rpc.grpc.internal -import kotlinx.io.Buffer +import kotlinx.rpc.grpc.pb.WireDecoder internal expect fun WireDecoder.pushLimit(byteLen: Int): Int internal expect fun WireDecoder.popLimit(limit: Int) diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt index 7ef050946..b72020bae 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt @@ -7,6 +7,10 @@ package kotlinx.rpc.grpc.pb import kotlinx.io.Buffer import kotlinx.rpc.internal.utils.InternalRpcApi +// TODO: Evaluate if this buffer size is suitable for all targets (KRPC-186) +// maximum buffer size to allocate as contiguous memory in bytes +internal const val MAX_PACKED_BULK_SIZE: Int = 1_000_000 + /** * A platform-specific decoder for wire format data. * diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt index d96164932..028040823 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt @@ -19,7 +19,7 @@ import kotlinx.rpc.internal.utils.InternalRpcApi @OptIn(ExperimentalUnsignedTypes::class) public interface WireEncoder { public fun flush() - public fun writeBool(field: Int, value: Boolean): Boolean + public fun writeBool(fieldNr: Int, value: Boolean): Boolean public fun writeInt32(fieldNr: Int, value: Int): Boolean public fun writeInt64(fieldNr: Int, value: Long): Boolean public fun writeUInt32(fieldNr: Int, value: UInt): Boolean diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt index c82ec44a1..a353026d3 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt @@ -4,21 +4,21 @@ package kotlinx.rpc.grpc.pb -internal object WireSize +public object WireSize -internal expect fun WireSize.int32(value: Int): Int -internal expect fun WireSize.int64(value: Long): Int -internal expect fun WireSize.uInt32(value: UInt): Int -internal expect fun WireSize.uInt64(value: ULong): Int -internal expect fun WireSize.sInt32(value: Int): Int -internal expect fun WireSize.sInt64(value: Long): Int +public expect fun WireSize.int32(value: Int): Int +public expect fun WireSize.int64(value: Long): Int +public expect fun WireSize.uInt32(value: UInt): Int +public expect fun WireSize.uInt64(value: ULong): Int +public expect fun WireSize.sInt32(value: Int): Int +public expect fun WireSize.sInt64(value: Long): Int -internal fun WireSize.bool(value: Boolean) = int32(if (value) 1 else 0) -internal fun WireSize.enum(value: Int) = int32(value) -internal fun WireSize.packedInt32(value: List) = value.sumOf { int32(it) } -internal fun WireSize.packedInt64(value: List) = value.sumOf { int64(it) } -internal fun WireSize.packedUInt32(value: List) = value.sumOf { uInt32(it) } -internal fun WireSize.packedUInt64(value: List) = value.sumOf { uInt64(it) } -internal fun WireSize.packedSInt32(value: List) = value.sumOf { sInt32(it) } -internal fun WireSize.packedSInt64(value: List) = value.sumOf { sInt64(it) } -internal fun WireSize.packedEnum(value: List) = value.sumOf { enum(it) } +public fun WireSize.bool(value: Boolean): Int = int32(if (value) 1 else 0) +public fun WireSize.enum(value: Int): Int = int32(value) +public fun WireSize.packedInt32(value: List): Int = value.sumOf { int32(it) } +public fun WireSize.packedInt64(value: List): Int = value.sumOf { int64(it) } +public fun WireSize.packedUInt32(value: List): Int = value.sumOf { uInt32(it) } +public fun WireSize.packedUInt64(value: List): Int = value.sumOf { uInt64(it) } +public fun WireSize.packedSInt32(value: List): Int = value.sumOf { sInt32(it) } +public fun WireSize.packedSInt64(value: List): Int = value.sumOf { sInt64(it) } +public fun WireSize.packedEnum(value: List): Int = value.sumOf { enum(it) } diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt similarity index 91% rename from grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt index 20ea6fb30..0e0330caa 100644 --- a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/TestProtos.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt @@ -5,7 +5,7 @@ package kotlinx.rpc.grpc.pb import kotlinx.io.Buffer -import kotlinx.rpc.grpc.test.* +import kotlinx.rpc.grpc.test.common.* import kotlin.test.Test import kotlin.test.assertEquals @@ -30,7 +30,7 @@ class TestProtos { @Test fun testAllPrimitiveProto() { - val msg = AllPrimitives { + val msg = AllPrimitivesCommon { int32 = 12 int64 = 1234567890123456789L uint32 = 12345u @@ -48,20 +48,20 @@ class TestProtos { bytes = byteArrayOf(1, 2, 3) } - val decoded = decodeEncode(msg, { encodeWith(it) }, AllPrimitives::decodeWith) + val decoded = decodeEncode(msg, { encodeWith(it) }, AllPrimitivesCommon::decodeWith) assertEquals(msg.double, decoded?.double) } @Test fun testRepeatedProto() { - val msg = Repeated { + val msg = RepeatedCommon { listFixed32 = listOf(1, 2, 3).map { it.toUInt() } listInt32 = listOf(4, 5, 6) listString = listOf("a", "b", "c") } - val decoded = decodeEncode(msg, { encodeWith(it) }, Repeated::decodeWith) + val decoded = decodeEncode(msg, { encodeWith(it) }, RepeatedCommon::decodeWith) assertEquals(msg.listInt32, decoded?.listInt32) assertEquals(msg.listFixed32, decoded?.listFixed32) diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.kt similarity index 99% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.kt index e54d2946b..16f56dea7 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.kt @@ -2,11 +2,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.internal +package kotlinx.rpc.grpc.pb import kotlinx.io.Buffer -import kotlinx.rpc.grpc.pb.* -import kotlin.experimental.ExperimentalNativeApi import kotlin.test.* enum class TestPlatform { diff --git a/grpc/grpc-core/src/nativeTest/proto/all_primitives.proto b/grpc/grpc-core/src/commonTest/proto/all_primitives.proto similarity index 83% rename from grpc/grpc-core/src/nativeTest/proto/all_primitives.proto rename to grpc/grpc-core/src/commonTest/proto/all_primitives.proto index be698f9e6..a5e3650ef 100644 --- a/grpc/grpc-core/src/nativeTest/proto/all_primitives.proto +++ b/grpc/grpc-core/src/commonTest/proto/all_primitives.proto @@ -1,8 +1,8 @@ syntax = "proto3"; -package kotlinx.rpc.grpc.test; +package kotlinx.rpc.grpc.test.common; -message AllPrimitives { +message AllPrimitivesCommon { double double = 1; float float = 2; int32 int32 = 3; diff --git a/grpc/grpc-core/src/nativeTest/proto/repeated.proto b/grpc/grpc-core/src/commonTest/proto/repeated.proto similarity index 75% rename from grpc/grpc-core/src/nativeTest/proto/repeated.proto rename to grpc/grpc-core/src/commonTest/proto/repeated.proto index 3e7f99227..5b459db9d 100644 --- a/grpc/grpc-core/src/nativeTest/proto/repeated.proto +++ b/grpc/grpc-core/src/commonTest/proto/repeated.proto @@ -1,10 +1,10 @@ syntax = "proto3"; -package kotlinx.rpc.grpc.test; +package kotlinx.rpc.grpc.test.common; //import 'reference_package.proto'; -message Repeated { +message RepeatedCommon { repeated fixed32 listFixed32 = 1 [packed = true]; repeated int32 listInt32 = 2 [packed = false]; repeated string listString = 3; diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt similarity index 100% rename from grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.js.kt rename to grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt similarity index 87% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.jvm.kt rename to grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt index 378339170..c8501d4f9 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt @@ -4,6 +4,9 @@ package kotlinx.rpc.grpc.internal +import kotlinx.rpc.grpc.pb.WireDecoder +import kotlinx.rpc.grpc.pb.WireDecoderJvm + internal actual fun WireDecoder.pushLimit(byteLen: Int): Int { return (this as WireDecoderJvm).codedInputStream.pushLimit(byteLen) } diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt index c1b25290d..1d27ead8c 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt @@ -7,6 +7,7 @@ package kotlinx.rpc.grpc.pb import com.google.protobuf.CodedInputStream import kotlinx.io.Buffer import kotlinx.io.asInputStream +import kotlinx.rpc.grpc.internal.readPackedVarInternal internal class WireDecoderJvm(source: Buffer) : WireDecoder { // there is no way to omit coping here diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt index 332d4847a..b357cd147 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt @@ -4,35 +4,30 @@ package kotlinx.rpc.grpc.pb -import com.google.protobuf.CodedOutputStream.computeInt32SizeNoTag -import com.google.protobuf.CodedOutputStream.computeInt64SizeNoTag -import com.google.protobuf.CodedOutputStream.computeUInt32SizeNoTag -import com.google.protobuf.CodedOutputStream.computeUInt64SizeNoTag -import com.google.protobuf.CodedOutputStream.computeSInt32SizeNoTag -import com.google.protobuf.CodedOutputStream.computeSInt64SizeNoTag - -internal actual fun WireSize.int32(value: Int): UInt { - return computeInt32SizeNoTag(value).toUInt() +import com.google.protobuf.CodedOutputStream.* + +public actual fun WireSize.int32(value: Int): Int { + return computeInt32SizeNoTag(value) } -internal actual fun WireSize.int64(value: Long): UInt { - return computeInt64SizeNoTag(value).toUInt() +public actual fun WireSize.int64(value: Long): Int { + return computeInt64SizeNoTag(value) } -internal actual fun WireSize.uInt32(value: UInt): UInt { +public actual fun WireSize.uInt32(value: UInt): Int { // todo check java unsigned types - return computeUInt32SizeNoTag(value.toInt()).toUInt() + return computeUInt32SizeNoTag(value.toInt()) } -internal actual fun WireSize.uInt64(value: ULong): UInt { +public actual fun WireSize.uInt64(value: ULong): Int { // todo check java unsigned types - return computeUInt64SizeNoTag(value.toLong()).toUInt() + return computeUInt64SizeNoTag(value.toLong()) } -internal actual fun WireSize.sInt32(value: Int): UInt { - return computeSInt32SizeNoTag(value).toUInt() +public actual fun WireSize.sInt32(value: Int): Int { + return computeSInt32SizeNoTag(value) } -internal actual fun WireSize.sInt64(value: Long): UInt { - return computeSInt64SizeNoTag(value).toUInt() +public actual fun WireSize.sInt64(value: Long): Int { + return computeSInt64SizeNoTag(value) } diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.jvm.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.jvm.kt new file mode 100644 index 000000000..88e83bb57 --- /dev/null +++ b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.jvm.kt @@ -0,0 +1,7 @@ +/* + * 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.pb + +actual val testPlatform: TestPlatform = TestPlatform.Jvm \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt index 21a58f0f5..a45418e62 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt @@ -7,6 +7,8 @@ package kotlinx.rpc.grpc.internal import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.rpc.grpc.pb.WireDecoder +import kotlinx.rpc.grpc.pb.WireDecoderNative import libprotowire.pw_decoder_bytes_until_limit import libprotowire.pw_decoder_pop_limit import libprotowire.pw_decoder_push_limit diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt index 00cba88fb..c5b22c918 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt @@ -8,6 +8,7 @@ import kotlinx.cinterop.* import kotlinx.collections.immutable.persistentListOf import kotlinx.io.Buffer import kotlinx.rpc.grpc.internal.ZeroCopyInputSource +import kotlinx.rpc.grpc.internal.readPackedVarInternal import libprotowire.* import kotlin.experimental.ExperimentalNativeApi import kotlin.math.min diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt index f286b3d99..71b86439c 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt @@ -9,10 +9,10 @@ package kotlinx.rpc.grpc.pb import kotlinx.cinterop.ExperimentalForeignApi import libprotowire.* -internal actual fun WireSize.int32(value: Int) = pw_size_int32(value).toInt() -internal actual fun WireSize.int64(value: Long) = pw_size_int64(value).toInt() -internal actual fun WireSize.uInt32(value: UInt) = pw_size_uint32(value).toInt() -internal actual fun WireSize.uInt64(value: ULong) = pw_size_uint64(value).toInt() -internal actual fun WireSize.sInt32(value: Int) = pw_size_sint32(value).toInt() -internal actual fun WireSize.sInt64(value: Long) = pw_size_sint64(value).toInt() +public actual fun WireSize.int32(value: Int): Int = pw_size_int32(value).toInt() +public actual fun WireSize.int64(value: Long): Int = pw_size_int64(value).toInt() +public actual fun WireSize.uInt32(value: UInt): Int = pw_size_uint32(value).toInt() +public actual fun WireSize.uInt64(value: ULong): Int = pw_size_uint64(value).toInt() +public actual fun WireSize.sInt32(value: Int): Int = pw_size_sint32(value).toInt() +public actual fun WireSize.sInt64(value: Long): Int = pw_size_sint64(value).toInt() diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.native.kt b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.native.kt similarity index 84% rename from grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.native.kt rename to grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.native.kt index a90765af4..23fb4eae1 100644 --- a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.native.kt +++ b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.native.kt @@ -2,6 +2,6 @@ * 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 +package kotlinx.rpc.grpc.pb actual val testPlatform: TestPlatform = TestPlatform.Native diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt similarity index 100% rename from grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/readPacked.wasmJs.kt rename to grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt index 91c5100f9..9117e317d 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt @@ -21,6 +21,9 @@ class RpcProtobufPlugin { companion object { private const val DEBUG_OUTPUT_OPTION = "debugOutput" private const val MESSAGE_MODE_OPTION = "messageMode" + + // if set to "common" we generate kotlin common source code + private const val TARGET_MODE_OPTION = "targetMode" } enum class MessageMode { @@ -40,6 +43,7 @@ class RpcProtobufPlugin { private var debugOutput: String? = null private lateinit var messageGenerationMode: MessageMode + private var targetCommon: Boolean = false private val logger: Logger by lazy { val debugOutput = debugOutput ?: return@lazy NOPLogger.NOP_LOGGER @@ -71,6 +75,7 @@ class RpcProtobufPlugin { debugOutput = parameters[DEBUG_OUTPUT_OPTION] messageGenerationMode = MessageMode.of(parameters[MESSAGE_MODE_OPTION]) + targetCommon = parameters[TARGET_MODE_OPTION] == "common" val files = input.generateKotlinFiles() .map { file -> @@ -102,7 +107,16 @@ class RpcProtobufPlugin { private fun CodeGeneratorRequest.generateKotlinFiles(): List { val interpreter = ProtoToModelInterpreter(logger) val model = interpreter.interpretProtocRequest(this) - val fileGenerator = ModelToKotlinCommonGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) - return fileGenerator.generateKotlinFiles() + + // choose common generator if targetMode option was set. + if (targetCommon) { + val fileGenerator = + ModelToKotlinCommonGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) + return fileGenerator.generateKotlinFiles() + } else { + val fileGenerator = + ModelToKotlinJvmGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) + return fileGenerator.generateKotlinFiles() + } } } From ce3b69fa7a257b528baf8883e061081eef9d06db Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Wed, 30 Jul 2025 16:44:08 +0200 Subject: [PATCH 5/6] grpc-native: Address PR comments Signed-off-by: Johannes Zottele --- .../rpc/proto/DefaultProtoSourceSet.kt | 4 ++- grpc/grpc-core/build.gradle.kts | 4 +-- .../kotlin/kotlinx/rpc/grpc/pb/KTag.kt | 4 +++ .../kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt | 2 +- .../kotlin/kotlinx/rpc/grpc/pb/WireSize.kt | 31 +++++++++++++++++++ .../src/commonTest/proto/repeated.proto | 2 -- .../kotlinx/rpc/grpc/pb/WireSize.jvm.kt | 7 +++++ .../kotlinx/rpc/grpc/pb/WireSize.native.kt | 13 ++++++++ 8 files changed, 61 insertions(+), 6 deletions(-) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt index 165f8a914..81822424a 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt @@ -98,9 +98,11 @@ internal fun Project.createProtoExtensions() { project.withKotlinKmpExtension { findOrCreateAndConfigure("jvmMain", null) findOrCreateAndConfigure("jvmTest", null) + findOrCreateAndConfigure("commonMain", null) + findOrCreateAndConfigure("commonTest", null) sourceSets.configureEach { - if (name == "jvmMain" || name == "jvmTest" || name == "nativeTest" || name == "commonTest") { + if (name == "jvmMain" || name == "jvmTest" || name == "commonMain" || name == "commonTest") { findOrCreateAndConfigure(name, this) } } diff --git a/grpc/grpc-core/build.gradle.kts b/grpc/grpc-core/build.gradle.kts index ac9270ffa..2c74b1d5e 100644 --- a/grpc/grpc-core/build.gradle.kts +++ b/grpc/grpc-core/build.gradle.kts @@ -139,7 +139,7 @@ protoSourceSets { } } - configureEach { + commonTest { proto { exclude("exclude/**") } @@ -163,7 +163,7 @@ rpc { // Set compile for common(native) option if (name.endsWith("CommonTest")) { protocPlugins.kotlinMultiplatform { - options.set(options.getOrElse(emptyMap()) + mapOf("targetMode" to "common")) + options.put("targetMode", "common") } } diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt index 24b331fbf..d60f0f6a8 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt @@ -4,6 +4,9 @@ package kotlinx.rpc.grpc.pb +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi public enum class WireType { VARINT, // 0 FIXED64, // 1 @@ -13,6 +16,7 @@ public enum class WireType { FIXED32, // 5 } +@InternalRpcApi public data class KTag(val fieldNr: Int, val wireType: WireType) { init { diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt index b72020bae..167ad9d1e 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt @@ -42,7 +42,7 @@ public interface WireDecoder : AutoCloseable { public fun hadError(): Boolean /** - * When the read tag is null, it indicates EOF and the parse may stop at this point. + * When the read tag is null, it indicates EOF and the parser may stop at this point. */ public fun readTag(): KTag? public fun readBool(): Boolean diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt index a353026d3..50f957c1b 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt @@ -4,21 +4,52 @@ package kotlinx.rpc.grpc.pb +import kotlinx.rpc.internal.utils.InternalRpcApi + +@InternalRpcApi public object WireSize +@InternalRpcApi public expect fun WireSize.int32(value: Int): Int + +@InternalRpcApi public expect fun WireSize.int64(value: Long): Int + +@InternalRpcApi public expect fun WireSize.uInt32(value: UInt): Int + +@InternalRpcApi public expect fun WireSize.uInt64(value: ULong): Int + +@InternalRpcApi public expect fun WireSize.sInt32(value: Int): Int + +@InternalRpcApi public expect fun WireSize.sInt64(value: Long): Int +@InternalRpcApi public fun WireSize.bool(value: Boolean): Int = int32(if (value) 1 else 0) + +@InternalRpcApi public fun WireSize.enum(value: Int): Int = int32(value) + +@InternalRpcApi public fun WireSize.packedInt32(value: List): Int = value.sumOf { int32(it) } + +@InternalRpcApi public fun WireSize.packedInt64(value: List): Int = value.sumOf { int64(it) } + +@InternalRpcApi public fun WireSize.packedUInt32(value: List): Int = value.sumOf { uInt32(it) } + +@InternalRpcApi public fun WireSize.packedUInt64(value: List): Int = value.sumOf { uInt64(it) } + +@InternalRpcApi public fun WireSize.packedSInt32(value: List): Int = value.sumOf { sInt32(it) } + +@InternalRpcApi public fun WireSize.packedSInt64(value: List): Int = value.sumOf { sInt64(it) } + +@InternalRpcApi public fun WireSize.packedEnum(value: List): Int = value.sumOf { enum(it) } diff --git a/grpc/grpc-core/src/commonTest/proto/repeated.proto b/grpc/grpc-core/src/commonTest/proto/repeated.proto index 5b459db9d..002e421eb 100644 --- a/grpc/grpc-core/src/commonTest/proto/repeated.proto +++ b/grpc/grpc-core/src/commonTest/proto/repeated.proto @@ -2,8 +2,6 @@ syntax = "proto3"; package kotlinx.rpc.grpc.test.common; -//import 'reference_package.proto'; - message RepeatedCommon { repeated fixed32 listFixed32 = 1 [packed = true]; repeated int32 listInt32 = 2 [packed = false]; diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt index b357cd147..71896a28e 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt @@ -5,29 +5,36 @@ package kotlinx.rpc.grpc.pb import com.google.protobuf.CodedOutputStream.* +import kotlinx.rpc.internal.utils.InternalRpcApi +@InternalRpcApi public actual fun WireSize.int32(value: Int): Int { return computeInt32SizeNoTag(value) } +@InternalRpcApi public actual fun WireSize.int64(value: Long): Int { return computeInt64SizeNoTag(value) } +@InternalRpcApi public actual fun WireSize.uInt32(value: UInt): Int { // todo check java unsigned types return computeUInt32SizeNoTag(value.toInt()) } +@InternalRpcApi public actual fun WireSize.uInt64(value: ULong): Int { // todo check java unsigned types return computeUInt64SizeNoTag(value.toLong()) } +@InternalRpcApi public actual fun WireSize.sInt32(value: Int): Int { return computeSInt32SizeNoTag(value) } +@InternalRpcApi public actual fun WireSize.sInt64(value: Long): Int { return computeSInt64SizeNoTag(value) } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt index 71b86439c..72ccaddd6 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt @@ -7,12 +7,25 @@ package kotlinx.rpc.grpc.pb import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.rpc.internal.utils.InternalRpcApi import libprotowire.* +@InternalRpcApi public actual fun WireSize.int32(value: Int): Int = pw_size_int32(value).toInt() + +@InternalRpcApi public actual fun WireSize.int64(value: Long): Int = pw_size_int64(value).toInt() + +@InternalRpcApi public actual fun WireSize.uInt32(value: UInt): Int = pw_size_uint32(value).toInt() + +@InternalRpcApi public actual fun WireSize.uInt64(value: ULong): Int = pw_size_uint64(value).toInt() + +@InternalRpcApi public actual fun WireSize.sInt32(value: Int): Int = pw_size_sint32(value).toInt() + +@InternalRpcApi public actual fun WireSize.sInt64(value: Long): Int = pw_size_sint64(value).toInt() + From eaa24faa8891d85e56a4bbb9ebc17f68151a3d16 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Wed, 30 Jul 2025 16:46:12 +0200 Subject: [PATCH 6/6] grpc-native: Rename ProtosTest class Signed-off-by: Johannes Zottele --- .../src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0e0330caa..49f33c1ad 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 @@ -9,7 +9,7 @@ import kotlinx.rpc.grpc.test.common.* import kotlin.test.Test import kotlin.test.assertEquals -class TestProtos { +class ProtosTest { private fun decodeEncode( msg: T,