From 51862c2e089918e5bd3a3df9fc3272bd12d05495 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Thu, 31 Jul 2025 16:57:48 +0200 Subject: [PATCH 01/11] grpc: Generate model from Descriptor instead of DescriptorProto Signed-off-by: Johannes Zottele --- .../src/commonTest/proto/all_primitives.proto | 18 +- .../src/commonTest/proto/repeated.proto | 4 + .../src/jvmTest/proto/repeated.proto | 1 + .../kotlinx/rpc/protobuf/DescriptorToModel.kt | 241 ++++++++++++++++++ .../protobuf/ModelToKotlinCommonGenerator.kt | 20 +- .../kotlinx/rpc/protobuf/ProtoToDescriptor.kt | 79 ++++++ .../rpc/protobuf/ProtoToModelInterpreter.kt | 3 +- .../kotlinx/rpc/protobuf/RpcProtobufPlugin.kt | 116 ++++++++- .../rpc/protobuf/model/OneOfDeclaration.kt | 3 + 9 files changed, 456 insertions(+), 29 deletions(-) create mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/DescriptorToModel.kt create mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToDescriptor.kt diff --git a/grpc/grpc-core/src/commonTest/proto/all_primitives.proto b/grpc/grpc-core/src/commonTest/proto/all_primitives.proto index a5e3650ef..14772b74a 100644 --- a/grpc/grpc-core/src/commonTest/proto/all_primitives.proto +++ b/grpc/grpc-core/src/commonTest/proto/all_primitives.proto @@ -9,13 +9,13 @@ message AllPrimitivesCommon { 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 sint32 sint32 = 7; + optional sint64 sint64 = 8; + optional fixed32 fixed32 = 9; + optional fixed64 fixed64 = 10; + optional sfixed32 sfixed32 = 11; + optional sfixed64 sfixed64 = 12; + optional bool bool = 13; + optional string string = 14; + optional bytes bytes = 15; } diff --git a/grpc/grpc-core/src/commonTest/proto/repeated.proto b/grpc/grpc-core/src/commonTest/proto/repeated.proto index 002e421eb..55d1a2a19 100644 --- a/grpc/grpc-core/src/commonTest/proto/repeated.proto +++ b/grpc/grpc-core/src/commonTest/proto/repeated.proto @@ -6,4 +6,8 @@ message RepeatedCommon { repeated fixed32 listFixed32 = 1 [packed = true]; repeated int32 listInt32 = 2 [packed = false]; repeated string listString = 3; + + message InnerClass { + + } } diff --git a/grpc/grpc-core/src/jvmTest/proto/repeated.proto b/grpc/grpc-core/src/jvmTest/proto/repeated.proto index c7256377a..eff1a7382 100644 --- a/grpc/grpc-core/src/jvmTest/proto/repeated.proto +++ b/grpc/grpc-core/src/jvmTest/proto/repeated.proto @@ -10,3 +10,4 @@ message Repeated { repeated string listString = 3; repeated References listReference = 4; } + diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/DescriptorToModel.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/DescriptorToModel.kt new file mode 100644 index 000000000..d7a938060 --- /dev/null +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/DescriptorToModel.kt @@ -0,0 +1,241 @@ +/* + * 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.protobuf + +import com.google.protobuf.DescriptorProtos +import com.google.protobuf.Descriptors +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest +import kotlinx.rpc.protobuf.model.* + +private val modelCache = mutableMapOf() + +fun CodeGeneratorRequest.toCommonModel(): Model { + val protoFileMap = protoFileList.associateBy { it.name } + val fileDescriptors = mutableMapOf() + + val files = protoFileList.map { protoFile -> protoFile.toDescriptor(protoFileMap, fileDescriptors) } + + return Model( + files = files.map { it.toCommonModel() } + ) +} + +private inline fun D.cached(block: (D) -> T): T + where D : Descriptors.GenericDescriptor, T : Any { + if (modelCache.containsKey(this)) { + return modelCache[this] as T + } + val declaration = block(this) + modelCache[this] = declaration + return declaration +} + +private fun Descriptors.FileDescriptor.toCommonModel(): FileDeclaration = cached { + return FileDeclaration( + name = kotlinFileName(), + packageName = FqName.Package.fromString(`package`), + dependencies = dependencies.map { it.toCommonModel() }, + messageDeclarations = messageTypes.map { it.toCommonModel() }, + enumDeclarations = enumTypes.map { it.toCommonModel() }, + serviceDeclarations = services.map { it.toCommonModel() }, + deprecated = options.deprecated, + doc = null, + ) +} + +private fun Descriptors.Descriptor.toCommonModel(): MessageDeclaration = cached { + val regularFields = fields.filter { field -> field.realContainingOneof == null }.map { it.toCommonModel() } + + return MessageDeclaration( + outerClassName = fqName(), + name = fqName(), + actualFields = regularFields, + // get all oneof declarations that are not created from an optional in proto3 https://github.com/googleapis/api-linter/issues/1323 + oneOfDeclarations = oneofs.filter { it.fields[0].realContainingOneof != null }.map { it.toCommonModel() }, + enumDeclarations = enumTypes.map { it.toCommonModel() }, + nestedDeclarations = nestedTypes.map { it.toCommonModel() }, + deprecated = options.deprecated, + doc = null, + ) +} + +private fun Descriptors.FieldDescriptor.toCommonModel(): FieldDeclaration = cached { + toProto().hasProto3Optional() + return FieldDeclaration( + name = fqName().simpleName, + number = number, + type = modelType(), + nullable = isNullable(), + deprecated = options.deprecated, + doc = null, + proto = toProto(), + ) +} + +private fun Descriptors.FieldDescriptor.isNullable(): Boolean { + // aligns with edition settings and backward compatibility with proto2 and proto3 + return hasPresence() && !isRequired && !hasDefaultValue() +} + +private fun Descriptors.OneofDescriptor.toCommonModel(): OneOfDeclaration = cached { + return OneOfDeclaration( + name = fqName(), + variants = fields.map { it.toCommonModel() }, + descriptor = this + ) +} + +private fun Descriptors.EnumDescriptor.toCommonModel(): EnumDeclaration = cached { + val entriesMap = mutableMapOf() + val aliases = mutableListOf() + + values.forEach { value -> + if (entriesMap.containsKey(value.number)) { + val original = entriesMap.getValue(value.number) + aliases.add(value.toAliasModel(original)) + } else { + entriesMap[value.number] = value.toCommonModel() + } + } + + if (!options.allowAlias && aliases.isNotEmpty()) { + error("Enum ${fullName} has aliases: ${aliases.joinToString { it.name.simpleName }}") + } + + return EnumDeclaration( + outerClassName = fqName(), + name = fqName(), + originalEntries = entriesMap.values.toList(), + aliases = aliases, + deprecated = options.deprecated, + doc = null + ) +} + +private fun Descriptors.EnumValueDescriptor.toCommonModel(): EnumDeclaration.Entry = cached { + return EnumDeclaration.Entry( + name = fqName(), + deprecated = options.deprecated, + doc = null, + ) +} + +// no caching, as it would conflict with .toModel +private fun Descriptors.EnumValueDescriptor.toAliasModel(original: EnumDeclaration.Entry): EnumDeclaration.Alias { + return EnumDeclaration.Alias( + name = fqName(), + original = original, + deprecated = options.deprecated, + doc = null, + ) +} + +private fun Descriptors.ServiceDescriptor.toCommonModel(): ServiceDeclaration = cached { + return ServiceDeclaration( + name = fqName(), + methods = methods.map { it.toCommonModel() } + ) +} + +private fun Descriptors.MethodDescriptor.toCommonModel(): MethodDeclaration = cached { + return MethodDeclaration( + name = name, + clientStreaming = isClientStreaming, + serverStreaming = isServerStreaming, + inputType = lazy { inputType.toCommonModel() }, + outputType = lazy { outputType.toCommonModel() } + ) +} + + +private fun DescriptorProtos.FileDescriptorProto.toDescriptor( + protoFileMap: Map, + cache: MutableMap +): Descriptors.FileDescriptor { + if (cache.containsKey(name)) return cache[name]!! + + val dependencies = dependencyList.map { depName -> + val depProto = protoFileMap[depName] ?: error("Missing dependency: $depName") + depProto.toDescriptor(protoFileMap, cache) + }.toTypedArray() + + val fileDescriptor = Descriptors.FileDescriptor.buildFrom(this, dependencies) + cache[name] = fileDescriptor + return fileDescriptor +} + +//// Type Conversion Extension //// + +private fun Descriptors.FieldDescriptor.modelType(): FieldType { + val baseType = when (type) { + Descriptors.FieldDescriptor.Type.DOUBLE -> FieldType.IntegralType.DOUBLE + Descriptors.FieldDescriptor.Type.FLOAT -> FieldType.IntegralType.FLOAT + Descriptors.FieldDescriptor.Type.INT64 -> FieldType.IntegralType.INT64 + Descriptors.FieldDescriptor.Type.UINT64 -> FieldType.IntegralType.UINT64 + Descriptors.FieldDescriptor.Type.INT32 -> FieldType.IntegralType.INT32 + Descriptors.FieldDescriptor.Type.FIXED64 -> FieldType.IntegralType.FIXED64 + Descriptors.FieldDescriptor.Type.FIXED32 -> FieldType.IntegralType.FIXED32 + Descriptors.FieldDescriptor.Type.BOOL -> FieldType.IntegralType.BOOL + Descriptors.FieldDescriptor.Type.STRING -> FieldType.IntegralType.STRING + Descriptors.FieldDescriptor.Type.BYTES -> FieldType.IntegralType.BYTES + Descriptors.FieldDescriptor.Type.UINT32 -> FieldType.IntegralType.UINT32 + Descriptors.FieldDescriptor.Type.SFIXED32 -> FieldType.IntegralType.SFIXED32 + Descriptors.FieldDescriptor.Type.SFIXED64 -> FieldType.IntegralType.SFIXED64 + Descriptors.FieldDescriptor.Type.SINT32 -> FieldType.IntegralType.SINT32 + Descriptors.FieldDescriptor.Type.SINT64 -> FieldType.IntegralType.SINT64 + Descriptors.FieldDescriptor.Type.ENUM -> FieldType.Reference(lazy { enumType!!.toCommonModel().name }) + Descriptors.FieldDescriptor.Type.MESSAGE -> FieldType.Reference(lazy { messageType!!.toCommonModel().name }) + Descriptors.FieldDescriptor.Type.GROUP -> error("GROUP type is unsupported") + } + + if (isRepeated) { + return FieldType.List(baseType) + } + + // TODO: Handle map type + + return baseType +} + +//// Utility Extensions //// + +private fun Descriptors.FileDescriptor.kotlinFileName(): String { + return "${protoFileNameToKotlinName()}.kt" +} + +private fun Descriptors.FileDescriptor.protoFileNameToKotlinName(): String { + return name.removeSuffix(".proto").fullProtoNameToKotlin(firstLetterUpper = true) +} + + +private fun String.fullProtoNameToKotlin(firstLetterUpper: Boolean = false): String { + val lastDelimiterIndex = indexOfLast { it == '.' || it == '/' } + return if (lastDelimiterIndex != -1) { + val packageName = substring(0, lastDelimiterIndex) + val name = substring(lastDelimiterIndex + 1) + val delimiter = this[lastDelimiterIndex] + return "$packageName$delimiter${name.simpleProtoNameToKotlin(firstLetterUpper = true)}" + } else { + simpleProtoNameToKotlin(firstLetterUpper) + } +} + + +private val snakeRegExp = "(_[a-z]|-[a-z])".toRegex() + +private fun String.snakeToCamelCase(): String { + return replace(snakeRegExp) { it.value.last().uppercase() } +} + +private fun String.simpleProtoNameToKotlin(firstLetterUpper: Boolean = false): String { + return snakeToCamelCase().run { + if (firstLetterUpper) { + replaceFirstChar { it.uppercase() } + } else { + this + } + } +} + 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 3a1e20198..f4b92815c 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt @@ -239,30 +239,34 @@ class ModelToKotlinCommonGenerator( val fieldName = field.name if (field.nullable) { scope("$fieldName?.also") { - code(field.writeValue()) + code(field.writeValue("it")) } } else if (!field.hasPresence) { ifBranch(condition = field.defaultCheck(), ifBlock = { - code(field.writeValue()) + code(field.writeValue(field.name)) }) } else { - code(field.writeValue()) + code(field.writeValue(field.name)) } } } - private fun FieldDeclaration.writeValue(): String { + private fun FieldDeclaration.writeValue(variable: String): String { return when (val fieldType = type) { - is FieldType.IntegralType -> "encoder.write${type.decodeEncodeFuncName()}($number, $name)" + is FieldType.IntegralType -> "encoder.write${type.decodeEncodeFuncName()}($number, $variable)" is FieldType.List -> when { packed && packedFixedSize -> - "encoder.writePacked${fieldType.value.decodeEncodeFuncName()}($number, $name)" + "encoder.writePacked${fieldType.value.decodeEncodeFuncName()}($number, $variable)" packed && !packedFixedSize -> - "encoder.writePacked${fieldType.value.decodeEncodeFuncName()}($number, $name, ${wireSizeCall(name)})" + "encoder.writePacked${fieldType.value.decodeEncodeFuncName()}($number, $variable, ${ + wireSizeCall( + variable + ) + })" else -> - "$name.forEach { encoder.write${fieldType.value.decodeEncodeFuncName()}($number, it) }" + "$variable.forEach { encoder.write${fieldType.value.decodeEncodeFuncName()}($number, it) }" } is FieldType.Map -> TODO() diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToDescriptor.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToDescriptor.kt new file mode 100644 index 000000000..265cf9652 --- /dev/null +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToDescriptor.kt @@ -0,0 +1,79 @@ +/* + * 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.protobuf + +import com.google.protobuf.DescriptorProtos +import com.google.protobuf.Descriptors +import kotlinx.rpc.protobuf.model.FqName +import kotlinx.rpc.protobuf.model.NameResolver + +private fun DescriptorProtos.FileDescriptorProto.toDescriptor( + protoFileMap: Map, + cache: MutableMap +): Descriptors.FileDescriptor { + if (cache.containsKey(name)) return cache[name]!! + + val dependencies = dependencyList.map { depName -> + val depProto = protoFileMap[depName] ?: error("Missing dependency: $depName") + depProto.toDescriptor(protoFileMap, cache) + }.toTypedArray() + + val fileDescriptor = Descriptors.FileDescriptor.buildFrom(this, dependencies) + cache[name] = fileDescriptor + return fileDescriptor +} + +//// GenericDescriptor Extensions //// + +fun Descriptors.GenericDescriptor.fqName(): FqName { + val nameCapital = name.simpleProtoNameToKotlin(firstLetterUpper = true) + val nameLower = name.simpleProtoNameToKotlin() + return when (this) { + is Descriptors.FileDescriptor -> FqName.Package.fromString(`package`) + is Descriptors.Descriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) + is Descriptors.FieldDescriptor -> { + val usedName = if (realContainingOneof != null) nameCapital else nameLower + FqName.Declaration(usedName, containingType?.fqName() ?: file.fqName()) + } + + is Descriptors.EnumValueDescriptor -> FqName.Declaration(name, type.fqName()) + is Descriptors.OneofDescriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) + is Descriptors.ServiceDescriptor -> FqName.Declaration(nameCapital, file?.fqName() ?: file.fqName()) + is Descriptors.MethodDescriptor -> FqName.Declaration(nameLower, service?.fqName() ?: file.fqName()) + else -> error("Unknown generic descriptor: $this") + } +} + +//// Descriptor Extensions //// + +fun Descriptors.Descriptor.deprecated(): Boolean { + return options.deprecated +} + +fun Descriptors.FieldDescriptor.test() { + this +} + +//// Utility Extensions //// + +private val snakeRegExp = "(_[a-z]|-[a-z])".toRegex() + +private fun String.snakeToCamelCase(): String { + return replace(snakeRegExp) { it.value.last().uppercase() } +} + +private fun String.simpleProtoNameToKotlin(firstLetterUpper: Boolean = false): String { + return snakeToCamelCase().run { + if (firstLetterUpper) { + replaceFirstChar { it.uppercase() } + } else { + this + } + } +} + +private fun NameResolver.declarationFqName(simpleName: String, parent: FqName): FqName.Declaration { + return FqName.Declaration(simpleName, parent).also { add(it) } +} 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 f101d1bd2..e878d0647 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt @@ -289,7 +289,8 @@ class ProtoToModelInterpreter( doc = null, proto = field, ) - } + }, + null ) } 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 9117e317d..3709bd2e1 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt @@ -9,9 +9,14 @@ import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.encoder.PatternLayoutEncoder import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.FileAppender +import com.google.protobuf.Descriptors import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.Feature +import kotlinx.rpc.protobuf.model.FieldDeclaration +import kotlinx.rpc.protobuf.model.FileDeclaration +import kotlinx.rpc.protobuf.model.MessageDeclaration +import kotlinx.rpc.protobuf.model.Model import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.helpers.NOPLogger @@ -77,7 +82,14 @@ class RpcProtobufPlugin { messageGenerationMode = MessageMode.of(parameters[MESSAGE_MODE_OPTION]) targetCommon = parameters[TARGET_MODE_OPTION] == "common" - val files = input.generateKotlinFiles() + // choose common generator if targetMode option was set. + val generatorFn = if (targetCommon) { + { input.generateKotlinCommonFiles() } + } else { + { input.generateKotlinJvmFiles() } + } + + val files = generatorFn() .map { file -> CodeGeneratorResponse.File.newBuilder() .apply { @@ -104,19 +116,101 @@ class RpcProtobufPlugin { .build() } - private fun CodeGeneratorRequest.generateKotlinFiles(): List { + private fun CodeGeneratorRequest.generateKotlinJvmFiles(): List { val interpreter = ProtoToModelInterpreter(logger) val model = interpreter.interpretProtocRequest(this) + val fileGenerator = + ModelToKotlinJvmGenerator(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() + private fun CodeGeneratorRequest.generateKotlinCommonFiles(): List { + val model = this.toCommonModel() + val fileGenerator = + ModelToKotlinCommonGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) + return fileGenerator.generateKotlinFiles() + } + + private fun showFqNames(model: Model, descriptors: List) { + + System.err.println("Model:") + for (file in model.files) { + file.print("") + } + + System.err.println(" ----------------------- ") + + System.err.println("Descriptors:") + for (file in descriptors) { + file.print("") + } + + println("Make it crash with this print") + } + + private fun FileDeclaration.print(intent: String) { + System.err.println("[FILE] $intent- ${packageName.simpleName}: ${packageName}") + for (msg in messageDeclarations) { + msg.print(intent + "\t") + } + } + + private fun MessageDeclaration.print(intent: String) { + System.err.println("[MSG] $intent- ${name.simpleName}: ${name}") + for (field in actualFields) { + field.print(intent + "\t") + } + for (msg in nestedDeclarations) { + msg.print(intent + "\t") + } + } + + private fun FieldDeclaration.print(intent: String) { + System.err.println("[FIELD] $intent- ${this.name}") + } + + private fun Descriptors.FileDescriptor.print(intent: String) { + System.err.println("[FILE] $intent- ${fqName().simpleName}: ${fqName()}") + for (msg in messageTypes) { + msg.print(intent + "\t") + } + } + + private fun Descriptors.Descriptor.print(intent: String) { + System.err.println("[MSG] $intent- ${fqName().simpleName}: ${fqName()}") + for (field in fields) { + field.print(intent + "\t") + } + for (msg in nestedTypes) { + msg.print(intent + "\t") + } + } + + private fun Descriptors.FieldDescriptor.print(intent: String) { + System.err.println("[FIELD] $intent- ${fqName().simpleName}: ${ktTypeName()}") + } + + private fun Descriptors.FieldDescriptor.ktTypeName(): String { + return when (type) { + Descriptors.FieldDescriptor.Type.DOUBLE -> "Double" + Descriptors.FieldDescriptor.Type.FLOAT -> "Float" + Descriptors.FieldDescriptor.Type.INT64 -> "Long" + Descriptors.FieldDescriptor.Type.UINT64 -> "ULong" + Descriptors.FieldDescriptor.Type.INT32 -> "Int" + Descriptors.FieldDescriptor.Type.FIXED64 -> "ULong" + Descriptors.FieldDescriptor.Type.FIXED32 -> "UInt" + Descriptors.FieldDescriptor.Type.BOOL -> "Boolean" + Descriptors.FieldDescriptor.Type.STRING -> "String" + Descriptors.FieldDescriptor.Type.BYTES -> "ByteArray" + Descriptors.FieldDescriptor.Type.UINT32 -> "UInt" + Descriptors.FieldDescriptor.Type.SFIXED32 -> "Int" + Descriptors.FieldDescriptor.Type.SFIXED64 -> "Long" + Descriptors.FieldDescriptor.Type.SINT32 -> "Int" + Descriptors.FieldDescriptor.Type.SINT64 -> "Long" + Descriptors.FieldDescriptor.Type.ENUM -> "" + Descriptors.FieldDescriptor.Type.MESSAGE -> messageType!!.fqName().toString() + Descriptors.FieldDescriptor.Type.GROUP -> error("GROUP is unsupported") } } } + diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/OneOfDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/OneOfDeclaration.kt index aab8d237f..d7737072d 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/OneOfDeclaration.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/OneOfDeclaration.kt @@ -4,7 +4,10 @@ package kotlinx.rpc.protobuf.model +import com.google.protobuf.Descriptors + data class OneOfDeclaration( val name: FqName, val variants: List, + val descriptor: Descriptors.OneofDescriptor? ) From 957148d60afb00b8a5c61a62a9a43a1178c94149 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Thu, 31 Jul 2025 17:32:07 +0200 Subject: [PATCH 02/11] grpc: Remove JVM Generator related code Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/buf/BufExtensions.kt | 20 +- .../rpc/proto/DefaultProtoSourceSet.kt | 4 +- .../kotlin/kotlinx/rpc/base/GrpcBaseTest.kt | 14 +- .../{jvmMain => commonMain}/proto/some.proto | 0 .../{jvmTest => commonTest}/proto/other.proto | 0 .../{jvmMain => commonMain}/proto/some.proto | 0 .../no_grpc/build.gradle.kts | 4 +- .../no_jvm_targets/build.gradle.kts | 4 +- .../{jvmTest => commonTest}/proto/some.proto | 0 .../with_other_targets/build.gradle.kts | 4 +- .../{jvmMain => commonMain}/proto/some.proto | 0 grpc/grpc-core/build.gradle.kts | 6 - .../proto/exclude/empty_deprecated.proto | 0 .../proto/exclude}/enum.proto | 0 .../proto/exclude/enum_options.proto | 0 .../proto/exclude/example.proto | 0 .../proto/exclude}/image-recognizer.proto | 0 .../proto/exclude/multiple_files.proto | 0 .../proto/exclude}/nested.proto | 0 .../proto/exclude}/one_of.proto | 0 .../proto/exclude}/optional.proto | 0 .../proto/exclude/options.proto | 0 .../proto/exclude}/primitive_service.proto | 0 .../proto/exclude}/reference.proto | 0 .../proto/exclude}/reference_package.proto | 0 .../proto/exclude}/reference_service.proto | 0 .../proto/exclude}/streaming.proto | 0 .../proto/exclude}/test_map.proto | 0 .../proto/exclude/with_comments.proto | 0 .../rpc/grpc/core/test/GrpcServerTest.kt | 37 - .../rpc/grpc/core/test/StreamingTest.kt | 86 --- .../grpc/core/test/TestPrimitiveService.kt | 36 - .../grpc/core/test/TestReferenceService.kt | 285 ------- .../rpc/protobuf/ModelToKotlinJvmGenerator.kt | 695 ------------------ .../kotlinx/rpc/protobuf/ProtoToDescriptor.kt | 79 -- .../rpc/protobuf/ProtoToModelInterpreter.kt | 416 ----------- .../kotlinx/rpc/protobuf/RpcProtobufPlugin.kt | 108 +-- ...riptorToModel.kt => codeRequestToModel.kt} | 55 +- .../rpc/protobuf/model/NameResolver.kt | 192 +---- 39 files changed, 59 insertions(+), 1986 deletions(-) rename gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/{jvmMain => commonMain}/proto/some.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/{jvmTest => commonTest}/proto/other.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/src/{jvmMain => commonMain}/proto/some.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/src/{jvmTest => commonTest}/proto/some.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/src/{jvmMain => commonMain}/proto/some.proto (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/proto/exclude/empty_deprecated.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/enum.proto (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/proto/exclude/enum_options.proto (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/proto/exclude/example.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/image-recognizer.proto (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/proto/exclude/multiple_files.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/nested.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/one_of.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/optional.proto (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/proto/exclude/options.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/primitive_service.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/reference.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/reference_package.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/reference_service.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/streaming.proto (100%) rename grpc/grpc-core/src/{jvmTest/proto => commonTest/proto/exclude}/test_map.proto (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/proto/exclude/with_comments.proto (100%) delete mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt delete mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt delete mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt delete mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinJvmGenerator.kt delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToDescriptor.kt delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt rename protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/{DescriptorToModel.kt => codeRequestToModel.kt} (88%) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt index 7b66a5087..a69e37b7e 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -5,19 +5,19 @@ package kotlinx.rpc.buf import kotlinx.rpc.buf.tasks.BufExecTask +import kotlinx.rpc.proto.ProtocPlugin import org.gradle.api.Action import org.gradle.api.Project -import org.gradle.api.provider.Property -import org.gradle.kotlin.dsl.property -import java.io.File -import javax.inject.Inject -import kotlin.time.Duration -import kotlinx.rpc.proto.ProtocPlugin import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.property +import java.io.File +import javax.inject.Inject import kotlin.reflect.KClass +import kotlin.time.Duration /** * Options for the Buf tasks. @@ -100,14 +100,14 @@ public open class BufExtension @Inject constructor(objects: ObjectFactory) { * Allows registering custom Buf tasks that can operate on the generated workspace. */ public open class BufTasksExtension @Inject constructor(internal val project: Project) { - // TODO change to commonMain/commonTest in docs when it's supported KRPC-180 + /** * Registers a custom Buf task that operates on the generated workspace. * * Name conventions: * `lint` input for [name] will result in tasks * named 'bufLintMain' and 'bufLintTest' for Kotlin/JVM projects - * and 'bufLintJvmMain' and 'bufLintJvmTest' for Kotlin/Multiplatform projects. + * and 'bufLintCommonMain' and 'bufLintCommonTest' for Kotlin/Multiplatform projects. * * Note the by default 'test' task doesn't depend on 'main' task. */ @@ -127,14 +127,14 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr return provider } - // TODO change to commonMain/commonTest in docs when it's supported KRPC-180 + /** * Registers a custom Buf task that operates on the generated workspace. * * Name conventions: * `lint` input for [name] will result in tasks * named 'bufLintMain' and 'bufLintTest' for Kotlin/JVM projects - * and 'bufLintJvmMain' and 'bufLintJvmTest' for Kotlin/Multiplatform projects. + * and 'bufLintCommonMain' and 'bufLintCommonTest' for Kotlin/Multiplatform projects. * * Note the by default 'test' task doesn't depend on 'main' task. */ 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 81822424a..6bc7d7858 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt @@ -96,13 +96,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 == "commonMain" || name == "commonTest") { + if (name == "commonMain" || name == "commonTest") { findOrCreateAndConfigure(name, this) } } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt index e19263bc9..15e73d7a4 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt @@ -9,17 +9,7 @@ import org.gradle.testkit.runner.TaskOutcome import org.intellij.lang.annotations.Language import org.junit.jupiter.api.TestInstance import java.nio.file.Path -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.absolutePathString -import kotlin.io.path.deleteRecursively -import kotlin.io.path.exists -import kotlin.io.path.extension -import kotlin.io.path.isRegularFile -import kotlin.io.path.pathString -import kotlin.io.path.readLines -import kotlin.io.path.relativeTo -import kotlin.io.path.walk -import kotlin.lazy +import kotlin.io.path.* import kotlin.test.assertEquals import kotlin.test.fail @@ -229,7 +219,7 @@ abstract class GrpcBaseTest : BaseTest() { } companion object { - private const val KMP_SOURCE_SET = "jvm" + private const val KMP_SOURCE_SET = "common" private val KMP_SOURCE_SET_CAPITAL = KMP_SOURCE_SET.replaceFirstChar(Char::uppercaseChar) private const val KOTLIN_MULTIPLATFORM_DIR = "kotlin-multiplatform" const val RPC_INTERNAL = "_rpc_internal" diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/jvmMain/proto/some.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/commonMain/proto/some.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/jvmMain/proto/some.proto rename to gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/commonMain/proto/some.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/jvmTest/proto/other.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/commonTest/proto/other.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/jvmTest/proto/other.proto rename to gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/src/commonTest/proto/other.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/src/jvmMain/proto/some.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/src/commonMain/proto/some.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/src/jvmMain/proto/some.proto rename to gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/src/commonMain/proto/some.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_grpc/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_grpc/build.gradle.kts index 315141d9c..1b1d627a6 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_grpc/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_grpc/build.gradle.kts @@ -11,6 +11,6 @@ plugins { // should nonetheless be available protoSourceSets { - jvmMain {} - jvmTest {} + commonMain {} + commonTest {} } diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_jvm_targets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_jvm_targets/build.gradle.kts index b30e26cf5..0e712e9ca 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_jvm_targets/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_jvm_targets/build.gradle.kts @@ -16,6 +16,6 @@ kotlin { // should nonetheless be available protoSourceSets { - jvmMain {} - jvmTest {} + commonMain {} + commonTest {} } diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/src/jvmTest/proto/some.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/src/commonTest/proto/some.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/src/jvmTest/proto/some.proto rename to gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/src/commonTest/proto/some.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts index 85e4446bd..adf702591 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts @@ -16,8 +16,8 @@ kotlin { } protoSourceSets { - jvmMain {} - jvmTest {} + commonMain {} + commonTest {} } rpc { diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/src/jvmMain/proto/some.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/src/commonMain/proto/some.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/src/jvmMain/proto/some.proto rename to gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/src/commonMain/proto/some.proto diff --git a/grpc/grpc-core/build.gradle.kts b/grpc/grpc-core/build.gradle.kts index 2c74b1d5e..dbd2e104b 100644 --- a/grpc/grpc-core/build.gradle.kts +++ b/grpc/grpc-core/build.gradle.kts @@ -133,12 +133,6 @@ kotlin { } protoSourceSets { - jvmTest { - proto { - exclude("exclude/**") - } - } - commonTest { proto { exclude("exclude/**") diff --git a/grpc/grpc-core/src/jvmTest/proto/exclude/empty_deprecated.proto b/grpc/grpc-core/src/commonTest/proto/exclude/empty_deprecated.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/exclude/empty_deprecated.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/empty_deprecated.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/enum.proto b/grpc/grpc-core/src/commonTest/proto/exclude/enum.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/enum.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/enum.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/exclude/enum_options.proto b/grpc/grpc-core/src/commonTest/proto/exclude/enum_options.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/exclude/enum_options.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/enum_options.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/exclude/example.proto b/grpc/grpc-core/src/commonTest/proto/exclude/example.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/exclude/example.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/example.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/image-recognizer.proto b/grpc/grpc-core/src/commonTest/proto/exclude/image-recognizer.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/image-recognizer.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/image-recognizer.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/exclude/multiple_files.proto b/grpc/grpc-core/src/commonTest/proto/exclude/multiple_files.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/exclude/multiple_files.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/multiple_files.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/nested.proto b/grpc/grpc-core/src/commonTest/proto/exclude/nested.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/nested.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/nested.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/one_of.proto b/grpc/grpc-core/src/commonTest/proto/exclude/one_of.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/one_of.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/one_of.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/optional.proto b/grpc/grpc-core/src/commonTest/proto/exclude/optional.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/optional.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/optional.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/exclude/options.proto b/grpc/grpc-core/src/commonTest/proto/exclude/options.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/exclude/options.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/options.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/primitive_service.proto b/grpc/grpc-core/src/commonTest/proto/exclude/primitive_service.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/primitive_service.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/primitive_service.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/reference.proto b/grpc/grpc-core/src/commonTest/proto/exclude/reference.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/reference.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/reference.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/reference_package.proto b/grpc/grpc-core/src/commonTest/proto/exclude/reference_package.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/reference_package.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/reference_package.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/reference_service.proto b/grpc/grpc-core/src/commonTest/proto/exclude/reference_service.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/reference_service.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/reference_service.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/streaming.proto b/grpc/grpc-core/src/commonTest/proto/exclude/streaming.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/streaming.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/streaming.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/test_map.proto b/grpc/grpc-core/src/commonTest/proto/exclude/test_map.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/test_map.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/test_map.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/exclude/with_comments.proto b/grpc/grpc-core/src/commonTest/proto/exclude/with_comments.proto similarity index 100% rename from grpc/grpc-core/src/jvmTest/proto/exclude/with_comments.proto rename to grpc/grpc-core/src/commonTest/proto/exclude/with_comments.proto diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt deleted file mode 100644 index 7204d639e..000000000 --- a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt +++ /dev/null @@ -1,37 +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.core.test - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.test.runTest -import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.GrpcClient -import kotlinx.rpc.grpc.GrpcServer - -abstract class GrpcServerTest { - private val serverMutex = Mutex() - - abstract fun RpcServer.registerServices() - - protected fun runGrpcTest(test: suspend (GrpcClient) -> Unit, ) = runTest { - serverMutex.withLock { - val grpcClient = GrpcClient("localhost", 8080) { - usePlaintext() - } - - val grpcServer = GrpcServer(8080, builder = { - registerServices() - }) - - grpcServer.start() - test(grpcClient) - grpcServer.shutdown() - grpcServer.awaitTermination() - grpcClient.shutdown() - grpcClient.awaitTermination() - } - } -} diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt deleted file mode 100644 index f5a69160b..000000000 --- a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt +++ /dev/null @@ -1,86 +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.core.test - -import StreamingTestService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectIndexed -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.last -import kotlinx.coroutines.flow.toList -import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.test.invoke -import kotlinx.rpc.registerService -import kotlinx.rpc.withService -import kotlin.test.Test -import kotlin.test.assertEquals - -class StreamingTestServiceImpl : StreamingTestService { - override fun Server(message: kotlinx.rpc.grpc.test.References): Flow { - return flow { emit(message); emit(message); emit(message) } - } - - override suspend fun Client(message: Flow): kotlinx.rpc.grpc.test.References { - return message.last() - } - - override fun Bidi(message: Flow): Flow { - return message - } -} - -class StreamingTest : GrpcServerTest() { - override fun RpcServer.registerServices() { - registerService { StreamingTestServiceImpl() } - } - - @Test - fun testServerStreaming() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - service.Server(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field= 42 - } - }).toList().run { - assertEquals(3, size) - - forEach { - assertEquals(42, it.other.field) - } - } - } - - @Test - fun testClientStreaming() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Client(flow { - repeat(3) { - emit(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 + it - } - }) - } - }) - - assertEquals(44, result.other.field) - } - - @Test - fun testBidiStreaming() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - service.Bidi(flow { - repeat(3) { - emit(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 + it - } - }) - } - }).collectIndexed { i, it -> - assertEquals(42 + i, it.other.field) - } - } -} diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt deleted file mode 100644 index b15b3ff20..000000000 --- a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt +++ /dev/null @@ -1,36 +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.core.test - -import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.test.AllPrimitives -import kotlinx.rpc.grpc.test.invoke -import kotlinx.rpc.grpc.test.PrimitiveService -import kotlinx.rpc.registerService -import kotlinx.rpc.withService -import kotlin.test.Test -import kotlin.test.assertEquals - -class PrimitiveServiceImpl : PrimitiveService { - override suspend fun Echo(message: AllPrimitives): AllPrimitives { - return message - } -} - -class TestPrimitiveService : GrpcServerTest() { - override fun RpcServer.registerServices() { - registerService { PrimitiveServiceImpl() } - } - - @Test - fun testPrimitive(): Unit = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Echo(AllPrimitives { - int32 = 42 - }) - - assertEquals(42, result.int32) - } -} diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt deleted file mode 100644 index 3eb7ff64a..000000000 --- a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt +++ /dev/null @@ -1,285 +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.core.test - -import ReferenceTestService -import References -import Other -import invoke -import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.test.AllPrimitives -import kotlinx.rpc.grpc.test.Nested -import kotlinx.rpc.grpc.test.OneOf -import kotlinx.rpc.grpc.test.OptionalTypes -import kotlinx.rpc.grpc.test.Repeated -import kotlinx.rpc.grpc.test.TestMap -import kotlinx.rpc.grpc.test.UsingEnum -import kotlinx.rpc.grpc.test.invoke -import kotlinx.rpc.registerService -import kotlinx.rpc.withService -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class ReferenceTestServiceImpl : ReferenceTestService { - override suspend fun Get(message: References): kotlinx.rpc.grpc.test.References { - return kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = message.other.arg.toInt() - } - - primitive = message.other.arg - } - } - - override suspend fun Enum(message: UsingEnum): UsingEnum { - return message - } - - override suspend fun Optional(message: OptionalTypes): OptionalTypes { - return message - } - - override suspend fun Repeated(message: Repeated): Repeated { - return message - } - - override suspend fun Nested(message: Nested): Nested { - return message - } - - override suspend fun Map(message: TestMap): TestMap { - return message - } - - override suspend fun OneOf(message: OneOf): OneOf { - return message - } -} - -class TestReferenceService : GrpcServerTest() { - override fun RpcServer.registerServices() { - registerService { ReferenceTestServiceImpl() } - } - - @Test - fun testReferenceService() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val arg = References { - other = Other { - arg = "42" - } - } - - val result = service.Get(arg) - - assertEquals("42", result.primitive) - assertEquals(42, result.other.field) - } - - @Test - fun testEnum() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Enum(UsingEnum { - enum = kotlinx.rpc.grpc.test.Enum.ONE - }) - - assertEquals(kotlinx.rpc.grpc.test.Enum.ONE, result.enum) - } - - @Test - fun testOptional() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val resultNotNull = service.Optional(OptionalTypes { - name = "test" - age = 42 - reference = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }) - - assertEquals("test", resultNotNull.name) - assertEquals(42, resultNotNull.age) - assertEquals(42, resultNotNull.reference?.field) - - val resultNullable = service.Optional(OptionalTypes { - name = null - age = null - reference = null - }) - - assertEquals(null, resultNullable.name) - assertEquals(null, resultNullable.age) - assertEquals(null, resultNullable.reference) - } - - @Test - fun testRepeated() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Repeated(Repeated { - listFixed32 = listOf(0u, 1u, 2u) - listInt32 = listOf(0, 1, 2) - listString = listOf("test", "hello") - listReference = listOf(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }) - }) - - assertEquals(listOf("test", "hello"), result.listString) - assertEquals(listOf(0u, 1u, 2u), result.listFixed32) - assertEquals(listOf(0, 1, 2), result.listInt32) - assertEquals(1, result.listReference.size) - assertEquals(42, result.listReference[0].other.field) - - val resultEmpty = service.Repeated(Repeated {}) - - assertEquals(emptyList(), resultEmpty.listString) - assertEquals(emptyList(), resultEmpty.listFixed32) - assertEquals(emptyList(), resultEmpty.listInt32) - assertEquals(emptyList(), resultEmpty.listReference) - } - - @Test - fun testNested() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Nested(Nested { - inner1 = Nested.Inner1 { - inner11 = Nested.Inner1.Inner11 { - reference21 = null - reference12 = Nested.Inner1.Inner12 { - recursion = null - } - enum = Nested.Inner2.NestedEnum.ZERO - } - - inner22 = Nested.Inner1.Inner12 { - recursion = Nested.Inner1.Inner12 { - recursion = null - } - } - - string = "42_1" - - inner1 = null - } - - inner2 = Nested.Inner2 { - inner21 = Nested.Inner2.Inner21 { - reference11 = Nested.Inner1.Inner11 { - reference21 = null - reference12 = Nested.Inner1.Inner12 { - recursion = null - } - enum = Nested.Inner2.NestedEnum.ZERO - } - - reference22 = Nested.Inner2.Inner22 { - enum = Nested.Inner2.NestedEnum.ZERO - } - } - - inner22 = Nested.Inner2.Inner22 { - enum = Nested.Inner2.NestedEnum.ZERO - } - string = "42_2" - } - - string = "42" - enum = Nested.Inner2.NestedEnum.ZERO - }) - - // Assert Inner1.Inner11 - assertEquals(null, result.inner1.inner11.reference21) - assertEquals(null, result.inner1.inner11.reference12.recursion) - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner1.inner11.enum) - - // Assert Inner1.Inner12 - assertNotNull(result.inner1.inner22.recursion) - assertEquals(null, result.inner1.inner22.recursion?.recursion) - - // Assert Inner1 - assertEquals("42_1", result.inner1.string) - assertEquals(null, result.inner1.inner1) - - // Assert Inner2.Inner21 - assertEquals(null, result.inner2.inner21.reference11.reference21) - assertEquals(null, result.inner2.inner21.reference11.reference12.recursion) - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference11.enum) - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference22.enum) - - // Assert Inner2.Inner22 - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner22.enum) - - // Assert Inner2 - assertEquals("42_2", result.inner2.string) - - // Assert root Nested - assertEquals("42", result.string) - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.enum) - } - - @Test - fun testMap() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Map(TestMap { - primitives = mapOf("1" to 2, "2" to 1) - references = mapOf("ref" to kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }) - }) - - assertEquals(mapOf("1" to 2L, "2" to 1L), result.primitives) - assertEquals(mapOf("ref" to 42), result.references.mapValues { it.value.other.field }) - } - - @Test - fun testOneOf() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result1 = service.OneOf(OneOf { - primitives = OneOf.Primitives.StringValue("42") - references = OneOf.References.Other(kotlinx.rpc.grpc.test.Other { - field = 42 - }) - mixed = OneOf.Mixed.Int64(42L) - single = OneOf.Single.Bytes(byteArrayOf(42)) - }) - - assertEquals("42", (result1.primitives as OneOf.Primitives.StringValue).value) - assertEquals(42, (result1.references as OneOf.References.Other).value.field) - assertEquals(42L, (result1.mixed as OneOf.Mixed.Int64).value) - assertContentEquals(byteArrayOf(42), (result1.single as OneOf.Single.Bytes).value) - - val result2 = service.OneOf(OneOf { - primitives = OneOf.Primitives.Bool(true) - references = OneOf.References.InnerReferences(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }) - mixed = OneOf.Mixed.AllPrimitives(AllPrimitives { - string = "42" - }) - }) - - assertEquals(true, (result2.primitives as OneOf.Primitives.Bool).value) - assertEquals(42, (result2.references as OneOf.References.InnerReferences).value.other.field) - assertEquals("42", (result2.mixed as OneOf.Mixed.AllPrimitives).value.string) - assertEquals(null, result2.single) - - val result3 = service.OneOf(OneOf { - primitives = OneOf.Primitives.Int32(42) - }) - - assertEquals(42, (result3.primitives as OneOf.Primitives.Int32).value) - assertEquals(null, result3.references) - assertEquals(null, result3.mixed) - assertEquals(null, result3.single) - } -} diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinJvmGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinJvmGenerator.kt deleted file mode 100644 index 48083b26e..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinJvmGenerator.kt +++ /dev/null @@ -1,695 +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: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 ModelToKotlinJvmGenerator( - 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.*") - - 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.serviceDeclarations.forEach { generateInternalService(it) } - - fileDeclaration.messageDeclarations.forEach { - generateToAndFromPlatformCastsRec(it) - } - - fileDeclaration.enumDeclarations.forEach { - generateToAndFromPlatformCastsEnum(it) - } - } - - private fun CodeGenerator.generateToAndFromPlatformCastsRec(declaration: MessageDeclaration) { - generateToAndFromPlatformCasts(declaration) - - declaration.nestedDeclarations.forEach { nested -> - generateToAndFromPlatformCastsRec(nested) - } - - declaration.enumDeclarations.forEach { nested -> - generateToAndFromPlatformCastsEnum(nested) - } - } - - 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.type is FieldType.Reference && 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.generateToAndFromPlatformCasts(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)") - } - - val platformType = "${declaration.outerClassName.safeFullName()}.${declaration.name.fullNestedName()}" - function( - name = "toPlatform", - contextReceiver = declaration.name.safeFullName(), - returnType = platformType, - ) { - scope("return $platformType.newBuilder().apply", ".build()") { - declaration.actualFields.forEach { field -> - val setFieldCall = setFieldCall(field) - - when { - field.type is FieldType.OneOf -> { - scope("this@toPlatform.${field.name}?.let { value ->", openingBracket = false) { - scope("when (value)") { - val oneOf = declaration.oneOfDeclarations[field.type.index] - val oneOfName = oneOf.name.safeFullName() - - oneOf.variants.forEach { variant -> - code("is $oneOfName.${variant.name} -> ${setFieldCall(variant)}(value.value${variant.type.toPlatformCast()})") - } - } - } - } - - field.nullable -> { - code("this@toPlatform.${field.name}?.let { $setFieldCall(it${field.type.toPlatformCast()}) }") - } - - else -> { - code("$setFieldCall(this@toPlatform.${field.name}${field.type.toPlatformCast()})") - } - } - } - } - } - - function( - name = "toKotlin", - contextReceiver = platformType, - returnType = declaration.name.safeFullName(), - ) { - scope("return ${declaration.name.safeFullName()}") { - declaration.actualFields.forEach { field -> - val javaName = fieldJavaName(field) - - val getter = "this@toKotlin.$javaName${field.type.toKotlinCast()}" - when { - field.type is FieldType.OneOf -> { - val oneOf = declaration.oneOfDeclarations[field.type.index] - val oneOfName = oneOf.name.safeFullName() - - scope("${field.name} = when") { - oneOf.variants.forEach { variant -> - code( - "${hasFieldJavaMethod(variant)} -> $oneOfName.${variant.name}(this@toKotlin.${ - fieldJavaName( - variant - ) - }${variant.type.toKotlinCast()})" - ) - } - code("else -> null") - } - } - - field.nullable -> { - ifBranch( - prefix = "${field.name} = ", - condition = hasFieldJavaMethod(field), - ifBlock = { - code(getter) - }, - elseBlock = { - code("null") - } - ) - } - - else -> { - code("${field.name} = $getter") - } - } - } - } - } - } - - private fun fieldJavaName(field: FieldDeclaration): String { - val name = field.name.replaceFirstChar { ch -> ch.lowercase() } - return when (field.type) { - is FieldType.List -> "${name}List" - is FieldType.Map -> "${name}Map" - else -> name - } - } - - 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() } - val setFieldCall = when (field.type) { - is FieldType.List -> "addAll$uppercaseName" - is FieldType.Map -> "putAll$uppercaseName" - else -> "set$uppercaseName" - } - return setFieldCall - } - - private fun FieldType.toPlatformCast(): String { - return when (this) { - FieldType.IntegralType.FIXED32 -> ".toInt()" - FieldType.IntegralType.FIXED64 -> ".toLong()" - FieldType.IntegralType.UINT32 -> ".toInt()" - FieldType.IntegralType.UINT64 -> ".toLong()" - FieldType.IntegralType.BYTES -> ".let { bytes -> com.google.protobuf.ByteString.copyFrom(bytes) }" - is FieldType.Reference -> ".toPlatform()".also { - val fq by value - importRootDeclarationIfNeeded(fq, "toPlatform", true) - } - - is FieldType.List -> ".map { it${value.toPlatformCast()} }" - - is FieldType.Map -> { - val entry by entry - - ".mapValues { it.value${entry.value.toPlatformCast()} }" - } - - else -> "" - } - } - - private fun FieldType.toKotlinCast(): String { - return when (this) { - FieldType.IntegralType.FIXED32 -> ".toUInt()" - FieldType.IntegralType.FIXED64 -> ".toULong()" - FieldType.IntegralType.UINT32 -> ".toUInt()" - FieldType.IntegralType.UINT64 -> ".toULong()" - FieldType.IntegralType.BYTES -> ".toByteArray()" - is FieldType.Reference -> ".toKotlin()".also { - val fq by value - importRootDeclarationIfNeeded(fq, "toKotlin", true) - } - - is FieldType.List -> ".map { it${value.toKotlinCast()} }" - - is FieldType.Map -> { - val entry by entry - - ".mapValues { it.value${entry.value.toKotlinCast()} }" - } - - else -> "" - } - } - - 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}" - ) - } - } - } - } - } - - private fun CodeGenerator.generateToAndFromPlatformCastsEnum(declaration: EnumDeclaration) { - val platformType = "${declaration.outerClassName.safeFullName()}.${declaration.name.fullNestedName()}" - - function( - name = "toPlatform", - contextReceiver = declaration.name.safeFullName(), - returnType = platformType, - ) { - scope("return when (this)") { - declaration.aliases.forEach { field -> - code("${declaration.name.fullNestedName()}.${field.name.simpleName} -> $platformType.${field.name.simpleName}") - } - - declaration.originalEntries.forEach { field -> - code("${declaration.name.fullNestedName()}.${field.name.simpleName} -> $platformType.${field.name.simpleName}") - } - } - } - - function( - name = "toKotlin", - contextReceiver = platformType, - returnType = declaration.name.safeFullName(), - ) { - scope("return when (this)") { - declaration.aliases.forEach { field -> - code("$platformType.${field.name.simpleName} -> ${declaration.name.fullNestedName()}.${field.name.simpleName}") - } - - declaration.originalEntries.forEach { field -> - code("$platformType.${field.name.simpleName} -> ${declaration.name.fullNestedName()}.${field.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 CodeGenerator.generateInternalService(service: ServiceDeclaration) { - code("@Suppress(\"unused\", \"all\")") - clazz( - modifiers = "private", - name = "${service.name.simpleName}Delegate", - declarationType = DeclarationType.Object, - superTypes = listOf("kotlinx.rpc.grpc.descriptor.GrpcDelegate<${service.name.safeFullName()}>"), - ) { - function( - name = "clientProvider", - modifiers = "override", - args = "channel: kotlinx.rpc.grpc.ManagedChannel", - returnType = "kotlinx.rpc.grpc.descriptor.GrpcClientDelegate", - ) { - code("return ${service.name.simpleName}ClientDelegate(channel)") - } - - function( - name = "definitionFor", - modifiers = "override", - args = "impl: ${service.name.safeFullName()}", - returnType = "kotlinx.rpc.grpc.ServerServiceDefinition", - ) { - scope("return ${service.name.simpleName}ServerDelegate(impl).bindService()") - } - } - - code("@Suppress(\"unused\", \"all\")") - clazz( - modifiers = "private", - name = "${service.name.simpleName}ServerDelegate", - declarationType = DeclarationType.Class, - superTypes = listOf("${service.name.simpleName}GrpcKt.${service.name.simpleName}CoroutineImplBase()"), - constructorArgs = listOf("private val impl: ${service.name.safeFullName()}"), - ) { - service.methods.forEach { method -> - val grpcName = method.name.replaceFirstChar { it.lowercase() } - - val inputType by method.inputType - val outputType by method.outputType - - function( - name = grpcName, - modifiers = "override${if (method.serverStreaming) "" else " suspend"}", - args = "request: ${inputType.toPlatformMessageType().wrapInFlowIf(method.clientStreaming)}", - returnType = outputType.toPlatformMessageType().wrapInFlowIf(method.serverStreaming), - ) { - val toKotlin = if (method.clientStreaming) { - "map { it.toKotlin() }" - } else { - "toKotlin()" - } - - val toPlatform = if (method.serverStreaming) { - "map { it.toPlatform() }" - } else { - "toPlatform()" - } - - code("return impl.${method.name}(request.${toKotlin}).${toPlatform}") - - importRootDeclarationIfNeeded(inputType.name, "toPlatform", true) - importRootDeclarationIfNeeded(outputType.name, "toKotlin", true) - } - } - } - - code("@Suppress(\"unused\", \"all\")") - clazz( - modifiers = "private", - name = "${service.name.simpleName}ClientDelegate", - declarationType = DeclarationType.Class, - superTypes = listOf("kotlinx.rpc.grpc.descriptor.GrpcClientDelegate"), - constructorArgs = listOf("private val channel: kotlinx.rpc.grpc.ManagedChannel"), - ) { - val stubType = "${service.name.simpleName}GrpcKt.${service.name.simpleName}CoroutineStub" - - property( - name = "stub", - modifiers = "private", - type = stubType, - delegate = true, - value = "lazy", - ) { - code("$stubType(channel.platformApi)") - } - - function( - name = "call", - modifiers = "override suspend", - args = "rpcCall: kotlinx.rpc.RpcCall", - typeParameters = "R", - returnType = "R", - ) { - val methods = service.methods.filter { !it.serverStreaming } - - if (methods.isEmpty()) { - code("error(\"Illegal call: \${rpcCall.callableName}\")") - return@function - } - - generateCallsImpls(methods) - } - - function( - name = "callServerStreaming", - modifiers = "override", - args = "rpcCall: kotlinx.rpc.RpcCall", - typeParameters = "R", - returnType = "Flow", - ) { - val methods = service.methods.filter { it.serverStreaming } - - if (methods.isEmpty()) { - code("error(\"Illegal streaming call: \${rpcCall.callableName}\")") - return@function - } - - generateCallsImpls(methods) - } - } - } - - private fun CodeGenerator.generateCallsImpls( - methods: List, - ) { - code("val message = rpcCall.parameters[0]") - code("@Suppress(\"UNCHECKED_CAST\")") - scope("return when (rpcCall.callableName)") { - methods.forEach { method -> - val inputType by method.inputType - val outputType by method.outputType - val grpcName = method.name.replaceFirstChar { it.lowercase() } - - val toKotlin = if (method.serverStreaming) { - "map { it.toKotlin() }" - } else { - "toKotlin()" - } - - val toPlatform = if (method.clientStreaming) { - "map { it.toPlatform() }" - } else { - "toPlatform()" - } - - val argumentCast = inputType.name.safeFullName().wrapInFlowIf(method.clientStreaming) - val resultCast = "R".wrapInFlowIf(method.serverStreaming) - - val result = "stub.$grpcName((message as $argumentCast).${toPlatform})" - code("\"${method.name}\" -> $result.${toKotlin} as $resultCast") - - importRootDeclarationIfNeeded(inputType.name, "toPlatform", true) - importRootDeclarationIfNeeded(outputType.name, "toKotlin", true) - } - - code("else -> error(\"Illegal call: \${rpcCall.callableName}\")") - } - } - - private fun MessageDeclaration.toPlatformMessageType(): String { - return "${outerClassName.safeFullName()}.${name.fullNestedName()}" - } - - 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/ProtoToDescriptor.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToDescriptor.kt deleted file mode 100644 index 265cf9652..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToDescriptor.kt +++ /dev/null @@ -1,79 +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.protobuf - -import com.google.protobuf.DescriptorProtos -import com.google.protobuf.Descriptors -import kotlinx.rpc.protobuf.model.FqName -import kotlinx.rpc.protobuf.model.NameResolver - -private fun DescriptorProtos.FileDescriptorProto.toDescriptor( - protoFileMap: Map, - cache: MutableMap -): Descriptors.FileDescriptor { - if (cache.containsKey(name)) return cache[name]!! - - val dependencies = dependencyList.map { depName -> - val depProto = protoFileMap[depName] ?: error("Missing dependency: $depName") - depProto.toDescriptor(protoFileMap, cache) - }.toTypedArray() - - val fileDescriptor = Descriptors.FileDescriptor.buildFrom(this, dependencies) - cache[name] = fileDescriptor - return fileDescriptor -} - -//// GenericDescriptor Extensions //// - -fun Descriptors.GenericDescriptor.fqName(): FqName { - val nameCapital = name.simpleProtoNameToKotlin(firstLetterUpper = true) - val nameLower = name.simpleProtoNameToKotlin() - return when (this) { - is Descriptors.FileDescriptor -> FqName.Package.fromString(`package`) - is Descriptors.Descriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) - is Descriptors.FieldDescriptor -> { - val usedName = if (realContainingOneof != null) nameCapital else nameLower - FqName.Declaration(usedName, containingType?.fqName() ?: file.fqName()) - } - - is Descriptors.EnumValueDescriptor -> FqName.Declaration(name, type.fqName()) - is Descriptors.OneofDescriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) - is Descriptors.ServiceDescriptor -> FqName.Declaration(nameCapital, file?.fqName() ?: file.fqName()) - is Descriptors.MethodDescriptor -> FqName.Declaration(nameLower, service?.fqName() ?: file.fqName()) - else -> error("Unknown generic descriptor: $this") - } -} - -//// Descriptor Extensions //// - -fun Descriptors.Descriptor.deprecated(): Boolean { - return options.deprecated -} - -fun Descriptors.FieldDescriptor.test() { - this -} - -//// Utility Extensions //// - -private val snakeRegExp = "(_[a-z]|-[a-z])".toRegex() - -private fun String.snakeToCamelCase(): String { - return replace(snakeRegExp) { it.value.last().uppercase() } -} - -private fun String.simpleProtoNameToKotlin(firstLetterUpper: Boolean = false): String { - return snakeToCamelCase().run { - if (firstLetterUpper) { - replaceFirstChar { it.uppercase() } - } else { - this - } - } -} - -private fun NameResolver.declarationFqName(simpleName: String, parent: FqName): FqName.Declaration { - return FqName.Declaration(simpleName, parent).also { add(it) } -} diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt deleted file mode 100644 index e878d0647..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt +++ /dev/null @@ -1,416 +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:Suppress("detekt.all") - -package kotlinx.rpc.protobuf - -import com.google.protobuf.DescriptorProtos -import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type -import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest -import kotlinx.rpc.protobuf.model.* -import kotlinx.rpc.protobuf.model.FieldType.IntegralType -import org.slf4j.Logger -import kotlin.properties.Delegates - -private const val ENUM_UNRECOGNIZED = "UNRECOGNIZED" - -class ProtoToModelInterpreter( - @Suppress("unused") - private val logger: Logger, -) { - private val fileDependencies = mutableMapOf() - private val messages = mutableMapOf() - private val rootResolver = NameResolver.create(logger) - - fun interpretProtocRequest(message: CodeGeneratorRequest): Model { - val protoFileMap = message.protoFileList.associateBy { it.name } - - return Model(message.fileToGenerateList.map { protoFileMap.getValue(it).toModel() }) - } - - // package name of a currently parsed file - private var packageName: FqName.Package by Delegates.notNull() - - private fun DescriptorProtos.FileDescriptorProto.toModel(): FileDeclaration { - val dependencies = dependencyList.map { depFilename -> - fileDependencies[depFilename] - ?: error("Unknown dependency $depFilename for $name proto file, wrong topological order") - } - - packageName = FqName.Package.fromString(kotlinPackageName(`package`, options)) - - val resolver = rootResolver - .withScope(packageName) - .withImports(dependencies.map { it.packageName }) - - val outerClass = outerClassFq() - - return FileDeclaration( - name = kotlinFileName(), - packageName = packageName, - dependencies = dependencies, - messageDeclarations = messageTypeList.mapNotNull { it.toModel(resolver, outerClass, parent = null) }, - enumDeclarations = enumTypeList.map { it.toModel(resolver, outerClass, packageName) }, - serviceDeclarations = serviceList.map { it.toModel(resolver) }, - deprecated = options.deprecated, - doc = null, - ).also { - // TODO KRPC-144: check uniqueness - fileDependencies[name] = it - } - } - - private fun DescriptorProtos.FileDescriptorProto.outerClassFq(): FqName { - val filename = protoFileNameToKotlinName() - - val messageClash = messageTypeList.any { it.name.fullProtoNameToKotlin(firstLetterUpper = true) == filename } - val enumClash = enumTypeList.any { it.name.fullProtoNameToKotlin(firstLetterUpper = true) == filename } - val serviceClash = serviceList.any { it.name.fullProtoNameToKotlin(firstLetterUpper = true) == filename } - - val suffix = if (messageClash || enumClash || serviceClash) { - "OuterClass" - } else { - "" - } - - val simpleName = "${filename}$suffix" - return FqName.Declaration(simpleName, packageName) - } - - private fun DescriptorProtos.FileDescriptorProto.kotlinFileName(): String { - return "${protoFileNameToKotlinName()}.kt" - } - - private fun DescriptorProtos.FileDescriptorProto.protoFileNameToKotlinName(): String { - return name.removeSuffix(".proto").fullProtoNameToKotlin(firstLetterUpper = true) - } - - private fun kotlinPackageName(originalPackage: String, options: DescriptorProtos.FileOptions): String { - // todo check forbidden package names - return originalPackage - } - - private val mapEntries = mutableMapOf>() - - private fun DescriptorProtos.DescriptorProto.toModel( - parentResolver: NameResolver, - outerClass: FqName, - parent: FqName?, - ): MessageDeclaration? { - val simpleName = name.fullProtoNameToKotlin(firstLetterUpper = true) - val fqName = parentResolver.declarationFqName(simpleName, parent ?: packageName) - val resolver = parentResolver.withScope(fqName) - - if (options.mapEntry) { - mapEntries[fqName] = resolveMapEntry(resolver) - return null - } - - // resolve before fields - val nestedDeclarations = nestedTypeList.mapNotNull { it.toModel(resolver, outerClass, fqName) } - val fields = fieldList.mapNotNull { - val oneOfName = if (it.hasOneofIndex()) { - oneofDeclList[it.oneofIndex].name - } else { - null - } - - it.toModel(oneOfName, resolver) - } - - return MessageDeclaration( - outerClassName = outerClass, - name = fqName, - actualFields = fields, - oneOfDeclarations = oneofDeclList.mapIndexedNotNull { i, desc -> desc.toModel(i, resolver, fqName) }, - enumDeclarations = enumTypeList.map { it.toModel(resolver, outerClass, fqName) }, - nestedDeclarations = nestedDeclarations, - deprecated = options.deprecated, - doc = null, - ).apply { - messages[name] = this - } - } - - private fun DescriptorProtos.DescriptorProto.resolveMapEntry(resolver: NameResolver): Lazy = - lazy { - val keyType = fieldList[0].toModel(null, resolver) ?: error("Key type is null") - val valueType = fieldList[1].toModel(null, resolver) ?: error("Value type is null") - - FieldType.Map.Entry(keyType.type, valueType.type) - } - - private val oneOfFieldMembers = mutableMapOf>() - - private fun DescriptorProtos.FieldDescriptorProto.toModel( - oneOfName: String?, - resolver: NameResolver, - ): FieldDeclaration? { - if (oneOfName != null) { - val fieldType = when { - // effectively optional - // https://github.com/protocolbuffers/protobuf/blob/main/docs/implementing_proto3_presence.md#updating-a-code-generator - oneOfName == "_$name" -> { - fieldType(resolver) - } - - oneOfFieldMembers[oneofIndex] == null -> { - oneOfFieldMembers[oneofIndex] = mutableListOf() - .also { list -> list.add(this) } - - val name = oneOfName.fullProtoNameToKotlin(firstLetterUpper = true) - FieldType.OneOf(lazy { resolver.resolve(name) }, oneofIndex) - } - - else -> { - oneOfFieldMembers[oneofIndex]!!.add(this) - null - } - } ?: return null - - 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, - ) - } - - private fun DescriptorProtos.FieldDescriptorProto.fieldType(resolver: NameResolver): FieldType { - return when { - // from https://github.com/protocolbuffers/protobuf/blob/5fc0e22b9f912c2aa94a34502887c3719e805834/src/google/protobuf/descriptor.proto#L294 - // if the name starts with a '.', it is fully-qualified. - hasTypeName() && typeName.startsWith(".") -> { - typeName - .substringAfter('.') - .fullProtoNameToKotlin(firstLetterUpper = true) - // KRPC-144 Import types - .let { resolvedType(it, resolver) } - } - - hasTypeName() -> { - typeName - .fullProtoNameToKotlin(firstLetterUpper = true) - // KRPC-144 Import types: we assume local types now - .let { resolvedType(it, resolver) } - } - - else -> { - wrapWithLabel(primitiveType()) - } - } - } - - @Suppress("detekt.CyclomaticComplexMethod") - private fun DescriptorProtos.FieldDescriptorProto.primitiveType(): IntegralType { - return when (type) { - Type.TYPE_STRING -> IntegralType.STRING - Type.TYPE_BYTES -> IntegralType.BYTES - Type.TYPE_BOOL -> IntegralType.BOOL - Type.TYPE_FLOAT -> IntegralType.FLOAT - Type.TYPE_DOUBLE -> IntegralType.DOUBLE - Type.TYPE_INT32 -> IntegralType.INT32 - Type.TYPE_INT64 -> IntegralType.INT64 - Type.TYPE_UINT32 -> IntegralType.UINT32 - Type.TYPE_UINT64 -> IntegralType.UINT64 - Type.TYPE_FIXED32 -> IntegralType.FIXED32 - Type.TYPE_FIXED64 -> IntegralType.FIXED64 - Type.TYPE_SINT32 -> IntegralType.SINT32 - Type.TYPE_SINT64 -> IntegralType.SINT64 - Type.TYPE_SFIXED32 -> IntegralType.SFIXED32 - Type.TYPE_SFIXED64 -> IntegralType.SFIXED64 - - Type.TYPE_ENUM, Type.TYPE_MESSAGE, Type.TYPE_GROUP, null -> - error("Expected to find primitive type, instead got $type with name '$typeName'") - } - } - - private fun DescriptorProtos.FieldDescriptorProto.resolvedType(name: String, resolver: NameResolver): FieldType { - val entry = mapEntries[resolver.resolveOrNull(name)] - - if (entry != null) { - return FieldType.Map(entry) - } - - val fieldType = FieldType.Reference(lazy { resolver.resolve(name) }) - - return wrapWithLabel(fieldType) - } - - private fun DescriptorProtos.FieldDescriptorProto.wrapWithLabel(fieldType: FieldType): FieldType { - return when (label) { - DescriptorProtos.FieldDescriptorProto.Label.LABEL_REPEATED -> { - FieldType.List(fieldType) - } - // LABEL_OPTIONAL is not actually optional in proto3. - // Actual optional is oneOf with one option and same name - else -> { - fieldType - } - } - } - - private fun DescriptorProtos.OneofDescriptorProto.toModel( - index: Int, - resolver: NameResolver, - parent: FqName, - ): OneOfDeclaration? { - val name = name.fullProtoNameToKotlin(firstLetterUpper = true) - val fqName = resolver.declarationFqName(name, parent) - - val fields = oneOfFieldMembers[index] ?: return null - - val fieldResolver = resolver.withScope(fqName) - return OneOfDeclaration( - name = fqName, - 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, - ) - }, - null - ) - } - - private fun DescriptorProtos.EnumDescriptorProto.toModel( - resolver: NameResolver, - outerClassName: FqName, - parent: FqName, - ): EnumDeclaration { - val allowAlias = options.allowAlias - val originalEntries = mutableMapOf() - val aliases = mutableListOf() - - val enumName = resolver.declarationFqName(name.fullProtoNameToKotlin(firstLetterUpper = true), parent) - - valueList.forEach { enumEntry -> - val original = originalEntries[enumEntry.number] - if (original != null) { - if (!allowAlias) { - error( - "Aliases are not allowed for enum type $name: " + - "${enumEntry.number} of ${enumEntry.name} is already used by $original entry. " + - "Allow aliases via `allow_alias = true` option to avoid this error." - ) - } - - aliases.add( - EnumDeclaration.Alias( - name = resolver.declarationFqName(enumEntry.name, enumName), - original = original, - deprecated = enumEntry.options.deprecated, - doc = null, - ) - ) - } else { - originalEntries[enumEntry.number] = EnumDeclaration.Entry( - name = resolver.declarationFqName(enumEntry.name, enumName), - deprecated = enumEntry.options.deprecated, - doc = null, - ) - } - } - - originalEntries[-1] = EnumDeclaration.Entry( - name = resolver.declarationFqName(ENUM_UNRECOGNIZED, enumName), - deprecated = false, - doc = null, - ) - - return EnumDeclaration( - name = enumName, - outerClassName = outerClassName, - originalEntries = originalEntries.values.toList(), - aliases = aliases, - deprecated = options.deprecated, - doc = null, - ) - } - - private fun DescriptorProtos.ServiceDescriptorProto.toModel(resolver: NameResolver): ServiceDeclaration { - return ServiceDeclaration( - name = resolver.declarationFqName(name.fullProtoNameToKotlin(firstLetterUpper = true), packageName), - methods = methodList.map { it.toModel(resolver) } - ) - } - - private fun DescriptorProtos.MethodDescriptorProto.toModel(resolver: NameResolver): MethodDeclaration { - return MethodDeclaration( - name = name.fullProtoNameToKotlin(firstLetterUpper = false), - inputType = inputType - .substringAfter('.') // see typeName resolution - .fullProtoNameToKotlin(firstLetterUpper = true) - .let { - lazy { - messages[resolver.resolve(it)] - ?: error("Unknown message type $it, available: ${messages.keys.joinToString(",")}") - } - }, - outputType = outputType - .substringAfter('.') // see typeName resolution - .fullProtoNameToKotlin(firstLetterUpper = true) - .let { - lazy { - messages[resolver.resolve(it)] - ?: error("Unknown message type $it, available: ${messages.keys.joinToString(",")}") - } - }, - clientStreaming = clientStreaming, - serverStreaming = serverStreaming, - ) - } - - private fun String.fullProtoNameToKotlin(firstLetterUpper: Boolean = false): String { - val lastDelimiterIndex = indexOfLast { it == '.' || it == '/' } - return if (lastDelimiterIndex != -1) { - val packageName = substring(0, lastDelimiterIndex) - val name = substring(lastDelimiterIndex + 1) - val delimiter = this[lastDelimiterIndex] - return "$packageName$delimiter${name.simpleProtoNameToKotlin(firstLetterUpper = true)}" - } else { - simpleProtoNameToKotlin(firstLetterUpper) - } - } - - private val snakeRegExp = "(_[a-z]|-[a-z])".toRegex() - - private fun String.snakeToCamelCase(): String { - return replace(snakeRegExp) { it.value.last().uppercase() } - } - - private fun String.simpleProtoNameToKotlin(firstLetterUpper: Boolean = false): String { - return snakeToCamelCase().run { - if (firstLetterUpper) { - replaceFirstChar { it.uppercase() } - } else { - this - } - } - } - - private fun NameResolver.declarationFqName(simpleName: String, parent: FqName): FqName.Declaration { - return FqName.Declaration(simpleName, parent).also { add(it) } - } -} 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 3709bd2e1..99bac5237 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt @@ -9,14 +9,9 @@ import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.encoder.PatternLayoutEncoder import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.FileAppender -import com.google.protobuf.Descriptors import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.Feature -import kotlinx.rpc.protobuf.model.FieldDeclaration -import kotlinx.rpc.protobuf.model.FileDeclaration -import kotlinx.rpc.protobuf.model.MessageDeclaration -import kotlinx.rpc.protobuf.model.Model import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.helpers.NOPLogger @@ -26,9 +21,6 @@ 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 { @@ -80,16 +72,8 @@ class RpcProtobufPlugin { debugOutput = parameters[DEBUG_OUTPUT_OPTION] messageGenerationMode = MessageMode.of(parameters[MESSAGE_MODE_OPTION]) - targetCommon = parameters[TARGET_MODE_OPTION] == "common" - - // choose common generator if targetMode option was set. - val generatorFn = if (targetCommon) { - { input.generateKotlinCommonFiles() } - } else { - { input.generateKotlinJvmFiles() } - } - val files = generatorFn() + val files = input.generateKotlinCommonFiles() .map { file -> CodeGeneratorResponse.File.newBuilder() .apply { @@ -116,101 +100,11 @@ class RpcProtobufPlugin { .build() } - private fun CodeGeneratorRequest.generateKotlinJvmFiles(): List { - val interpreter = ProtoToModelInterpreter(logger) - val model = interpreter.interpretProtocRequest(this) - val fileGenerator = - ModelToKotlinJvmGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) - return fileGenerator.generateKotlinFiles() - } - private fun CodeGeneratorRequest.generateKotlinCommonFiles(): List { val model = this.toCommonModel() val fileGenerator = ModelToKotlinCommonGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) return fileGenerator.generateKotlinFiles() } - - private fun showFqNames(model: Model, descriptors: List) { - - System.err.println("Model:") - for (file in model.files) { - file.print("") - } - - System.err.println(" ----------------------- ") - - System.err.println("Descriptors:") - for (file in descriptors) { - file.print("") - } - - println("Make it crash with this print") - } - - private fun FileDeclaration.print(intent: String) { - System.err.println("[FILE] $intent- ${packageName.simpleName}: ${packageName}") - for (msg in messageDeclarations) { - msg.print(intent + "\t") - } - } - - private fun MessageDeclaration.print(intent: String) { - System.err.println("[MSG] $intent- ${name.simpleName}: ${name}") - for (field in actualFields) { - field.print(intent + "\t") - } - for (msg in nestedDeclarations) { - msg.print(intent + "\t") - } - } - - private fun FieldDeclaration.print(intent: String) { - System.err.println("[FIELD] $intent- ${this.name}") - } - - private fun Descriptors.FileDescriptor.print(intent: String) { - System.err.println("[FILE] $intent- ${fqName().simpleName}: ${fqName()}") - for (msg in messageTypes) { - msg.print(intent + "\t") - } - } - - private fun Descriptors.Descriptor.print(intent: String) { - System.err.println("[MSG] $intent- ${fqName().simpleName}: ${fqName()}") - for (field in fields) { - field.print(intent + "\t") - } - for (msg in nestedTypes) { - msg.print(intent + "\t") - } - } - - private fun Descriptors.FieldDescriptor.print(intent: String) { - System.err.println("[FIELD] $intent- ${fqName().simpleName}: ${ktTypeName()}") - } - - private fun Descriptors.FieldDescriptor.ktTypeName(): String { - return when (type) { - Descriptors.FieldDescriptor.Type.DOUBLE -> "Double" - Descriptors.FieldDescriptor.Type.FLOAT -> "Float" - Descriptors.FieldDescriptor.Type.INT64 -> "Long" - Descriptors.FieldDescriptor.Type.UINT64 -> "ULong" - Descriptors.FieldDescriptor.Type.INT32 -> "Int" - Descriptors.FieldDescriptor.Type.FIXED64 -> "ULong" - Descriptors.FieldDescriptor.Type.FIXED32 -> "UInt" - Descriptors.FieldDescriptor.Type.BOOL -> "Boolean" - Descriptors.FieldDescriptor.Type.STRING -> "String" - Descriptors.FieldDescriptor.Type.BYTES -> "ByteArray" - Descriptors.FieldDescriptor.Type.UINT32 -> "UInt" - Descriptors.FieldDescriptor.Type.SFIXED32 -> "Int" - Descriptors.FieldDescriptor.Type.SFIXED64 -> "Long" - Descriptors.FieldDescriptor.Type.SINT32 -> "Int" - Descriptors.FieldDescriptor.Type.SINT64 -> "Long" - Descriptors.FieldDescriptor.Type.ENUM -> "" - Descriptors.FieldDescriptor.Type.MESSAGE -> messageType!!.fqName().toString() - Descriptors.FieldDescriptor.Type.GROUP -> error("GROUP is unsupported") - } - } } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/DescriptorToModel.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt similarity index 88% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/DescriptorToModel.kt rename to protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt index d7a938060..92ae65500 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/DescriptorToModel.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt @@ -22,6 +22,23 @@ fun CodeGeneratorRequest.toCommonModel(): Model { ) } + +private fun DescriptorProtos.FileDescriptorProto.toDescriptor( + protoFileMap: Map, + cache: MutableMap +): Descriptors.FileDescriptor { + if (cache.containsKey(name)) return cache[name]!! + + val dependencies = dependencyList.map { depName -> + val depProto = protoFileMap[depName] ?: error("Missing dependency: $depName") + depProto.toDescriptor(protoFileMap, cache) + }.toTypedArray() + + val fileDescriptor = Descriptors.FileDescriptor.buildFrom(this, dependencies) + cache[name] = fileDescriptor + return fileDescriptor +} + private inline fun D.cached(block: (D) -> T): T where D : Descriptors.GenericDescriptor, T : Any { if (modelCache.containsKey(this)) { @@ -149,23 +166,6 @@ private fun Descriptors.MethodDescriptor.toCommonModel(): MethodDeclaration = ca ) } - -private fun DescriptorProtos.FileDescriptorProto.toDescriptor( - protoFileMap: Map, - cache: MutableMap -): Descriptors.FileDescriptor { - if (cache.containsKey(name)) return cache[name]!! - - val dependencies = dependencyList.map { depName -> - val depProto = protoFileMap[depName] ?: error("Missing dependency: $depName") - depProto.toDescriptor(protoFileMap, cache) - }.toTypedArray() - - val fileDescriptor = Descriptors.FileDescriptor.buildFrom(this, dependencies) - cache[name] = fileDescriptor - return fileDescriptor -} - //// Type Conversion Extension //// private fun Descriptors.FieldDescriptor.modelType(): FieldType { @@ -199,6 +199,27 @@ private fun Descriptors.FieldDescriptor.modelType(): FieldType { return baseType } +//// GenericDescriptor Extensions //// + +private fun Descriptors.GenericDescriptor.fqName(): FqName { + val nameCapital = name.simpleProtoNameToKotlin(firstLetterUpper = true) + val nameLower = name.simpleProtoNameToKotlin() + return when (this) { + is Descriptors.FileDescriptor -> FqName.Package.fromString(`package`) + is Descriptors.Descriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) + is Descriptors.FieldDescriptor -> { + val usedName = if (realContainingOneof != null) nameCapital else nameLower + FqName.Declaration(usedName, containingType?.fqName() ?: file.fqName()) + } + + is Descriptors.EnumValueDescriptor -> FqName.Declaration(name, type.fqName()) + is Descriptors.OneofDescriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) + is Descriptors.ServiceDescriptor -> FqName.Declaration(nameCapital, file?.fqName() ?: file.fqName()) + is Descriptors.MethodDescriptor -> FqName.Declaration(nameLower, service?.fqName() ?: file.fqName()) + else -> error("Unknown generic descriptor: $this") + } +} + //// Utility Extensions //// private fun Descriptors.FileDescriptor.kotlinFileName(): String { diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt index 8f469b293..d93ccf56d 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt @@ -1,193 +1,3 @@ /* * 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.protobuf.model - -import org.slf4j.Logger - -internal class NameResolver private constructor( - private val logger: Logger, - private val root: Node = Node(FqName.Package.Root.simpleName, FqName.Package.Root), - private val scope: Node = root, - private val imports: List = emptyList(), -) { - fun add(name: FqName) { - val parentNode = getParentNodeFor(name) - - if (parentNode.children.containsKey(name.simpleName)) { - error("Name ${name.fullName()} already exists") - } - - parentNode.children[name.simpleName] = Node(name.simpleName, name, parentNode) - } - - private fun getParentNodeFor(name: FqName): Node { - var next: FqName = name - val parents = mutableListOf() - while (next.parent != next) { - next = next.parent - if (next != next.parent) { - parents += next - } - } - - var parentNode = root - parents.reversed().forEach { parent -> - parentNode = parentNode.resolve(parent) - } - return parentNode - } - - private fun Node.resolve(name: FqName): Node { - if (name is FqName.Declaration && !children.containsKey(name.simpleName)) { - error("Name ${name.fullName()} doesn't exist, can't resolve ${name.fullName()}") - } - - return children.getOrPut(name.simpleName) { - Node(name.simpleName, name, this) - } - } - - fun resolve(name: String): FqName { - return resolveOrNull(name) ?: error("Name $name doesn't exist") - } - - fun resolveOrNull(name: String): FqName? { - val (parents, simpleName) = name.asParentsAndSimpleName() - - return resolveInScope(parents, simpleName, scope) - ?: run { - for (import in imports) { - resolveInScope(parents, simpleName, import) - ?.let { return it } - } - - null - } - } - - private fun resolveInScope(parents: List, simpleName: String, scope: Node): FqName? { - val inScope = resolveFromNode(scope, parents, simpleName) - - return when (inScope) { - is ResolveResult.Success -> { - inScope.fqName - } - - is ResolveResult.PartialResolve -> { - null - } - - is ResolveResult.NoResolve -> { - val inRoot = resolveFromNode(root, parents, simpleName) - - when (inRoot) { - is ResolveResult.Success -> { - inRoot.fqName - } - - else -> { - var node = scope.parent - while (node != null && node.fqName is FqName.Declaration) { - val inImport = resolveFromNode(node, parents, simpleName) - - if (inImport is ResolveResult.Success) { - return inImport.fqName - } - - node = node.parent - } - - null - } - } - } - } - } - - private fun resolveFromNode(start: Node, parents: List, simpleName: String): ResolveResult { - val declarationOnlyResolve = start.fqName != FqName.Package.Root && start.fqName is FqName.Package - var node: Node? = start - var i = 0 - var entered = false - while (i != parents.size) { - node = node?.children[parents[i++]] - if (node == null) { - break - } else if (node.fqName is FqName.Package && declarationOnlyResolve) { - return ResolveResult.NoResolve - } - entered = true - } - - if (node != null) { - val name = node.children[simpleName] - if (name != null) { - return ResolveResult.Success(name.fqName) - } - } - - return if (entered) { - ResolveResult.PartialResolve - } else { - ResolveResult.NoResolve - } - } - - sealed interface ResolveResult { - data class Success(val fqName: FqName) : ResolveResult - data object PartialResolve : ResolveResult - data object NoResolve : ResolveResult - } - - fun withImports(imports: List): NameResolver { - return NameResolver(logger, root, scope, imports.map { getParentNodeFor(it).resolve(it) }) - } - - fun withScope(name: FqName): NameResolver { - val node = getParentNodeFor(name).resolve(name) - return NameResolver(logger, root, node, imports) - } - - companion object { - fun create(logger: Logger): NameResolver { - return NameResolver(logger) - } - } - - private class Node( - val name: String, - val fqName: FqName, - val parent: Node? = null, - val children: MutableMap = mutableMapOf(), - ) { - private var _list: List? = null - - fun asList(): List { - if (_list != null) { - return _list!! - } - - if (parent == null) { - return emptyList() - } - - _list = parent.asList() + name - return _list!! - } - } - - @Suppress("unused") - fun pprint(): String { - return buildString { pprint(root, 0) } - } - - private fun StringBuilder.pprint(node: Node, indent: Int) { - val spaces = " ".repeat(indent) - appendLine("$spaces${node.fqName.fullName()}") - for (child in node.children.values) { - pprint(child, indent + 4) - } - } -} + */ \ No newline at end of file From 8c6ae5638bac8ade4f4b0edc14f2ad3d32bd256f Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Thu, 31 Jul 2025 17:40:16 +0200 Subject: [PATCH 03/11] protoc-plugin: Move all declarations into model.kt Signed-off-by: Johannes Zottele --- .../rpc/protobuf/model/EnumDeclaration.kt | 27 --- .../{FieldDeclaration.kt => FieldType.kt} | 28 --- .../rpc/protobuf/model/FileDeclaration.kt | 16 -- .../rpc/protobuf/model/MessageDeclaration.kt | 16 -- .../rpc/protobuf/model/MethodDeclaration.kt | 13 -- .../kotlinx/rpc/protobuf/model/Model.kt | 9 - .../rpc/protobuf/model/NameResolver.kt | 196 +++++++++++++++++- .../rpc/protobuf/model/OneOfDeclaration.kt | 13 -- .../rpc/protobuf/model/ServiceDeclaration.kt | 10 - .../kotlinx/rpc/protobuf/model/model.kt | 101 +++++++++ 10 files changed, 296 insertions(+), 133 deletions(-) delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/EnumDeclaration.kt rename protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/{FieldDeclaration.kt => FieldType.kt} (70%) delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FileDeclaration.kt delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/MessageDeclaration.kt delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/MethodDeclaration.kt delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/Model.kt delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/OneOfDeclaration.kt delete mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/ServiceDeclaration.kt create mode 100644 protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/EnumDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/EnumDeclaration.kt deleted file mode 100644 index 89ede3e25..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/EnumDeclaration.kt +++ /dev/null @@ -1,27 +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.protobuf.model - -data class EnumDeclaration( - val outerClassName: FqName, - val name: FqName, - val originalEntries: List, - val aliases: List, - val deprecated: Boolean, - val doc: String?, -) { - data class Entry( - val name: FqName, - val deprecated: Boolean, - val doc: String?, - ) - - data class Alias( - val name: FqName, - val original: Entry, - val deprecated: Boolean, - val doc: String?, - ) -} diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldType.kt similarity index 70% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt rename to protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldType.kt index f76f321a9..50f50846f 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldDeclaration.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldType.kt @@ -4,34 +4,6 @@ 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, diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FileDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FileDeclaration.kt deleted file mode 100644 index 843817361..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FileDeclaration.kt +++ /dev/null @@ -1,16 +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.protobuf.model - -data class FileDeclaration( - val name: String, - val packageName: FqName.Package, - val dependencies: List, - val messageDeclarations: List, - val enumDeclarations: List, - val serviceDeclarations: List, - val deprecated: Boolean, - val doc: String?, -) diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/MessageDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/MessageDeclaration.kt deleted file mode 100644 index 675c982a9..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/MessageDeclaration.kt +++ /dev/null @@ -1,16 +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.protobuf.model - -data class MessageDeclaration( - val outerClassName: FqName, - val name: FqName, - val actualFields: List, // excludes oneOf fields, but includes oneOf itself - val oneOfDeclarations: List, - val enumDeclarations: List, - val nestedDeclarations: List, - val deprecated: Boolean, - val doc: String?, -) diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/MethodDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/MethodDeclaration.kt deleted file mode 100644 index 1c3527b76..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/MethodDeclaration.kt +++ /dev/null @@ -1,13 +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.protobuf.model - -data class MethodDeclaration( - val name: String, - val clientStreaming: Boolean, - val serverStreaming: Boolean, - val inputType: Lazy, - val outputType: Lazy, -) diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/Model.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/Model.kt deleted file mode 100644 index 5cb43129a..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/Model.kt +++ /dev/null @@ -1,9 +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.protobuf.model - -data class Model( - val files: List, -) diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt index d93ccf56d..fb136714a 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt @@ -1,3 +1,197 @@ /* * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ \ No newline at end of file + */ + +// The NameResolver isn't used as we rely on the Java Descriptor for message resolving. +// However, it might be useful if we write a bootstrapped plugin in K/N. +// +//package kotlinx.rpc.protobuf.model +// +//import org.slf4j.Logger +// +// +//internal class NameResolver private constructor( +// private val logger: Logger, +// private val root: Node = Node(FqName.Package.Root.simpleName, FqName.Package.Root), +// private val scope: Node = root, +// private val imports: List = emptyList(), +//) { +// fun add(name: FqName) { +// val parentNode = getParentNodeFor(name) +// +// if (parentNode.children.containsKey(name.simpleName)) { +// error("Name ${name.fullName()} already exists") +// } +// +// parentNode.children[name.simpleName] = Node(name.simpleName, name, parentNode) +// } +// +// private fun getParentNodeFor(name: FqName): Node { +// var next: FqName = name +// val parents = mutableListOf() +// while (next.parent != next) { +// next = next.parent +// if (next != next.parent) { +// parents += next +// } +// } +// +// var parentNode = root +// parents.reversed().forEach { parent -> +// parentNode = parentNode.resolve(parent) +// } +// return parentNode +// } +// +// private fun Node.resolve(name: FqName): Node { +// if (name is FqName.Declaration && !children.containsKey(name.simpleName)) { +// error("Name ${name.fullName()} doesn't exist, can't resolve ${name.fullName()}") +// } +// +// return children.getOrPut(name.simpleName) { +// Node(name.simpleName, name, this) +// } +// } +// +// fun resolve(name: String): FqName { +// return resolveOrNull(name) ?: error("Name $name doesn't exist") +// } +// +// fun resolveOrNull(name: String): FqName? { +// val (parents, simpleName) = name.asParentsAndSimpleName() +// +// return resolveInScope(parents, simpleName, scope) +// ?: run { +// for (import in imports) { +// resolveInScope(parents, simpleName, import) +// ?.let { return it } +// } +// +// null +// } +// } +// +// private fun resolveInScope(parents: List, simpleName: String, scope: Node): FqName? { +// val inScope = resolveFromNode(scope, parents, simpleName) +// +// return when (inScope) { +// is ResolveResult.Success -> { +// inScope.fqName +// } +// +// is ResolveResult.PartialResolve -> { +// null +// } +// +// is ResolveResult.NoResolve -> { +// val inRoot = resolveFromNode(root, parents, simpleName) +// +// when (inRoot) { +// is ResolveResult.Success -> { +// inRoot.fqName +// } +// +// else -> { +// var node = scope.parent +// while (node != null && node.fqName is FqName.Declaration) { +// val inImport = resolveFromNode(node, parents, simpleName) +// +// if (inImport is ResolveResult.Success) { +// return inImport.fqName +// } +// +// node = node.parent +// } +// +// null +// } +// } +// } +// } +// } +// +// private fun resolveFromNode(start: Node, parents: List, simpleName: String): ResolveResult { +// val declarationOnlyResolve = start.fqName != FqName.Package.Root && start.fqName is FqName.Package +// var node: Node? = start +// var i = 0 +// var entered = false +// while (i != parents.size) { +// node = node?.children[parents[i++]] +// if (node == null) { +// break +// } else if (node.fqName is FqName.Package && declarationOnlyResolve) { +// return ResolveResult.NoResolve +// } +// entered = true +// } +// +// if (node != null) { +// val name = node.children[simpleName] +// if (name != null) { +// return ResolveResult.Success(name.fqName) +// } +// } +// +// return if (entered) { +// ResolveResult.PartialResolve +// } else { +// ResolveResult.NoResolve +// } +// } +// +// sealed interface ResolveResult { +// data class Success(val fqName: FqName) : ResolveResult +// data object PartialResolve : ResolveResult +// data object NoResolve : ResolveResult +// } +// +// fun withImports(imports: List): NameResolver { +// return NameResolver(logger, root, scope, imports.map { getParentNodeFor(it).resolve(it) }) +// } +// +// fun withScope(name: FqName): NameResolver { +// val node = getParentNodeFor(name).resolve(name) +// return NameResolver(logger, root, node, imports) +// } +// +// companion object { +// fun create(logger: Logger): NameResolver { +// return NameResolver(logger) +// } +// } +// +// private class Node( +// val name: String, +// val fqName: FqName, +// val parent: Node? = null, +// val children: MutableMap = mutableMapOf(), +// ) { +// private var _list: List? = null +// +// fun asList(): List { +// if (_list != null) { +// return _list!! +// } +// +// if (parent == null) { +// return emptyList() +// } +// +// _list = parent.asList() + name +// return _list!! +// } +// } +// +// @Suppress("unused") +// fun pprint(): String { +// return buildString { pprint(root, 0) } +// } +// +// private fun StringBuilder.pprint(node: Node, indent: Int) { +// val spaces = " ".repeat(indent) +// appendLine("$spaces${node.fqName.fullName()}") +// for (child in node.children.values) { +// pprint(child, indent + 4) +// } +// } +//} \ No newline at end of file diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/OneOfDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/OneOfDeclaration.kt deleted file mode 100644 index d7737072d..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/OneOfDeclaration.kt +++ /dev/null @@ -1,13 +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.protobuf.model - -import com.google.protobuf.Descriptors - -data class OneOfDeclaration( - val name: FqName, - val variants: List, - val descriptor: Descriptors.OneofDescriptor? -) diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/ServiceDeclaration.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/ServiceDeclaration.kt deleted file mode 100644 index 54e635d62..000000000 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/ServiceDeclaration.kt +++ /dev/null @@ -1,10 +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.protobuf.model - -data class ServiceDeclaration( - val name: FqName, - val methods: List, -) diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt new file mode 100644 index 000000000..0bca41512 --- /dev/null +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt @@ -0,0 +1,101 @@ +/* + * 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.protobuf.model + +import com.google.protobuf.DescriptorProtos +import com.google.protobuf.Descriptors + +data class Model( + val files: List, +) + +data class FileDeclaration( + val name: String, + val packageName: FqName.Package, + val dependencies: List, + val messageDeclarations: List, + val enumDeclarations: List, + val serviceDeclarations: List, + val deprecated: Boolean, + val doc: String?, +) + +data class MessageDeclaration( + val outerClassName: FqName, + val name: FqName, + val actualFields: List, // excludes oneOf fields, but includes oneOf itself + val oneOfDeclarations: List, + val enumDeclarations: List, + val nestedDeclarations: List, + val deprecated: Boolean, + val doc: String?, +) + +data class EnumDeclaration( + val outerClassName: FqName, + val name: FqName, + val originalEntries: List, + val aliases: List, + val deprecated: Boolean, + val doc: String?, +) { + data class Entry( + val name: FqName, + val deprecated: Boolean, + val doc: String?, + ) + + data class Alias( + val name: FqName, + val original: Entry, + val deprecated: Boolean, + val doc: String?, + ) +} + +data class OneOfDeclaration( + val name: FqName, + val variants: List, + val descriptor: Descriptors.OneofDescriptor? +) + +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 +} + +data class ServiceDeclaration( + val name: FqName, + val methods: List, +) + +data class MethodDeclaration( + val name: String, + val clientStreaming: Boolean, + val serverStreaming: Boolean, + val inputType: Lazy, + val outputType: Lazy, +) + From ec3a536f262d39906ea0364818b2d9bdd579014c Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Thu, 31 Jul 2025 18:14:14 +0200 Subject: [PATCH 04/11] protoc-plugin: Add descriptors to declarations and remove unnecessary fields Signed-off-by: Johannes Zottele --- .../protobuf/ModelToKotlinCommonGenerator.kt | 20 ++++----- .../rpc/protobuf/codeRequestToModel.kt | 35 ++++++--------- .../kotlinx/rpc/protobuf/model/model.kt | 45 +++++++------------ 3 files changed, 38 insertions(+), 62 deletions(-) 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 f4b92815c..3bfa949a7 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt @@ -213,7 +213,7 @@ class ModelToKotlinCommonGenerator( code("$assignment decoder.read$encFuncName()") } - is FieldType.List -> if (field.packed) { + is FieldType.List -> if (field.dec.isPacked) { whenCase("tag.fieldNr == ${field.number} && tag.wireType == WireType.LENGTH_DELIMITED") { code("$assignment decoder.readPacked${fieldType.value.decodeEncodeFuncName()}()") } @@ -241,7 +241,7 @@ class ModelToKotlinCommonGenerator( scope("$fieldName?.also") { code(field.writeValue("it")) } - } else if (!field.hasPresence) { + } else if (!field.dec.hasPresence()) { ifBranch(condition = field.defaultCheck(), ifBlock = { code(field.writeValue(field.name)) }) @@ -255,10 +255,10 @@ class ModelToKotlinCommonGenerator( return when (val fieldType = type) { is FieldType.IntegralType -> "encoder.write${type.decodeEncodeFuncName()}($number, $variable)" is FieldType.List -> when { - packed && packedFixedSize -> + dec.isPacked && packedFixedSize -> "encoder.writePacked${fieldType.value.decodeEncodeFuncName()}($number, $variable)" - packed && !packedFixedSize -> + dec.isPacked && !packedFixedSize -> "encoder.writePacked${fieldType.value.decodeEncodeFuncName()}($number, $variable, ${ wireSizeCall( variable @@ -285,7 +285,7 @@ class ModelToKotlinCommonGenerator( } is FieldType.List -> when { - isPackable && !packedFixedSize -> sizeFunc + dec.isPacked && !packedFixedSize -> sizeFunc else -> error("Unexpected use of size call for field: $name, type: $fieldType") } @@ -431,13 +431,13 @@ class ModelToKotlinCommonGenerator( 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 + val inputType = method.inputType + val outputType = 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), + modifiers = if (method.dec.isServerStreaming) "" else "suspend", + args = "message: ${inputType.name.safeFullName().wrapInFlowIf(method.dec.isClientStreaming)}", + returnType = outputType.name.safeFullName().wrapInFlowIf(method.dec.isServerStreaming), ) } } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt index 92ae65500..387bfda33 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt @@ -57,8 +57,8 @@ private fun Descriptors.FileDescriptor.toCommonModel(): FileDeclaration = cached messageDeclarations = messageTypes.map { it.toCommonModel() }, enumDeclarations = enumTypes.map { it.toCommonModel() }, serviceDeclarations = services.map { it.toCommonModel() }, - deprecated = options.deprecated, doc = null, + dec = this, ) } @@ -66,15 +66,14 @@ private fun Descriptors.Descriptor.toCommonModel(): MessageDeclaration = cached val regularFields = fields.filter { field -> field.realContainingOneof == null }.map { it.toCommonModel() } return MessageDeclaration( - outerClassName = fqName(), name = fqName(), actualFields = regularFields, // get all oneof declarations that are not created from an optional in proto3 https://github.com/googleapis/api-linter/issues/1323 oneOfDeclarations = oneofs.filter { it.fields[0].realContainingOneof != null }.map { it.toCommonModel() }, enumDeclarations = enumTypes.map { it.toCommonModel() }, nestedDeclarations = nestedTypes.map { it.toCommonModel() }, - deprecated = options.deprecated, doc = null, + dec = this, ) } @@ -82,25 +81,18 @@ private fun Descriptors.FieldDescriptor.toCommonModel(): FieldDeclaration = cach toProto().hasProto3Optional() return FieldDeclaration( name = fqName().simpleName, - number = number, type = modelType(), - nullable = isNullable(), - deprecated = options.deprecated, doc = null, - proto = toProto(), + dec = this, ) } -private fun Descriptors.FieldDescriptor.isNullable(): Boolean { - // aligns with edition settings and backward compatibility with proto2 and proto3 - return hasPresence() && !isRequired && !hasDefaultValue() -} private fun Descriptors.OneofDescriptor.toCommonModel(): OneOfDeclaration = cached { return OneOfDeclaration( name = fqName(), variants = fields.map { it.toCommonModel() }, - descriptor = this + dec = this, ) } @@ -122,20 +114,19 @@ private fun Descriptors.EnumDescriptor.toCommonModel(): EnumDeclaration = cached } return EnumDeclaration( - outerClassName = fqName(), name = fqName(), originalEntries = entriesMap.values.toList(), aliases = aliases, - deprecated = options.deprecated, - doc = null + doc = null, + dec = this, ) } private fun Descriptors.EnumValueDescriptor.toCommonModel(): EnumDeclaration.Entry = cached { return EnumDeclaration.Entry( name = fqName(), - deprecated = options.deprecated, doc = null, + dec = this, ) } @@ -144,25 +135,25 @@ private fun Descriptors.EnumValueDescriptor.toAliasModel(original: EnumDeclarati return EnumDeclaration.Alias( name = fqName(), original = original, - deprecated = options.deprecated, doc = null, + dec = this, ) } private fun Descriptors.ServiceDescriptor.toCommonModel(): ServiceDeclaration = cached { return ServiceDeclaration( name = fqName(), - methods = methods.map { it.toCommonModel() } + methods = methods.map { it.toCommonModel() }, + dec = this, ) } private fun Descriptors.MethodDescriptor.toCommonModel(): MethodDeclaration = cached { return MethodDeclaration( name = name, - clientStreaming = isClientStreaming, - serverStreaming = isServerStreaming, - inputType = lazy { inputType.toCommonModel() }, - outputType = lazy { outputType.toCommonModel() } + inputType = inputType.toCommonModel(), + outputType = outputType.toCommonModel(), + dec = this, ) } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt index 0bca41512..57267380a 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt @@ -4,7 +4,6 @@ package kotlinx.rpc.protobuf.model -import com.google.protobuf.DescriptorProtos import com.google.protobuf.Descriptors data class Model( @@ -18,84 +17,70 @@ data class FileDeclaration( val messageDeclarations: List, val enumDeclarations: List, val serviceDeclarations: List, - val deprecated: Boolean, val doc: String?, + val dec: Descriptors.FileDescriptor, ) data class MessageDeclaration( - val outerClassName: FqName, val name: FqName, val actualFields: List, // excludes oneOf fields, but includes oneOf itself val oneOfDeclarations: List, val enumDeclarations: List, val nestedDeclarations: List, - val deprecated: Boolean, val doc: String?, + val dec: Descriptors.Descriptor, ) data class EnumDeclaration( - val outerClassName: FqName, val name: FqName, val originalEntries: List, val aliases: List, - val deprecated: Boolean, val doc: String?, + val dec: Descriptors.EnumDescriptor, ) { data class Entry( val name: FqName, - val deprecated: Boolean, val doc: String?, + val dec: Descriptors.EnumValueDescriptor, ) data class Alias( val name: FqName, val original: Entry, - val deprecated: Boolean, val doc: String?, + val dec: Descriptors.EnumValueDescriptor, ) } data class OneOfDeclaration( val name: FqName, val variants: List, - val descriptor: Descriptors.OneofDescriptor? + val dec: Descriptors.OneofDescriptor ) 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 dec: Descriptors.FieldDescriptor ) { - 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 + + // aligns with edition settings and backward compatibility with proto2 and proto3 + val nullable: Boolean = dec.hasPresence() && !dec.isRequired && !dec.hasDefaultValue() + val number: Int = dec.number } data class ServiceDeclaration( val name: FqName, val methods: List, + val dec: Descriptors.ServiceDescriptor, ) data class MethodDeclaration( val name: String, - val clientStreaming: Boolean, - val serverStreaming: Boolean, - val inputType: Lazy, - val outputType: Lazy, + val inputType: MessageDeclaration, + val outputType: MessageDeclaration, + val dec: Descriptors.MethodDescriptor, ) From d811cb0f71dd4c2acb738bbf36605e4c1e59aed7 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Thu, 31 Jul 2025 18:40:39 +0200 Subject: [PATCH 05/11] protoc-plugin: Add Map types Signed-off-by: Johannes Zottele --- .../kotlinx/rpc/protobuf/CodeGenerator.kt | 14 +- .../protobuf/ModelToKotlinCommonGenerator.kt | 5 +- .../kotlinx/rpc/protobuf/RpcProtobufPlugin.kt | 24 +--- .../rpc/protobuf/codeRequestToModel.kt | 127 +++++++++++------- 4 files changed, 87 insertions(+), 83 deletions(-) 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 006faee13..b1a6cdeff 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt @@ -7,12 +7,8 @@ package kotlinx.rpc.protobuf import org.slf4j.Logger import org.slf4j.helpers.NOPLogger -data class CodeGenerationParameters( - val messageMode: RpcProtobufPlugin.MessageMode, -) open class CodeGenerator( - val parameters: CodeGenerationParameters, private val indent: String, private val builder: StringBuilder = StringBuilder(), private val logger: Logger = NOPLogger.NOP_LOGGER, @@ -68,7 +64,7 @@ open class CodeGenerator( } private fun withNextIndent(block: CodeGenerator.() -> Unit) { - CodeGenerator(parameters, "$indent$ONE_INDENT", builder, logger).block() + CodeGenerator("$indent$ONE_INDENT", builder, logger).block() } internal fun scope( @@ -132,7 +128,7 @@ open class CodeGenerator( return } - val nested = CodeGenerator(parameters, "$indent$ONE_INDENT", logger = logger).apply(block) + val nested = CodeGenerator("$indent$ONE_INDENT", logger = logger).apply(block) if (nested.isEmpty) { newLine() @@ -306,13 +302,12 @@ open class CodeGenerator( } class FileGenerator( - codeGenerationParameters: CodeGenerationParameters, var filename: String? = null, var packageName: String? = null, var packagePath: String? = packageName, var fileOptIns: List = emptyList(), logger: Logger = NOPLogger.NOP_LOGGER, -) : CodeGenerator(codeGenerationParameters, "", logger = logger) { +) : CodeGenerator("", logger = logger) { private val imports = mutableListOf() fun importPackage(name: String) { @@ -354,10 +349,9 @@ class FileGenerator( } fun file( - codeGenerationParameters: CodeGenerationParameters, name: String? = null, packageName: String? = null, logger: Logger = NOPLogger.NOP_LOGGER, block: FileGenerator.() -> Unit, -): FileGenerator = FileGenerator(codeGenerationParameters, name, packageName, packageName, emptyList(), logger) +): FileGenerator = FileGenerator(name, packageName, packageName, emptyList(), logger) .apply(block) 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 3bfa949a7..fadeaebcb 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt @@ -15,7 +15,6 @@ 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() } @@ -36,7 +35,7 @@ class ModelToKotlinCommonGenerator( private fun FileDeclaration.generatePublicKotlinFile(): FileGenerator { currentPackage = packageName - return file(codeGenerationParameters, logger = logger) { + return file(logger = logger) { filename = this@generatePublicKotlinFile.name packageName = this@generatePublicKotlinFile.packageName.safeFullName() packagePath = this@generatePublicKotlinFile.packageName.safeFullName() @@ -61,7 +60,7 @@ class ModelToKotlinCommonGenerator( private fun FileDeclaration.generateInternalKotlinFile(): FileGenerator { currentPackage = packageName - return file(codeGenerationParameters, logger = logger) { + return file(logger = logger) { filename = this@generateInternalKotlinFile.name packageName = this@generateInternalKotlinFile.packageName.safeFullName() packagePath = 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 99bac5237..4ff31adbf 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt @@ -20,27 +20,9 @@ import java.io.File class RpcProtobufPlugin { companion object { private const val DEBUG_OUTPUT_OPTION = "debugOutput" - private const val MESSAGE_MODE_OPTION = "messageMode" - } - - enum class MessageMode { - Interface, Class; - - companion object { - fun of(value: String?): MessageMode { - return when (value) { - "interface" -> Interface - "class" -> Class - null -> error("Message mode is not specified, use --messageMode=interface or --messageMode=class") - else -> error("Unknown message mode: $value") - } - } - } } 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,7 +53,6 @@ class RpcProtobufPlugin { } debugOutput = parameters[DEBUG_OUTPUT_OPTION] - messageGenerationMode = MessageMode.of(parameters[MESSAGE_MODE_OPTION]) val files = input.generateKotlinCommonFiles() .map { file -> @@ -101,9 +82,8 @@ class RpcProtobufPlugin { } private fun CodeGeneratorRequest.generateKotlinCommonFiles(): List { - val model = this.toCommonModel() - val fileGenerator = - ModelToKotlinCommonGenerator(model, logger, CodeGenerationParameters(messageGenerationMode)) + val model = this.toModel() + val fileGenerator = ModelToKotlinCommonGenerator(model, logger) return fileGenerator.generateKotlinFiles() } } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt index 387bfda33..078f6af6f 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt @@ -11,18 +11,33 @@ import kotlinx.rpc.protobuf.model.* private val modelCache = mutableMapOf() -fun CodeGeneratorRequest.toCommonModel(): Model { +/** + * Converts a [CodeGeneratorRequest] into the protoc plugin [Model] of the protobuf. + * + * @return a [Model] instance containing a list of [FileDeclaration]s that represent + * the converted protobuf files. + */ +fun CodeGeneratorRequest.toModel(): Model { val protoFileMap = protoFileList.associateBy { it.name } val fileDescriptors = mutableMapOf() val files = protoFileList.map { protoFile -> protoFile.toDescriptor(protoFileMap, fileDescriptors) } return Model( - files = files.map { it.toCommonModel() } + files = files.map { it.toModel() } ) } +/** + * Converts a [DescriptorProtos.FileDescriptorProto] instance to a [Descriptors.FileDescriptor], + * resolving its dependencies. + * + * @param protoFileMap a map of file names to `FileDescriptorProto` objects which are available for resolution. + * @param cache a mutable map that stores already resolved `FileDescriptor` objects by file name to prevent + * redundant computations. + * @return the resolved `FileDescriptor` instance corresponding to this `FileDescriptorProto`. + */ private fun DescriptorProtos.FileDescriptorProto.toDescriptor( protoFileMap: Map, cache: MutableMap @@ -39,6 +54,38 @@ private fun DescriptorProtos.FileDescriptorProto.toDescriptor( return fileDescriptor } +/** + * Returns the fully qualified name [FqName] of this descriptor, resolving it to the most specific + * declaration or package name based on its type. + * + * Depending on the type of the descriptor, the fully qualified name is computed recursively, + * using the containing type or file, and appropriately converting names. + * + * @return The fully qualified name represented as an instance of FqName, specific to the descriptor's context. + */ +private fun Descriptors.GenericDescriptor.fqName(): FqName { + val nameCapital = name.simpleProtoNameToKotlin(firstLetterUpper = true) + val nameLower = name.simpleProtoNameToKotlin() + return when (this) { + is Descriptors.FileDescriptor -> FqName.Package.fromString(`package`) + is Descriptors.Descriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) + is Descriptors.FieldDescriptor -> { + val usedName = if (realContainingOneof != null) nameCapital else nameLower + FqName.Declaration(usedName, containingType?.fqName() ?: file.fqName()) + } + + is Descriptors.EnumValueDescriptor -> FqName.Declaration(name, type.fqName()) + is Descriptors.OneofDescriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) + is Descriptors.ServiceDescriptor -> FqName.Declaration(nameCapital, file?.fqName() ?: file.fqName()) + is Descriptors.MethodDescriptor -> FqName.Declaration(nameLower, service?.fqName() ?: file.fqName()) + else -> error("Unknown generic descriptor: $this") + } +} + +/** + * Caches the `descriptor.toModel()` result in the [modelCache] to ensure that only a single object + * per descriptor exists. + */ private inline fun D.cached(block: (D) -> T): T where D : Descriptors.GenericDescriptor, T : Any { if (modelCache.containsKey(this)) { @@ -49,35 +96,35 @@ private inline fun D.cached(block: (D) -> T): T return declaration } -private fun Descriptors.FileDescriptor.toCommonModel(): FileDeclaration = cached { +private fun Descriptors.FileDescriptor.toModel(): FileDeclaration = cached { return FileDeclaration( name = kotlinFileName(), packageName = FqName.Package.fromString(`package`), - dependencies = dependencies.map { it.toCommonModel() }, - messageDeclarations = messageTypes.map { it.toCommonModel() }, - enumDeclarations = enumTypes.map { it.toCommonModel() }, - serviceDeclarations = services.map { it.toCommonModel() }, + dependencies = dependencies.map { it.toModel() }, + messageDeclarations = messageTypes.map { it.toModel() }, + enumDeclarations = enumTypes.map { it.toModel() }, + serviceDeclarations = services.map { it.toModel() }, doc = null, dec = this, ) } -private fun Descriptors.Descriptor.toCommonModel(): MessageDeclaration = cached { - val regularFields = fields.filter { field -> field.realContainingOneof == null }.map { it.toCommonModel() } +private fun Descriptors.Descriptor.toModel(): MessageDeclaration = cached { + val regularFields = fields.filter { field -> field.realContainingOneof == null }.map { it.toModel() } return MessageDeclaration( name = fqName(), actualFields = regularFields, // get all oneof declarations that are not created from an optional in proto3 https://github.com/googleapis/api-linter/issues/1323 - oneOfDeclarations = oneofs.filter { it.fields[0].realContainingOneof != null }.map { it.toCommonModel() }, - enumDeclarations = enumTypes.map { it.toCommonModel() }, - nestedDeclarations = nestedTypes.map { it.toCommonModel() }, + oneOfDeclarations = oneofs.filter { it.fields[0].realContainingOneof != null }.map { it.toModel() }, + enumDeclarations = enumTypes.map { it.toModel() }, + nestedDeclarations = nestedTypes.map { it.toModel() }, doc = null, dec = this, ) } -private fun Descriptors.FieldDescriptor.toCommonModel(): FieldDeclaration = cached { +private fun Descriptors.FieldDescriptor.toModel(): FieldDeclaration = cached { toProto().hasProto3Optional() return FieldDeclaration( name = fqName().simpleName, @@ -88,15 +135,15 @@ private fun Descriptors.FieldDescriptor.toCommonModel(): FieldDeclaration = cach } -private fun Descriptors.OneofDescriptor.toCommonModel(): OneOfDeclaration = cached { +private fun Descriptors.OneofDescriptor.toModel(): OneOfDeclaration = cached { return OneOfDeclaration( name = fqName(), - variants = fields.map { it.toCommonModel() }, + variants = fields.map { it.toModel() }, dec = this, ) } -private fun Descriptors.EnumDescriptor.toCommonModel(): EnumDeclaration = cached { +private fun Descriptors.EnumDescriptor.toModel(): EnumDeclaration = cached { val entriesMap = mutableMapOf() val aliases = mutableListOf() @@ -105,7 +152,7 @@ private fun Descriptors.EnumDescriptor.toCommonModel(): EnumDeclaration = cached val original = entriesMap.getValue(value.number) aliases.add(value.toAliasModel(original)) } else { - entriesMap[value.number] = value.toCommonModel() + entriesMap[value.number] = value.toModel() } } @@ -122,7 +169,7 @@ private fun Descriptors.EnumDescriptor.toCommonModel(): EnumDeclaration = cached ) } -private fun Descriptors.EnumValueDescriptor.toCommonModel(): EnumDeclaration.Entry = cached { +private fun Descriptors.EnumValueDescriptor.toModel(): EnumDeclaration.Entry = cached { return EnumDeclaration.Entry( name = fqName(), doc = null, @@ -140,19 +187,19 @@ private fun Descriptors.EnumValueDescriptor.toAliasModel(original: EnumDeclarati ) } -private fun Descriptors.ServiceDescriptor.toCommonModel(): ServiceDeclaration = cached { +private fun Descriptors.ServiceDescriptor.toModel(): ServiceDeclaration = cached { return ServiceDeclaration( name = fqName(), - methods = methods.map { it.toCommonModel() }, + methods = methods.map { it.toModel() }, dec = this, ) } -private fun Descriptors.MethodDescriptor.toCommonModel(): MethodDeclaration = cached { +private fun Descriptors.MethodDescriptor.toModel(): MethodDeclaration = cached { return MethodDeclaration( name = name, - inputType = inputType.toCommonModel(), - outputType = outputType.toCommonModel(), + inputType = inputType.toModel(), + outputType = outputType.toModel(), dec = this, ) } @@ -176,41 +223,25 @@ private fun Descriptors.FieldDescriptor.modelType(): FieldType { Descriptors.FieldDescriptor.Type.SFIXED64 -> FieldType.IntegralType.SFIXED64 Descriptors.FieldDescriptor.Type.SINT32 -> FieldType.IntegralType.SINT32 Descriptors.FieldDescriptor.Type.SINT64 -> FieldType.IntegralType.SINT64 - Descriptors.FieldDescriptor.Type.ENUM -> FieldType.Reference(lazy { enumType!!.toCommonModel().name }) - Descriptors.FieldDescriptor.Type.MESSAGE -> FieldType.Reference(lazy { messageType!!.toCommonModel().name }) + Descriptors.FieldDescriptor.Type.ENUM -> FieldType.Reference(lazy { enumType!!.toModel().name }) + Descriptors.FieldDescriptor.Type.MESSAGE -> FieldType.Reference(lazy { messageType!!.toModel().name }) Descriptors.FieldDescriptor.Type.GROUP -> error("GROUP type is unsupported") } + if (isMapField) { + val keyType = messageType.findFieldByName("key").modelType() + val valType = messageType.findFieldByName("value").modelType() + val mapEntry = FieldType.Map.Entry(keyType, valType) + return FieldType.Map(lazy { mapEntry }) + } + if (isRepeated) { return FieldType.List(baseType) } - // TODO: Handle map type - return baseType } -//// GenericDescriptor Extensions //// - -private fun Descriptors.GenericDescriptor.fqName(): FqName { - val nameCapital = name.simpleProtoNameToKotlin(firstLetterUpper = true) - val nameLower = name.simpleProtoNameToKotlin() - return when (this) { - is Descriptors.FileDescriptor -> FqName.Package.fromString(`package`) - is Descriptors.Descriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) - is Descriptors.FieldDescriptor -> { - val usedName = if (realContainingOneof != null) nameCapital else nameLower - FqName.Declaration(usedName, containingType?.fqName() ?: file.fqName()) - } - - is Descriptors.EnumValueDescriptor -> FqName.Declaration(name, type.fqName()) - is Descriptors.OneofDescriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName()) - is Descriptors.ServiceDescriptor -> FqName.Declaration(nameCapital, file?.fqName() ?: file.fqName()) - is Descriptors.MethodDescriptor -> FqName.Declaration(nameLower, service?.fqName() ?: file.fqName()) - else -> error("Unknown generic descriptor: $this") - } -} - //// Utility Extensions //// private fun Descriptors.FileDescriptor.kotlinFileName(): String { From 018a86a3a8feb7451fd655b367f37255a6eabbc5 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Thu, 31 Jul 2025 19:04:05 +0200 Subject: [PATCH 06/11] gradle-plugin: Remove default protobuf JVM plugins from gradle-plugin Signed-off-by: Johannes Zottele --- .../kotlinx/rpc/grpc/DefaultGrpcExtension.kt | 33 ---------- .../kotlin/kotlinx/rpc/proto/ProtocPlugin.kt | 63 ------------------- .../rpc/proto/{derictory.kt => directory.kt} | 0 .../kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt | 9 +-- 4 files changed, 2 insertions(+), 103 deletions(-) rename gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/{derictory.kt => directory.kt} (100%) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt index 83db5de9b..ed822914b 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt @@ -4,9 +4,6 @@ package kotlinx.rpc.grpc -import kotlinx.rpc.GRPC_KOTLIN_VERSION -import kotlinx.rpc.GRPC_VERSION -import kotlinx.rpc.PROTOBUF_VERSION import kotlinx.rpc.buf.BufExtension import kotlinx.rpc.buf.configureBufExecutable import kotlinx.rpc.buf.tasks.registerBufExecTask @@ -14,10 +11,7 @@ import kotlinx.rpc.buf.tasks.registerBufGenerateTask import kotlinx.rpc.buf.tasks.registerGenerateBufGenYamlTask import kotlinx.rpc.buf.tasks.registerGenerateBufYamlTask import kotlinx.rpc.proto.* -import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_JAVA -import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN import kotlinx.rpc.proto.ProtocPlugin.Companion.KOTLIN_MULTIPLATFORM -import kotlinx.rpc.proto.ProtocPlugin.Companion.PROTOBUF_JAVA import kotlinx.rpc.util.ensureDirectoryExists import org.gradle.api.Action import org.gradle.api.GradleException @@ -61,9 +55,6 @@ internal open class DefaultGrpcExtension @Inject constructor( createDefaultProtocPlugins() project.protoSourceSets.forEach { protoSourceSet -> - protoSourceSet.protocPlugin(protocPlugins.protobufJava) - protoSourceSet.protocPlugin(protocPlugins.grpcJava) - protoSourceSet.protocPlugin(protocPlugins.grpcKotlin) protoSourceSet.protocPlugin(protocPlugins.kotlinMultiplatform) } @@ -294,30 +285,6 @@ internal open class DefaultGrpcExtension @Inject constructor( // ignore for bufGenerate task caching project.normalization.runtimeClasspath.ignore("**/protoc-gen-kotlin-multiplatform.log") project.normalization.runtimeClasspath.ignore("**/.keep") - - protocPlugins.create(GRPC_JAVA) { - isJava.set(true) - - remote { - locator.set("buf.build/grpc/java:v$GRPC_VERSION") - } - } - - protocPlugins.create(GRPC_KOTLIN) { - remote { - locator.set("buf.build/grpc/kotlin:v$GRPC_KOTLIN_VERSION") - } - } - - protocPlugins.create(PROTOBUF_JAVA) { - isJava.set(true) - - remote { - // for some reason they omit the first digit in this version: - // https://buf.build/protocolbuffers/java?version=v31.1 - locator.set("buf.build/protocolbuffers/java:v${PROTOBUF_VERSION.substringAfter(".")}") - } - } } private fun DefaultProtoSourceSet.correspondingMainSourceSetOrNull(): DefaultProtoSourceSet? { diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt index 8edd61ff8..496169fe7 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt @@ -4,10 +4,7 @@ package kotlinx.rpc.proto -import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_JAVA -import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN import kotlinx.rpc.proto.ProtocPlugin.Companion.KOTLIN_MULTIPLATFORM -import kotlinx.rpc.proto.ProtocPlugin.Companion.PROTOBUF_JAVA import kotlinx.rpc.util.OS import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer @@ -34,45 +31,6 @@ public fun NamedDomainObjectContainer.kotlinMultiplatform(action: kotlinMultiplatform.configure(action) } -/** - * Access to the `protobuf-java` protoc plugin. - */ -public val NamedDomainObjectContainer.protobufJava: NamedDomainObjectProvider - get() = named(PROTOBUF_JAVA) - -/** - * Configures the `protobuf-java` protoc plugin. - */ -public fun NamedDomainObjectContainer.protobufJava(action: Action) { - protobufJava.configure(action) -} - -/** - * Access to the `grpc-java` protoc plugin. - */ -public val NamedDomainObjectContainer.grpcJava: NamedDomainObjectProvider - get() = named(GRPC_JAVA) - -/** - * Configures the grpc-java protoc plugin. - */ -public fun NamedDomainObjectContainer.grpcJava(action: Action) { - grpcJava.configure(action) -} - -/** - * Access to the `grpc-kotlin` protoc plugin. - */ -public val NamedDomainObjectContainer.grpcKotlin: NamedDomainObjectProvider - get() = named(GRPC_KOTLIN) - -/** - * Configures the `grpc-kotlin` protoc plugin. - */ -public fun NamedDomainObjectContainer.grpcKotlin(action: Action) { - grpcKotlin.configure(action) -} - /** * Access to a specific protoc plugin. */ @@ -193,27 +151,6 @@ public open class ProtocPlugin( * @see [kotlinMultiplatform] */ public const val KOTLIN_MULTIPLATFORM: String = "kotlin-multiplatform" - - /** - * The name of the `protobuf-java` protoc plugin. - * - * @see [protobufJava] - */ - public const val PROTOBUF_JAVA: String = "java" - - /** - * The name of the `grpc-java` protoc plugin. - * - * @see [grpcJava] - */ - public const val GRPC_JAVA: String = "grpc-java" - - /** - * The name of the `grpc-kotlin` protoc plugin. - * - * @see [grpcKotlin] - */ - public const val GRPC_KOTLIN: String = "grpc-kotlin" } /** diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/derictory.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/directory.kt similarity index 100% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/derictory.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/directory.kt diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt index 5de8dd0ce..a4977b2d2 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt @@ -135,12 +135,6 @@ class GrpcJvmProjectTest : GrpcBaseTest() { version: v2 clean: true plugins: - - remote: buf.build/protocolbuffers/java:v${TEST_PROTOBUF_VERSION.substringAfter(".")} - out: java - - remote: buf.build/grpc/java:v$TEST_GRPC_VERSION - out: grpc-java - - remote: buf.build/grpc/kotlin:v$TEST_GRPC_KOTLIN_VERSION - out: grpc-kotlin - local: [protoc-gen-kotlin-multiplatform] out: kotlin-multiplatform opt: @@ -174,7 +168,8 @@ inputs: } @Suppress("detekt.MaxLineLength") - private val cliFlagsRegex = "- Buf Arguments: \\[.*?, generate, --output, .*?, --include-imports, --include-wkt, --error-format, json, --config, some\\.buf\\.yaml, --log-format, json, --timeout, 60s]".toRegex() + private val cliFlagsRegex = + "- Buf Arguments: \\[.*?, generate, --output, .*?, --include-imports, --include-wkt, --error-format, json, --config, some\\.buf\\.yaml, --log-format, json, --timeout, 60s]".toRegex() @Test fun `Custom Buf CLI Flags`() = runGrpcTest { From aad8eb0a34e67b515d8c3acd43cd19772459428a Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Thu, 31 Jul 2025 19:20:41 +0200 Subject: [PATCH 07/11] protoc-plugin: Remove test protos from jvmTest Signed-off-by: Johannes Zottele --- .../src/jvmTest/proto/all_primitives.proto | 21 ------------------- .../src/jvmTest/proto/repeated.proto | 13 ------------ 2 files changed, 34 deletions(-) delete mode 100644 grpc/grpc-core/src/jvmTest/proto/all_primitives.proto delete mode 100644 grpc/grpc-core/src/jvmTest/proto/repeated.proto diff --git a/grpc/grpc-core/src/jvmTest/proto/all_primitives.proto b/grpc/grpc-core/src/jvmTest/proto/all_primitives.proto deleted file mode 100644 index be698f9e6..000000000 --- a/grpc/grpc-core/src/jvmTest/proto/all_primitives.proto +++ /dev/null @@ -1,21 +0,0 @@ -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/jvmTest/proto/repeated.proto b/grpc/grpc-core/src/jvmTest/proto/repeated.proto deleted file mode 100644 index eff1a7382..000000000 --- a/grpc/grpc-core/src/jvmTest/proto/repeated.proto +++ /dev/null @@ -1,13 +0,0 @@ -syntax = "proto3"; - -package kotlinx.rpc.grpc.test; - -import 'reference_package.proto'; - -message Repeated { - repeated int32 listInt32 = 1; - repeated fixed32 listFixed32 = 2; - repeated string listString = 3; - repeated References listReference = 4; -} - From 0436cc304c19c5765bec7c23b42796f6dd5d3423 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Fri, 1 Aug 2025 13:01:15 +0200 Subject: [PATCH 08/11] grpc-pb: Address review comments Signed-off-by: Johannes Zottele --- grpc/grpc-core/build.gradle.kts | 9 --------- .../kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt | 3 ++- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/grpc/grpc-core/build.gradle.kts b/grpc/grpc-core/build.gradle.kts index dbd2e104b..e2df67fd9 100644 --- a/grpc/grpc-core/build.gradle.kts +++ b/grpc/grpc-core/build.gradle.kts @@ -152,15 +152,6 @@ 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/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt index 078f6af6f..3f1702701 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt @@ -21,7 +21,8 @@ fun CodeGeneratorRequest.toModel(): Model { val protoFileMap = protoFileList.associateBy { it.name } val fileDescriptors = mutableMapOf() - val files = protoFileList.map { protoFile -> protoFile.toDescriptor(protoFileMap, fileDescriptors) } + val files = fileToGenerateList.map { protoFileMap[it]!! } + .map { protoFile -> protoFile.toDescriptor(protoFileMap, fileDescriptors) } return Model( files = files.map { it.toModel() } From c0f81c28f68222cd657e9196aa7768bdfa634763 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Fri, 1 Aug 2025 14:32:47 +0200 Subject: [PATCH 09/11] grpc-pb: Revert removal of grpc service tests for JVM Signed-off-by: Johannes Zottele --- .../rpc/grpc/core/test/GrpcServerTest.kt | 37 +++ .../rpc/grpc/core/test/StreamingTest.kt | 80 +++++ .../grpc/core/test/TestPrimitiveService.kt | 34 +++ .../grpc/core/test/TestReferenceService.kt | 282 ++++++++++++++++++ 4 files changed, 433 insertions(+) create mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt create mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt create mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt create mode 100644 grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt new file mode 100644 index 000000000..7204d639e --- /dev/null +++ b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt @@ -0,0 +1,37 @@ +/* + * 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.core.test + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.test.runTest +import kotlinx.rpc.RpcServer +import kotlinx.rpc.grpc.GrpcClient +import kotlinx.rpc.grpc.GrpcServer + +abstract class GrpcServerTest { + private val serverMutex = Mutex() + + abstract fun RpcServer.registerServices() + + protected fun runGrpcTest(test: suspend (GrpcClient) -> Unit, ) = runTest { + serverMutex.withLock { + val grpcClient = GrpcClient("localhost", 8080) { + usePlaintext() + } + + val grpcServer = GrpcServer(8080, builder = { + registerServices() + }) + + grpcServer.start() + test(grpcClient) + grpcServer.shutdown() + grpcServer.awaitTermination() + grpcClient.shutdown() + grpcClient.awaitTermination() + } + } +} diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt new file mode 100644 index 000000000..658416fba --- /dev/null +++ b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt @@ -0,0 +1,80 @@ +/* + * 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.core.test + +import StreamingTestService +import kotlinx.coroutines.flow.* +import kotlinx.rpc.RpcServer +import kotlinx.rpc.withService +import kotlin.test.Test +import kotlin.test.assertEquals + +class StreamingTestServiceImpl : StreamingTestService { + override fun Server(message: kotlinx.rpc.grpc.test.References): Flow { + return flow { emit(message); emit(message); emit(message) } + } + + override suspend fun Client(message: Flow): kotlinx.rpc.grpc.test.References { + return message.last() + } + + override fun Bidi(message: Flow): Flow { + return message + } +} + +class StreamingTest : GrpcServerTest() { + override fun RpcServer.registerServices() { + registerService { StreamingTestServiceImpl() } + } + + @Test + fun testServerStreaming() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + service.Server(kotlinx.rpc.grpc.test.References { + other = kotlinx.rpc.grpc.test.Other { + field = 42 + } + }).toList().run { + assertEquals(3, size) + + forEach { + assertEquals(42, it.other.field) + } + } + } + + @Test + fun testClientStreaming() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val result = service.Client(flow { + repeat(3) { + emit(kotlinx.rpc.grpc.test.References { + other = kotlinx.rpc.grpc.test.Other { + field = 42 + it + } + }) + } + }) + + assertEquals(44, result.other.field) + } + + @Test + fun testBidiStreaming() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + service.Bidi(flow { + repeat(3) { + emit(kotlinx.rpc.grpc.test.References { + other = kotlinx.rpc.grpc.test.Other { + field = 42 + it + } + }) + } + }).collectIndexed { i, it -> + assertEquals(42 + i, it.other.field) + } + } +} diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt new file mode 100644 index 000000000..7910a956f --- /dev/null +++ b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt @@ -0,0 +1,34 @@ +/* + * 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.core.test + +import kotlinx.rpc.RpcServer +import kotlinx.rpc.grpc.test.AllPrimitives +import kotlinx.rpc.grpc.test.PrimitiveService +import kotlinx.rpc.withService +import kotlin.test.Test +import kotlin.test.assertEquals + +class PrimitiveServiceImpl : PrimitiveService { + override suspend fun Echo(message: AllPrimitives): AllPrimitives { + return message + } +} + +class TestPrimitiveService : GrpcServerTest() { + override fun RpcServer.registerServices() { + registerService { PrimitiveServiceImpl() } + } + + @Test + fun testPrimitive(): Unit = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val result = service.Echo(AllPrimitives { + int32 = 42 + }) + + assertEquals(42, result.int32) + } +} diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt new file mode 100644 index 000000000..926da1a51 --- /dev/null +++ b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt @@ -0,0 +1,282 @@ +/* + * 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.core.test + +import Other +import ReferenceTestService +import References +import kotlinx.rpc.RpcServer +import kotlinx.rpc.grpc.test.AllPrimitives +import kotlinx.rpc.grpc.test.Nested +import kotlinx.rpc.grpc.test.OneOf +import kotlinx.rpc.grpc.test.OptionalTypes +import kotlinx.rpc.grpc.test.Repeated +import kotlinx.rpc.grpc.test.TestMap +import kotlinx.rpc.grpc.test.UsingEnum +import kotlinx.rpc.withService +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class ReferenceTestServiceImpl : ReferenceTestService { + override suspend fun Get(message: References): kotlinx.rpc.grpc.test.References { + return kotlinx.rpc.grpc.test.References { + other = kotlinx.rpc.grpc.test.Other { + field = message.other.arg.toInt() + } + + primitive = message.other.arg + } + } + + override suspend fun Enum(message: UsingEnum): UsingEnum { + return message + } + + override suspend fun Optional(message: OptionalTypes): OptionalTypes { + return message + } + + override suspend fun Repeated(message: Repeated): Repeated { + return message + } + + override suspend fun Nested(message: Nested): Nested { + return message + } + + override suspend fun Map(message: TestMap): TestMap { + return message + } + + override suspend fun OneOf(message: OneOf): OneOf { + return message + } +} + +class TestReferenceService : GrpcServerTest() { + override fun RpcServer.registerServices() { + registerService { ReferenceTestServiceImpl() } + } + + @Test + fun testReferenceService() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val arg = References { + other = Other { + arg = "42" + } + } + + val result = service.Get(arg) + + assertEquals("42", result.primitive) + assertEquals(42, result.other.field) + } + + @Test + fun testEnum() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val result = service.Enum(UsingEnum { + enum = kotlinx.rpc.grpc.test.Enum.ONE + }) + + assertEquals(kotlinx.rpc.grpc.test.Enum.ONE, result.enum) + } + + @Test + fun testOptional() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val resultNotNull = service.Optional(OptionalTypes { + name = "test" + age = 42 + reference = kotlinx.rpc.grpc.test.Other { + field = 42 + } + }) + + assertEquals("test", resultNotNull.name) + assertEquals(42, resultNotNull.age) + assertEquals(42, resultNotNull.reference?.field) + + val resultNullable = service.Optional(OptionalTypes { + name = null + age = null + reference = null + }) + + assertEquals(null, resultNullable.name) + assertEquals(null, resultNullable.age) + assertEquals(null, resultNullable.reference) + } + + @Test + fun testRepeated() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val result = service.Repeated(Repeated { + listFixed32 = listOf(0u, 1u, 2u) + listInt32 = listOf(0, 1, 2) + listString = listOf("test", "hello") + listReference = listOf(kotlinx.rpc.grpc.test.References { + other = kotlinx.rpc.grpc.test.Other { + field = 42 + } + }) + }) + + assertEquals(listOf("test", "hello"), result.listString) + assertEquals(listOf(0u, 1u, 2u), result.listFixed32) + assertEquals(listOf(0, 1, 2), result.listInt32) + assertEquals(1, result.listReference.size) + assertEquals(42, result.listReference[0].other.field) + + val resultEmpty = service.Repeated(Repeated {}) + + assertEquals(emptyList(), resultEmpty.listString) + assertEquals(emptyList(), resultEmpty.listFixed32) + assertEquals(emptyList(), resultEmpty.listInt32) + assertEquals(emptyList(), resultEmpty.listReference) + } + + @Test + fun testNested() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val result = service.Nested(Nested { + inner1 = Nested.Inner1 { + inner11 = Nested.Inner1.Inner11 { + reference21 = null + reference12 = Nested.Inner1.Inner12 { + recursion = null + } + enum = Nested.Inner2.NestedEnum.ZERO + } + + inner22 = Nested.Inner1.Inner12 { + recursion = Nested.Inner1.Inner12 { + recursion = null + } + } + + string = "42_1" + + inner1 = null + } + + inner2 = Nested.Inner2 { + inner21 = Nested.Inner2.Inner21 { + reference11 = Nested.Inner1.Inner11 { + reference21 = null + reference12 = Nested.Inner1.Inner12 { + recursion = null + } + enum = Nested.Inner2.NestedEnum.ZERO + } + + reference22 = Nested.Inner2.Inner22 { + enum = Nested.Inner2.NestedEnum.ZERO + } + } + + inner22 = Nested.Inner2.Inner22 { + enum = Nested.Inner2.NestedEnum.ZERO + } + string = "42_2" + } + + string = "42" + enum = Nested.Inner2.NestedEnum.ZERO + }) + + // Assert Inner1.Inner11 + assertEquals(null, result.inner1.inner11.reference21) + assertEquals(null, result.inner1.inner11.reference12.recursion) + assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner1.inner11.enum) + + // Assert Inner1.Inner12 + assertNotNull(result.inner1.inner22.recursion) + assertEquals(null, result.inner1.inner22.recursion?.recursion) + + // Assert Inner1 + assertEquals("42_1", result.inner1.string) + assertEquals(null, result.inner1.inner1) + + // Assert Inner2.Inner21 + assertEquals(null, result.inner2.inner21.reference11.reference21) + assertEquals(null, result.inner2.inner21.reference11.reference12.recursion) + assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference11.enum) + assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference22.enum) + + // Assert Inner2.Inner22 + assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner22.enum) + + // Assert Inner2 + assertEquals("42_2", result.inner2.string) + + // Assert root Nested + assertEquals("42", result.string) + assertEquals(Nested.Inner2.NestedEnum.ZERO, result.enum) + } + + @Test + fun testMap() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val result = service.Map(TestMap { + primitives = mapOf("1" to 2, "2" to 1) + references = mapOf("ref" to kotlinx.rpc.grpc.test.References { + other = kotlinx.rpc.grpc.test.Other { + field = 42 + } + }) + }) + + assertEquals(mapOf("1" to 2L, "2" to 1L), result.primitives) + assertEquals(mapOf("ref" to 42), result.references.mapValues { it.value.other.field }) + } + + @Test + fun testOneOf() = runGrpcTest { grpcClient -> + val service = grpcClient.withService() + val result1 = service.OneOf(OneOf { + primitives = OneOf.Primitives.StringValue("42") + references = OneOf.References.Other(kotlinx.rpc.grpc.test.Other { + field = 42 + }) + mixed = OneOf.Mixed.Int64(42L) + single = OneOf.Single.Bytes(byteArrayOf(42)) + }) + + assertEquals("42", (result1.primitives as OneOf.Primitives.StringValue).value) + assertEquals(42, (result1.references as OneOf.References.Other).value.field) + assertEquals(42L, (result1.mixed as OneOf.Mixed.Int64).value) + assertContentEquals(byteArrayOf(42), (result1.single as OneOf.Single.Bytes).value) + + val result2 = service.OneOf(OneOf { + primitives = OneOf.Primitives.Bool(true) + references = OneOf.References.InnerReferences(kotlinx.rpc.grpc.test.References { + other = kotlinx.rpc.grpc.test.Other { + field = 42 + } + }) + mixed = OneOf.Mixed.AllPrimitives(AllPrimitives { + string = "42" + }) + }) + + assertEquals(true, (result2.primitives as OneOf.Primitives.Bool).value) + assertEquals(42, (result2.references as OneOf.References.InnerReferences).value.other.field) + assertEquals("42", (result2.mixed as OneOf.Mixed.AllPrimitives).value.string) + assertEquals(null, result2.single) + + val result3 = service.OneOf(OneOf { + primitives = OneOf.Primitives.Int32(42) + }) + + assertEquals(42, (result3.primitives as OneOf.Primitives.Int32).value) + assertEquals(null, result3.references) + assertEquals(null, result3.mixed) + assertEquals(null, result3.single) + } +} From 15d6b518263eadef9b4cdff6d15ee7e63734ce21 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Fri, 1 Aug 2025 14:34:20 +0200 Subject: [PATCH 10/11] grpc-pb: Move streaming grpc core tests to commonTest Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt | 0 .../kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt | 0 .../kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt | 0 .../kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename grpc/grpc-core/src/{jvmTest => commonTest}/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt (100%) rename grpc/grpc-core/src/{jvmTest => commonTest}/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt (100%) diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt similarity index 100% rename from grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt similarity index 100% rename from grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt similarity index 100% rename from grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt similarity index 100% rename from grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt From 716c6b66e573b3b844cc63b6d1d9f1c6944ec8bf Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Fri, 1 Aug 2025 14:36:39 +0200 Subject: [PATCH 11/11] grpc-pb: Comment grpc core test The GRPC services are not getting generated on this branch, so I had to comment them out. Signed-off-by: Johannes Zottele --- .../rpc/grpc/core/test/GrpcServerTest.kt | 62 +- .../rpc/grpc/core/test/StreamingTest.kt | 148 ++--- .../grpc/core/test/TestPrimitiveService.kt | 54 +- .../grpc/core/test/TestReferenceService.kt | 552 +++++++++--------- 4 files changed, 408 insertions(+), 408 deletions(-) diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt index 7204d639e..d233b29d8 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt @@ -4,34 +4,34 @@ package kotlinx.rpc.grpc.core.test -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.test.runTest -import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.GrpcClient -import kotlinx.rpc.grpc.GrpcServer - -abstract class GrpcServerTest { - private val serverMutex = Mutex() - - abstract fun RpcServer.registerServices() - - protected fun runGrpcTest(test: suspend (GrpcClient) -> Unit, ) = runTest { - serverMutex.withLock { - val grpcClient = GrpcClient("localhost", 8080) { - usePlaintext() - } - - val grpcServer = GrpcServer(8080, builder = { - registerServices() - }) - - grpcServer.start() - test(grpcClient) - grpcServer.shutdown() - grpcServer.awaitTermination() - grpcClient.shutdown() - grpcClient.awaitTermination() - } - } -} +//import kotlinx.coroutines.sync.Mutex +//import kotlinx.coroutines.sync.withLock +//import kotlinx.coroutines.test.runTest +//import kotlinx.rpc.RpcServer +//import kotlinx.rpc.grpc.GrpcClient +//import kotlinx.rpc.grpc.GrpcServer +// +//abstract class GrpcServerTest { +// private val serverMutex = Mutex() +// +// abstract fun RpcServer.registerServices() +// +// protected fun runGrpcTest(test: suspend (GrpcClient) -> Unit, ) = runTest { +// serverMutex.withLock { +// val grpcClient = GrpcClient("localhost", 8080) { +// usePlaintext() +// } +// +// val grpcServer = GrpcServer(8080, builder = { +// registerServices() +// }) +// +// grpcServer.start() +// test(grpcClient) +// grpcServer.shutdown() +// grpcServer.awaitTermination() +// grpcClient.shutdown() +// grpcClient.awaitTermination() +// } +// } +//} diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt index 658416fba..9649ea829 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt @@ -4,77 +4,77 @@ package kotlinx.rpc.grpc.core.test -import StreamingTestService -import kotlinx.coroutines.flow.* -import kotlinx.rpc.RpcServer -import kotlinx.rpc.withService -import kotlin.test.Test -import kotlin.test.assertEquals - -class StreamingTestServiceImpl : StreamingTestService { - override fun Server(message: kotlinx.rpc.grpc.test.References): Flow { - return flow { emit(message); emit(message); emit(message) } - } - - override suspend fun Client(message: Flow): kotlinx.rpc.grpc.test.References { - return message.last() - } - - override fun Bidi(message: Flow): Flow { - return message - } -} - -class StreamingTest : GrpcServerTest() { - override fun RpcServer.registerServices() { - registerService { StreamingTestServiceImpl() } - } - - @Test - fun testServerStreaming() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - service.Server(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }).toList().run { - assertEquals(3, size) - - forEach { - assertEquals(42, it.other.field) - } - } - } - - @Test - fun testClientStreaming() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Client(flow { - repeat(3) { - emit(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 + it - } - }) - } - }) - - assertEquals(44, result.other.field) - } - - @Test - fun testBidiStreaming() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - service.Bidi(flow { - repeat(3) { - emit(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 + it - } - }) - } - }).collectIndexed { i, it -> - assertEquals(42 + i, it.other.field) - } - } -} +//import StreamingTestService +//import kotlinx.coroutines.flow.* +//import kotlinx.rpc.RpcServer +//import kotlinx.rpc.withService +//import kotlin.test.Test +//import kotlin.test.assertEquals +// +//class StreamingTestServiceImpl : StreamingTestService { +// override fun Server(message: kotlinx.rpc.grpc.test.References): Flow { +// return flow { emit(message); emit(message); emit(message) } +// } +// +// override suspend fun Client(message: Flow): kotlinx.rpc.grpc.test.References { +// return message.last() +// } +// +// override fun Bidi(message: Flow): Flow { +// return message +// } +//} +// +//class StreamingTest : GrpcServerTest() { +// override fun RpcServer.registerServices() { +// registerService { StreamingTestServiceImpl() } +// } +// +// @Test +// fun testServerStreaming() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// service.Server(kotlinx.rpc.grpc.test.References { +// other = kotlinx.rpc.grpc.test.Other { +// field = 42 +// } +// }).toList().run { +// assertEquals(3, size) +// +// forEach { +// assertEquals(42, it.other.field) +// } +// } +// } +// +// @Test +// fun testClientStreaming() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val result = service.Client(flow { +// repeat(3) { +// emit(kotlinx.rpc.grpc.test.References { +// other = kotlinx.rpc.grpc.test.Other { +// field = 42 + it +// } +// }) +// } +// }) +// +// assertEquals(44, result.other.field) +// } +// +// @Test +// fun testBidiStreaming() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// service.Bidi(flow { +// repeat(3) { +// emit(kotlinx.rpc.grpc.test.References { +// other = kotlinx.rpc.grpc.test.Other { +// field = 42 + it +// } +// }) +// } +// }).collectIndexed { i, it -> +// assertEquals(42 + i, it.other.field) +// } +// } +//} diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt index 7910a956f..ef4875a01 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt @@ -4,31 +4,31 @@ package kotlinx.rpc.grpc.core.test -import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.test.AllPrimitives -import kotlinx.rpc.grpc.test.PrimitiveService -import kotlinx.rpc.withService -import kotlin.test.Test -import kotlin.test.assertEquals +//import kotlinx.rpc.RpcServer +//import kotlinx.rpc.grpc.test.AllPrimitives +//import kotlinx.rpc.grpc.test.PrimitiveService +//import kotlinx.rpc.withService +//import kotlin.test.Test +//import kotlin.test.assertEquals -class PrimitiveServiceImpl : PrimitiveService { - override suspend fun Echo(message: AllPrimitives): AllPrimitives { - return message - } -} - -class TestPrimitiveService : GrpcServerTest() { - override fun RpcServer.registerServices() { - registerService { PrimitiveServiceImpl() } - } - - @Test - fun testPrimitive(): Unit = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Echo(AllPrimitives { - int32 = 42 - }) - - assertEquals(42, result.int32) - } -} +//class PrimitiveServiceImpl : PrimitiveService { +// override suspend fun Echo(message: AllPrimitives): AllPrimitives { +// return message +// } +//} +// +//class TestPrimitiveService : GrpcServerTest() { +// override fun RpcServer.registerServices() { +// registerService { PrimitiveServiceImpl() } +// } +// +// @Test +// fun testPrimitive(): Unit = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val result = service.Echo(AllPrimitives { +// int32 = 42 +// }) +// +// assertEquals(42, result.int32) +// } +//} diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt index 926da1a51..983d62507 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt @@ -4,279 +4,279 @@ package kotlinx.rpc.grpc.core.test -import Other -import ReferenceTestService -import References -import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.test.AllPrimitives -import kotlinx.rpc.grpc.test.Nested -import kotlinx.rpc.grpc.test.OneOf -import kotlinx.rpc.grpc.test.OptionalTypes -import kotlinx.rpc.grpc.test.Repeated -import kotlinx.rpc.grpc.test.TestMap -import kotlinx.rpc.grpc.test.UsingEnum -import kotlinx.rpc.withService -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class ReferenceTestServiceImpl : ReferenceTestService { - override suspend fun Get(message: References): kotlinx.rpc.grpc.test.References { - return kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = message.other.arg.toInt() - } - - primitive = message.other.arg - } - } - - override suspend fun Enum(message: UsingEnum): UsingEnum { - return message - } - - override suspend fun Optional(message: OptionalTypes): OptionalTypes { - return message - } - - override suspend fun Repeated(message: Repeated): Repeated { - return message - } - - override suspend fun Nested(message: Nested): Nested { - return message - } - - override suspend fun Map(message: TestMap): TestMap { - return message - } - - override suspend fun OneOf(message: OneOf): OneOf { - return message - } -} - -class TestReferenceService : GrpcServerTest() { - override fun RpcServer.registerServices() { - registerService { ReferenceTestServiceImpl() } - } - - @Test - fun testReferenceService() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val arg = References { - other = Other { - arg = "42" - } - } - - val result = service.Get(arg) - - assertEquals("42", result.primitive) - assertEquals(42, result.other.field) - } - - @Test - fun testEnum() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Enum(UsingEnum { - enum = kotlinx.rpc.grpc.test.Enum.ONE - }) - - assertEquals(kotlinx.rpc.grpc.test.Enum.ONE, result.enum) - } - - @Test - fun testOptional() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val resultNotNull = service.Optional(OptionalTypes { - name = "test" - age = 42 - reference = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }) - - assertEquals("test", resultNotNull.name) - assertEquals(42, resultNotNull.age) - assertEquals(42, resultNotNull.reference?.field) - - val resultNullable = service.Optional(OptionalTypes { - name = null - age = null - reference = null - }) - - assertEquals(null, resultNullable.name) - assertEquals(null, resultNullable.age) - assertEquals(null, resultNullable.reference) - } - - @Test - fun testRepeated() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Repeated(Repeated { - listFixed32 = listOf(0u, 1u, 2u) - listInt32 = listOf(0, 1, 2) - listString = listOf("test", "hello") - listReference = listOf(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }) - }) - - assertEquals(listOf("test", "hello"), result.listString) - assertEquals(listOf(0u, 1u, 2u), result.listFixed32) - assertEquals(listOf(0, 1, 2), result.listInt32) - assertEquals(1, result.listReference.size) - assertEquals(42, result.listReference[0].other.field) - - val resultEmpty = service.Repeated(Repeated {}) - - assertEquals(emptyList(), resultEmpty.listString) - assertEquals(emptyList(), resultEmpty.listFixed32) - assertEquals(emptyList(), resultEmpty.listInt32) - assertEquals(emptyList(), resultEmpty.listReference) - } - - @Test - fun testNested() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Nested(Nested { - inner1 = Nested.Inner1 { - inner11 = Nested.Inner1.Inner11 { - reference21 = null - reference12 = Nested.Inner1.Inner12 { - recursion = null - } - enum = Nested.Inner2.NestedEnum.ZERO - } - - inner22 = Nested.Inner1.Inner12 { - recursion = Nested.Inner1.Inner12 { - recursion = null - } - } - - string = "42_1" - - inner1 = null - } - - inner2 = Nested.Inner2 { - inner21 = Nested.Inner2.Inner21 { - reference11 = Nested.Inner1.Inner11 { - reference21 = null - reference12 = Nested.Inner1.Inner12 { - recursion = null - } - enum = Nested.Inner2.NestedEnum.ZERO - } - - reference22 = Nested.Inner2.Inner22 { - enum = Nested.Inner2.NestedEnum.ZERO - } - } - - inner22 = Nested.Inner2.Inner22 { - enum = Nested.Inner2.NestedEnum.ZERO - } - string = "42_2" - } - - string = "42" - enum = Nested.Inner2.NestedEnum.ZERO - }) - - // Assert Inner1.Inner11 - assertEquals(null, result.inner1.inner11.reference21) - assertEquals(null, result.inner1.inner11.reference12.recursion) - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner1.inner11.enum) - - // Assert Inner1.Inner12 - assertNotNull(result.inner1.inner22.recursion) - assertEquals(null, result.inner1.inner22.recursion?.recursion) - - // Assert Inner1 - assertEquals("42_1", result.inner1.string) - assertEquals(null, result.inner1.inner1) - - // Assert Inner2.Inner21 - assertEquals(null, result.inner2.inner21.reference11.reference21) - assertEquals(null, result.inner2.inner21.reference11.reference12.recursion) - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference11.enum) - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference22.enum) - - // Assert Inner2.Inner22 - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner22.enum) - - // Assert Inner2 - assertEquals("42_2", result.inner2.string) - - // Assert root Nested - assertEquals("42", result.string) - assertEquals(Nested.Inner2.NestedEnum.ZERO, result.enum) - } - - @Test - fun testMap() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result = service.Map(TestMap { - primitives = mapOf("1" to 2, "2" to 1) - references = mapOf("ref" to kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }) - }) - - assertEquals(mapOf("1" to 2L, "2" to 1L), result.primitives) - assertEquals(mapOf("ref" to 42), result.references.mapValues { it.value.other.field }) - } - - @Test - fun testOneOf() = runGrpcTest { grpcClient -> - val service = grpcClient.withService() - val result1 = service.OneOf(OneOf { - primitives = OneOf.Primitives.StringValue("42") - references = OneOf.References.Other(kotlinx.rpc.grpc.test.Other { - field = 42 - }) - mixed = OneOf.Mixed.Int64(42L) - single = OneOf.Single.Bytes(byteArrayOf(42)) - }) - - assertEquals("42", (result1.primitives as OneOf.Primitives.StringValue).value) - assertEquals(42, (result1.references as OneOf.References.Other).value.field) - assertEquals(42L, (result1.mixed as OneOf.Mixed.Int64).value) - assertContentEquals(byteArrayOf(42), (result1.single as OneOf.Single.Bytes).value) - - val result2 = service.OneOf(OneOf { - primitives = OneOf.Primitives.Bool(true) - references = OneOf.References.InnerReferences(kotlinx.rpc.grpc.test.References { - other = kotlinx.rpc.grpc.test.Other { - field = 42 - } - }) - mixed = OneOf.Mixed.AllPrimitives(AllPrimitives { - string = "42" - }) - }) - - assertEquals(true, (result2.primitives as OneOf.Primitives.Bool).value) - assertEquals(42, (result2.references as OneOf.References.InnerReferences).value.other.field) - assertEquals("42", (result2.mixed as OneOf.Mixed.AllPrimitives).value.string) - assertEquals(null, result2.single) - - val result3 = service.OneOf(OneOf { - primitives = OneOf.Primitives.Int32(42) - }) - - assertEquals(42, (result3.primitives as OneOf.Primitives.Int32).value) - assertEquals(null, result3.references) - assertEquals(null, result3.mixed) - assertEquals(null, result3.single) - } -} +//import Other +//import ReferenceTestService +//import References +//import kotlinx.rpc.RpcServer +//import kotlinx.rpc.grpc.test.AllPrimitives +//import kotlinx.rpc.grpc.test.Nested +//import kotlinx.rpc.grpc.test.OneOf +//import kotlinx.rpc.grpc.test.OptionalTypes +//import kotlinx.rpc.grpc.test.Repeated +//import kotlinx.rpc.grpc.test.TestMap +//import kotlinx.rpc.grpc.test.UsingEnum +//import kotlinx.rpc.withService +//import kotlin.test.Test +//import kotlin.test.assertContentEquals +//import kotlin.test.assertEquals +//import kotlin.test.assertNotNull + +//class ReferenceTestServiceImpl : ReferenceTestService { +// override suspend fun Get(message: References): kotlinx.rpc.grpc.test.References { +// return kotlinx.rpc.grpc.test.References { +// other = kotlinx.rpc.grpc.test.Other { +// field = message.other.arg.toInt() +// } +// +// primitive = message.other.arg +// } +// } +// +// override suspend fun Enum(message: UsingEnum): UsingEnum { +// return message +// } +// +// override suspend fun Optional(message: OptionalTypes): OptionalTypes { +// return message +// } +// +// override suspend fun Repeated(message: Repeated): Repeated { +// return message +// } +// +// override suspend fun Nested(message: Nested): Nested { +// return message +// } +// +// override suspend fun Map(message: TestMap): TestMap { +// return message +// } +// +// override suspend fun OneOf(message: OneOf): OneOf { +// return message +// } +//} +// +//class TestReferenceService : GrpcServerTest() { +// override fun RpcServer.registerServices() { +// registerService { ReferenceTestServiceImpl() } +// } +// +// @Test +// fun testReferenceService() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val arg = References { +// other = Other { +// arg = "42" +// } +// } +// +// val result = service.Get(arg) +// +// assertEquals("42", result.primitive) +// assertEquals(42, result.other.field) +// } +// +// @Test +// fun testEnum() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val result = service.Enum(UsingEnum { +// enum = kotlinx.rpc.grpc.test.Enum.ONE +// }) +// +// assertEquals(kotlinx.rpc.grpc.test.Enum.ONE, result.enum) +// } +// +// @Test +// fun testOptional() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val resultNotNull = service.Optional(OptionalTypes { +// name = "test" +// age = 42 +// reference = kotlinx.rpc.grpc.test.Other { +// field = 42 +// } +// }) +// +// assertEquals("test", resultNotNull.name) +// assertEquals(42, resultNotNull.age) +// assertEquals(42, resultNotNull.reference?.field) +// +// val resultNullable = service.Optional(OptionalTypes { +// name = null +// age = null +// reference = null +// }) +// +// assertEquals(null, resultNullable.name) +// assertEquals(null, resultNullable.age) +// assertEquals(null, resultNullable.reference) +// } +// +// @Test +// fun testRepeated() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val result = service.Repeated(Repeated { +// listFixed32 = listOf(0u, 1u, 2u) +// listInt32 = listOf(0, 1, 2) +// listString = listOf("test", "hello") +// listReference = listOf(kotlinx.rpc.grpc.test.References { +// other = kotlinx.rpc.grpc.test.Other { +// field = 42 +// } +// }) +// }) +// +// assertEquals(listOf("test", "hello"), result.listString) +// assertEquals(listOf(0u, 1u, 2u), result.listFixed32) +// assertEquals(listOf(0, 1, 2), result.listInt32) +// assertEquals(1, result.listReference.size) +// assertEquals(42, result.listReference[0].other.field) +// +// val resultEmpty = service.Repeated(Repeated {}) +// +// assertEquals(emptyList(), resultEmpty.listString) +// assertEquals(emptyList(), resultEmpty.listFixed32) +// assertEquals(emptyList(), resultEmpty.listInt32) +// assertEquals(emptyList(), resultEmpty.listReference) +// } +// +// @Test +// fun testNested() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val result = service.Nested(Nested { +// inner1 = Nested.Inner1 { +// inner11 = Nested.Inner1.Inner11 { +// reference21 = null +// reference12 = Nested.Inner1.Inner12 { +// recursion = null +// } +// enum = Nested.Inner2.NestedEnum.ZERO +// } +// +// inner22 = Nested.Inner1.Inner12 { +// recursion = Nested.Inner1.Inner12 { +// recursion = null +// } +// } +// +// string = "42_1" +// +// inner1 = null +// } +// +// inner2 = Nested.Inner2 { +// inner21 = Nested.Inner2.Inner21 { +// reference11 = Nested.Inner1.Inner11 { +// reference21 = null +// reference12 = Nested.Inner1.Inner12 { +// recursion = null +// } +// enum = Nested.Inner2.NestedEnum.ZERO +// } +// +// reference22 = Nested.Inner2.Inner22 { +// enum = Nested.Inner2.NestedEnum.ZERO +// } +// } +// +// inner22 = Nested.Inner2.Inner22 { +// enum = Nested.Inner2.NestedEnum.ZERO +// } +// string = "42_2" +// } +// +// string = "42" +// enum = Nested.Inner2.NestedEnum.ZERO +// }) +// +// // Assert Inner1.Inner11 +// assertEquals(null, result.inner1.inner11.reference21) +// assertEquals(null, result.inner1.inner11.reference12.recursion) +// assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner1.inner11.enum) +// +// // Assert Inner1.Inner12 +// assertNotNull(result.inner1.inner22.recursion) +// assertEquals(null, result.inner1.inner22.recursion?.recursion) +// +// // Assert Inner1 +// assertEquals("42_1", result.inner1.string) +// assertEquals(null, result.inner1.inner1) +// +// // Assert Inner2.Inner21 +// assertEquals(null, result.inner2.inner21.reference11.reference21) +// assertEquals(null, result.inner2.inner21.reference11.reference12.recursion) +// assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference11.enum) +// assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference22.enum) +// +// // Assert Inner2.Inner22 +// assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner22.enum) +// +// // Assert Inner2 +// assertEquals("42_2", result.inner2.string) +// +// // Assert root Nested +// assertEquals("42", result.string) +// assertEquals(Nested.Inner2.NestedEnum.ZERO, result.enum) +// } +// +// @Test +// fun testMap() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val result = service.Map(TestMap { +// primitives = mapOf("1" to 2, "2" to 1) +// references = mapOf("ref" to kotlinx.rpc.grpc.test.References { +// other = kotlinx.rpc.grpc.test.Other { +// field = 42 +// } +// }) +// }) +// +// assertEquals(mapOf("1" to 2L, "2" to 1L), result.primitives) +// assertEquals(mapOf("ref" to 42), result.references.mapValues { it.value.other.field }) +// } +// +// @Test +// fun testOneOf() = runGrpcTest { grpcClient -> +// val service = grpcClient.withService() +// val result1 = service.OneOf(OneOf { +// primitives = OneOf.Primitives.StringValue("42") +// references = OneOf.References.Other(kotlinx.rpc.grpc.test.Other { +// field = 42 +// }) +// mixed = OneOf.Mixed.Int64(42L) +// single = OneOf.Single.Bytes(byteArrayOf(42)) +// }) +// +// assertEquals("42", (result1.primitives as OneOf.Primitives.StringValue).value) +// assertEquals(42, (result1.references as OneOf.References.Other).value.field) +// assertEquals(42L, (result1.mixed as OneOf.Mixed.Int64).value) +// assertContentEquals(byteArrayOf(42), (result1.single as OneOf.Single.Bytes).value) +// +// val result2 = service.OneOf(OneOf { +// primitives = OneOf.Primitives.Bool(true) +// references = OneOf.References.InnerReferences(kotlinx.rpc.grpc.test.References { +// other = kotlinx.rpc.grpc.test.Other { +// field = 42 +// } +// }) +// mixed = OneOf.Mixed.AllPrimitives(AllPrimitives { +// string = "42" +// }) +// }) +// +// assertEquals(true, (result2.primitives as OneOf.Primitives.Bool).value) +// assertEquals(42, (result2.references as OneOf.References.InnerReferences).value.other.field) +// assertEquals("42", (result2.mixed as OneOf.Mixed.AllPrimitives).value.string) +// assertEquals(null, result2.single) +// +// val result3 = service.OneOf(OneOf { +// primitives = OneOf.Primitives.Int32(42) +// }) +// +// assertEquals(42, (result3.primitives as OneOf.Primitives.Int32).value) +// assertEquals(null, result3.references) +// assertEquals(null, result3.mixed) +// assertEquals(null, result3.single) +// } +//}