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..81822424a 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 @@ -102,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") { + 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 47fe51902..2c74b1d5e 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/**") } } + + commonTest { + 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.put("targetMode", "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/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/internal/WireSize.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.kt deleted file mode 100644 index 161a4fe92..000000000 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.kt +++ /dev/null @@ -1,24 +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 - -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 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) } 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/internal/readPacked.kt index 5eb26b278..a7c62e0a1 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/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/internal/KTag.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt similarity index 76% 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..d60f0f6a8 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,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.rpc.grpc.internal.KTag.Companion.K_TAG_TYPE_BITS +import kotlinx.rpc.internal.utils.InternalRpcApi -internal enum class WireType { +@InternalRpcApi +public enum class WireType { VARINT, // 0 FIXED64, // 1 LENGTH_DELIMITED, // 2 @@ -15,13 +16,14 @@ internal enum class WireType { FIXED32, // 5 } -internal data class KTag(val fieldNr: Int, val wireType: WireType) { +@InternalRpcApi +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 +33,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/internal/WireDecoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt similarity index 51% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt rename to grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt index 64cbfd1d9..167ad9d1e 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt @@ -2,9 +2,10 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.rpc.grpc.internal +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 @@ -36,40 +37,45 @@ internal const val MAX_PACKED_BULK_SIZE: Int = 1_000_000 * } * ``` */ -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 +@InternalRpcApi +public interface WireDecoder : AutoCloseable { + public fun hadError(): Boolean - 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 + /** + * 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 + 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 } /** @@ -82,4 +88,4 @@ internal interface WireDecoder : AutoCloseable { * * @param source The buffer containing the encoded wire-format data. */ -internal expect fun WireDecoder(source: Buffer): WireDecoder +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..028040823 --- /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(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 + 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/pb/WireSize.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt new file mode 100644 index 000000000..50f957c1b --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt @@ -0,0 +1,55 @@ +/* + * 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.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/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt new file mode 100644 index 000000000..49f33c1ad --- /dev/null +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt @@ -0,0 +1,71 @@ +/* + * 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.common.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class ProtosTest { + + 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 = AllPrimitivesCommon { + 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) }, AllPrimitivesCommon::decodeWith) + + assertEquals(msg.double, decoded?.double) + } + + @Test + fun testRepeatedProto() { + 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) }, RepeatedCommon::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/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 577b90c42..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,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.Buffer import kotlin.test.* @@ -732,13 +732,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/commonTest/proto/all_primitives.proto b/grpc/grpc-core/src/commonTest/proto/all_primitives.proto new file mode 100644 index 000000000..a5e3650ef --- /dev/null +++ b/grpc/grpc-core/src/commonTest/proto/all_primitives.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test.common; + +message AllPrimitivesCommon { + 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/commonTest/proto/repeated.proto b/grpc/grpc-core/src/commonTest/proto/repeated.proto new file mode 100644 index 000000000..002e421eb --- /dev/null +++ b/grpc/grpc-core/src/commonTest/proto/repeated.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package kotlinx.rpc.grpc.test.common; + +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/internal/WireSize.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.js.kt deleted file mode 100644 index e70b9bd00..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.js.kt +++ /dev/null @@ -1,29 +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 - -internal actual fun WireSize.int32(value: Int): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.int64(value: Long): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.uInt32(value: UInt): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.uInt64(value: ULong): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.sInt32(value: Int): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.sInt64(value: Long): UInt { - TODO("Not yet implemented") -} \ No newline at end of file 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/pb/WireSize.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt new file mode 100644 index 000000000..041674e94 --- /dev/null +++ b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt @@ -0,0 +1,29 @@ +/* + * 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 + +internal actual fun WireSize.int32(value: Int): Int { + TODO("Not yet implemented") +} + +internal actual fun WireSize.int64(value: Long): Int { + TODO("Not yet implemented") +} + +internal actual fun WireSize.uInt32(value: UInt): Int { + TODO("Not yet implemented") +} + +internal actual fun WireSize.uInt64(value: ULong): Int { + TODO("Not yet implemented") +} + +internal actual fun WireSize.sInt32(value: Int): Int { + TODO("Not yet implemented") +} + +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/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt deleted file mode 100644 index 3697c3d91..000000000 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.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.internal - -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() -} - -internal actual fun WireSize.int64(value: Long): UInt { - return computeInt64SizeNoTag(value).toUInt() -} - -internal actual fun WireSize.uInt32(value: UInt): UInt { - // todo check java unsigned types - return computeUInt32SizeNoTag(value.toInt()).toUInt() -} - -internal actual fun WireSize.uInt64(value: ULong): UInt { - // todo check java unsigned types - return computeUInt64SizeNoTag(value.toLong()).toUInt() -} - -internal actual fun WireSize.sInt32(value: Int): UInt { - return computeSInt32SizeNoTag(value).toUInt() -} - -internal actual fun WireSize.sInt64(value: Long): UInt { - return computeSInt64SizeNoTag(value).toUInt() -} 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/internal/readPacked.jvm.kt index 378339170..c8501d4f9 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/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/internal/WireDecoder.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt similarity index 97% 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..1d27ead8c 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,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 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/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/pb/WireSize.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt new file mode 100644 index 000000000..71896a28e --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt @@ -0,0 +1,40 @@ +/* + * 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 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/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/WireSize.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.native.kt deleted file mode 100644 index 432479129..000000000 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.native.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:OptIn(ExperimentalForeignApi::class) - -package kotlinx.rpc.grpc.internal - -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) - 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/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..c5b22c918 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,13 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.rpc.grpc.internal +package kotlinx.rpc.grpc.pb 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 @@ -65,12 +67,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/pb/WireSize.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt new file mode 100644 index 000000000..72ccaddd6 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:OptIn(ExperimentalForeignApi::class) + +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() + + 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/internal/WireSize.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.wasmJs.kt deleted file mode 100644 index e70b9bd00..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.wasmJs.kt +++ /dev/null @@ -1,29 +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 - -internal actual fun WireSize.int32(value: Int): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.int64(value: Long): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.uInt32(value: UInt): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.uInt64(value: ULong): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.sInt32(value: Int): UInt { - TODO("Not yet implemented") -} - -internal actual fun WireSize.sInt64(value: Long): UInt { - TODO("Not yet implemented") -} \ No newline at end of file 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/pb/WireSize.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt new file mode 100644 index 000000000..041674e94 --- /dev/null +++ b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt @@ -0,0 +1,29 @@ +/* + * 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 + +internal actual fun WireSize.int32(value: Int): Int { + TODO("Not yet implemented") +} + +internal actual fun WireSize.int64(value: Long): Int { + TODO("Not yet implemented") +} + +internal actual fun WireSize.uInt32(value: UInt): Int { + TODO("Not yet implemented") +} + +internal actual fun WireSize.uInt64(value: ULong): Int { + TODO("Not yet implemented") +} + +internal actual fun WireSize.sInt32(value: Int): Int { + TODO("Not yet implemented") +} + +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/CodeGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt index 4969e5392..006faee13 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,38 @@ 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? = null, + block: (CodeGenerator.() -> Unit), + ) { + val cond = condition?.let { " ($it)" } ?: "" + scope("when$cond", 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..3a1e20198 --- /dev/null +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt @@ -0,0 +1,468 @@ +/* + * 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.pb.*") + + + 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 { + declaration.fields().forEach { (_, field) -> readMatchCase(field) } + 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.readMatchCase(field: FieldDeclaration) { + val encFuncName = field.type.decodeEncodeFuncName() + val assignment = "msg.${field.name} =" + when (val fieldType = field.type) { + 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("tag.fieldNr == ${field.number} && tag.wireType == WireType.LENGTH_DELIMITED") { + code("$assignment decoder.readPacked${fieldType.value.decodeEncodeFuncName()}()") + } + } else { + whenCase("tag.fieldNr == ${field.number} && 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", + contextReceiver = declaration.name.safeFullName(), + ) { + + declaration.fields().forEach { (_, field) -> + val fieldName = field.name + if (field.nullable) { + scope("$fieldName?.also") { + code(field.writeValue()) + } + } else if (!field.hasPresence) { + ifBranch(condition = field.defaultCheck(), ifBlock = { + code(field.writeValue()) + }) + } else { + 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") + } + } + + 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..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 = ModelToKotlinGenerator(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() + } } } 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..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 @@ -4,51 +4,94 @@ 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 +) { + 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, + FIXED64, + LENGTH_DELIMITED, + START_GROUP, + END_GROUP, + FIXED32 +} 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 { 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, + override val isPackable: Boolean = true + ) : + FieldType { + 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", "0L", WireType.VARINT), + UINT32("UInt", "0u", WireType.VARINT), + UINT64("ULong", "0uL", WireType.VARINT), + FIXED32("UInt", "0u", WireType.FIXED32), + FIXED64("ULong", "0uL", WireType.FIXED64), + SINT32("Int", "0", WireType.VARINT), + SINT64("Long", "0L", WireType.VARINT), + SFIXED32("Int", "0", WireType.FIXED32), + SFIXED64("Long", "0L", WireType.FIXED64); val fqName: FqName = FqName.Declaration(simpleName, FqName.Package.fromString("kotlin")) }