Skip to content

Commit e8b50b6

Browse files
committed
Enum support (#294)
1 parent 755a8c7 commit e8b50b6

File tree

8 files changed

+144
-51
lines changed

8 files changed

+144
-51
lines changed

protobuf-plugin/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ sourceSets {
3333
test {
3434
proto {
3535
exclude(
36+
"**/enum_options.proto",
3637
"**/empty_deprecated.proto",
37-
"**/enum.proto",
3838
"**/example.proto",
3939
"**/funny_types.proto",
4040
"**/map.proto",

protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package kotlinx.rpc.protobuf
77
import kotlinx.rpc.protobuf.CodeGenerator.DeclarationType
88
import kotlinx.rpc.protobuf.model.*
99
import org.slf4j.Logger
10+
import kotlin.sequences.forEach
1011

1112
private const val RPC_INTERNAL_PACKAGE_SUFFIX = "_rpc_internal"
1213

@@ -51,7 +52,8 @@ class ModelToKotlinGenerator(
5152
return file(codeGenerationParameters, logger = logger) {
5253
filename = this@generateInternalKotlinFile.name
5354
packageName = this@generateInternalKotlinFile.packageName.fullName()
54-
packagePath = this@generateInternalKotlinFile.packageName.fullName().packageNameSuffixed(RPC_INTERNAL_PACKAGE_SUFFIX)
55+
packagePath =
56+
this@generateInternalKotlinFile.packageName.fullName().packageNameSuffixed(RPC_INTERNAL_PACKAGE_SUFFIX)
5557

5658
fileOptIns = listOf("ExperimentalRpcApi::class", "InternalRpcApi::class")
5759

@@ -74,15 +76,13 @@ class ModelToKotlinGenerator(
7476

7577
private fun CodeGenerator.generatePublicDeclaredEntities(fileDeclaration: FileDeclaration) {
7678
fileDeclaration.messageDeclarations.forEach { generatePublicMessage(it) }
77-
// KRPC-141 Enum Types
78-
// fileDeclaration.enumDeclarations.forEach { generateEnum(it) }
79+
fileDeclaration.enumDeclarations.forEach { generatePublicEnum(it) }
7980
fileDeclaration.serviceDeclarations.forEach { generatePublicService(it) }
8081
}
8182

8283
private fun CodeGenerator.generateInternalDeclaredEntities(fileDeclaration: FileDeclaration) {
8384
fileDeclaration.messageDeclarations.forEach { generateInternalMessage(it) }
84-
// KRPC-141 Enum Types
85-
// fileDeclaration.enumDeclarations.forEach { generateEnum(it) }
85+
fileDeclaration.enumDeclarations.forEach { generateInternalEnum(it) }
8686
fileDeclaration.serviceDeclarations.forEach { generateInternalService(it) }
8787
}
8888

@@ -125,7 +125,7 @@ class ModelToKotlinGenerator(
125125
clazz(
126126
name = "${declaration.name.simpleName}Builder",
127127
declarationType = DeclarationType.Class,
128-
superTypes = listOf(declaration.name.simpleName),
128+
superTypes = listOf(declaration.name.fullName()),
129129
) {
130130
declaration.fields().forEach { (fieldDeclaration, type) ->
131131
val value = if (type is FieldType.Reference) {
@@ -144,8 +144,8 @@ class ModelToKotlinGenerator(
144144
name = "invoke",
145145
modifiers = "operator",
146146
args = "body: ${declaration.name.simpleName}Builder.() -> Unit",
147-
contextReceiver = "${declaration.name.simpleName}.Companion",
148-
returnType = declaration.name.simpleName,
147+
contextReceiver = "${declaration.name.fullName()}.Companion",
148+
returnType = declaration.name.fullName(),
149149
) {
150150
code("return ${declaration.name.simpleName}Builder().apply(body)")
151151
}
@@ -154,7 +154,7 @@ class ModelToKotlinGenerator(
154154

155155
function(
156156
name = "toPlatform",
157-
contextReceiver = declaration.name.simpleName,
157+
contextReceiver = declaration.name.fullName(),
158158
returnType = platformType,
159159
) {
160160
scope("return $platformType.newBuilder().apply", ".build()") {
@@ -168,7 +168,7 @@ class ModelToKotlinGenerator(
168168
function(
169169
name = "toKotlin",
170170
contextReceiver = platformType,
171-
returnType = declaration.name.simpleName,
171+
returnType = declaration.name.fullName(),
172172
) {
173173
scope("return ${declaration.name.simpleName}") {
174174
declaration.actualFields.forEach { field ->
@@ -252,12 +252,14 @@ class ModelToKotlinGenerator(
252252
}
253253
}
254254

255-
@Suppress("unused")
256-
private fun CodeGenerator.generateEnum(declaration: EnumDeclaration) {
257-
clazz(declaration.name.simpleName, "enum") {
258-
code(declaration.originalEntries.joinToString(", ", postfix = ";") { enumEntry ->
259-
enumEntry.name.simpleName
260-
})
255+
private fun CodeGenerator.generatePublicEnum(declaration: EnumDeclaration) {
256+
clazz(declaration.name.simpleName, modifiers = "enum") {
257+
declaration.originalEntries.forEach { entry ->
258+
code("${entry.name.simpleName},")
259+
newLine()
260+
}
261+
code(";")
262+
newLine()
261263

262264
if (declaration.aliases.isNotEmpty()) {
263265
newLine()
@@ -274,6 +276,43 @@ class ModelToKotlinGenerator(
274276
}
275277
}
276278

279+
@Suppress("unused")
280+
private fun CodeGenerator.generateInternalEnum(declaration: EnumDeclaration) {
281+
val platformType = "${declaration.outerClassName.fullName()}.${declaration.name.simpleName}"
282+
283+
function(
284+
name = "toPlatform",
285+
contextReceiver = declaration.name.fullName(),
286+
returnType = platformType,
287+
) {
288+
scope("return when (this)") {
289+
declaration.aliases.forEach { field ->
290+
code("${declaration.name.simpleName}.${field.name.simpleName} -> $platformType.${field.name.simpleName}")
291+
}
292+
293+
declaration.originalEntries.forEach { field ->
294+
code("${declaration.name.simpleName}.${field.name.simpleName} -> $platformType.${field.name.simpleName}")
295+
}
296+
}
297+
}
298+
299+
function(
300+
name = "toKotlin",
301+
contextReceiver = platformType,
302+
returnType = declaration.name.fullName(),
303+
) {
304+
scope("return when (this)") {
305+
declaration.aliases.forEach { field ->
306+
code("$platformType.${field.name.simpleName} -> ${declaration.name.simpleName}.${field.name.simpleName}")
307+
}
308+
309+
declaration.originalEntries.forEach { field ->
310+
code("$platformType.${field.name.simpleName} -> ${declaration.name.simpleName}.${field.name.simpleName}")
311+
}
312+
}
313+
}
314+
}
315+
277316
@Suppress("detekt.LongMethod")
278317
private fun CodeGenerator.generatePublicService(service: ServiceDeclaration) {
279318
code("@kotlinx.rpc.grpc.annotations.Grpc")

protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import kotlinx.rpc.protobuf.model.FieldType.IntegralType
1212
import org.slf4j.Logger
1313
import kotlin.properties.Delegates
1414

15+
private const val ENUM_UNRECOGNIZED = "UNRECOGNIZED"
16+
1517
class ProtoToModelInterpreter(
1618
@Suppress("unused")
1719
private val logger: Logger,
@@ -39,12 +41,14 @@ class ProtoToModelInterpreter(
3941
.withScope(packageName)
4042
.withImports(dependencies.map { it.packageName })
4143

44+
val outerClass = outerClassFq()
45+
4246
return FileDeclaration(
4347
name = kotlinFileName(),
4448
packageName = packageName,
4549
dependencies = dependencies,
46-
messageDeclarations = messageTypeList.map { it.toModel(outerClassFq(), parent = null, resolver) },
47-
enumDeclarations = enumTypeList.map { it.toModel(resolver) },
50+
messageDeclarations = messageTypeList.map { it.toModel(resolver, outerClass, parent = null) },
51+
enumDeclarations = enumTypeList.map { it.toModel(resolver, outerClass, packageName) },
4852
serviceDeclarations = serviceList.map { it.toModel(resolver) },
4953
deprecated = options.deprecated,
5054
doc = null,
@@ -58,9 +62,10 @@ class ProtoToModelInterpreter(
5862
val filename = protoFileNameToKotlinName()
5963

6064
val messageClash = messageTypeList.any { it.name.fullProtoNameToKotlin(firstLetterUpper = true) == filename }
65+
val enumClash = enumTypeList.any { it.name.fullProtoNameToKotlin(firstLetterUpper = true) == filename }
6166
val serviceClash = serviceList.any { it.name.fullProtoNameToKotlin(firstLetterUpper = true) == filename }
6267

63-
val suffix = if (messageClash || serviceClash) {
68+
val suffix = if (messageClash || enumClash || serviceClash) {
6469
"OuterClass"
6570
} else {
6671
""
@@ -84,9 +89,9 @@ class ProtoToModelInterpreter(
8489
}
8590

8691
private fun DescriptorProtos.DescriptorProto.toModel(
92+
parentResolver: NameResolver,
8793
outerClass: FqName,
8894
parent: FqName?,
89-
parentResolver: NameResolver,
9095
): MessageDeclaration {
9196
val simpleName = name.fullProtoNameToKotlin(firstLetterUpper = true)
9297
val fqName = parentResolver.declarationFqName(simpleName, parent ?: packageName)
@@ -107,8 +112,8 @@ class ProtoToModelInterpreter(
107112
name = fqName,
108113
actualFields = fields,
109114
oneOfDeclarations = oneofDeclList.asSequence().mapIndexedNotNull { i, desc -> desc.toModel(i, resolver) },
110-
enumDeclarations = enumTypeList.asSequence().map { it.toModel(resolver) },
111-
nestedDeclarations = nestedTypeList.asSequence().map { it.toModel(outerClass, fqName, resolver) },
115+
enumDeclarations = enumTypeList.asSequence().map { it.toModel(resolver, outerClass, parent ?: packageName) },
116+
nestedDeclarations = nestedTypeList.asSequence().map { it.toModel(resolver, outerClass, fqName) },
112117
deprecated = options.deprecated,
113118
doc = null,
114119
).apply {
@@ -247,7 +252,11 @@ class ProtoToModelInterpreter(
247252
)
248253
}
249254

250-
private fun DescriptorProtos.EnumDescriptorProto.toModel(resolver: NameResolver): EnumDeclaration {
255+
private fun DescriptorProtos.EnumDescriptorProto.toModel(
256+
resolver: NameResolver,
257+
outerClassName: FqName,
258+
parent: FqName,
259+
): EnumDeclaration {
251260
val allowAlias = options.allowAlias
252261
val originalEntries = mutableMapOf<Int, EnumDeclaration.Entry>()
253262
val aliases = mutableListOf<EnumDeclaration.Alias>()
@@ -257,34 +266,38 @@ class ProtoToModelInterpreter(
257266
if (original != null) {
258267
if (!allowAlias) {
259268
error(
260-
"Aliases are not allow for enum type $name: " +
269+
"Aliases are not allowed for enum type $name: " +
261270
"${enumEntry.number} of ${enumEntry.name} is already used by $original entry. " +
262271
"Allow aliases via `allow_alias = true` option to avoid this error."
263272
)
264273
}
265274

266275
aliases.add(
267276
EnumDeclaration.Alias(
268-
// TODO KRPC-141 Enum Types: check fqName for nested types
269-
name = resolver.declarationFqName(enumEntry.name, packageName),
277+
name = resolver.declarationFqName(enumEntry.name, parent),
270278
original = original,
271279
deprecated = enumEntry.options.deprecated,
272280
doc = null,
273281
)
274282
)
275283
} else {
276284
originalEntries[enumEntry.number] = EnumDeclaration.Entry(
277-
// TODO KRPC-141 Enum Types: check fqName for nested types
278-
name = resolver.declarationFqName(enumEntry.name, packageName),
285+
name = resolver.declarationFqName(enumEntry.name, parent),
279286
deprecated = enumEntry.options.deprecated,
280287
doc = null,
281288
)
282289
}
283290
}
284291

292+
originalEntries[-1] = EnumDeclaration.Entry(
293+
name = resolver.declarationFqName(ENUM_UNRECOGNIZED, parent),
294+
deprecated = false,
295+
doc = null,
296+
)
297+
285298
return EnumDeclaration(
286-
// TODO KRPC-141 Enum Types: check fqName for nested types
287-
name = resolver.declarationFqName(name.fullProtoNameToKotlin(firstLetterUpper = true), packageName),
299+
name = resolver.declarationFqName(name.fullProtoNameToKotlin(firstLetterUpper = true), parent),
300+
outerClassName = outerClassName,
288301
originalEntries = originalEntries.values.toList(),
289302
aliases = aliases,
290303
deprecated = options.deprecated,

protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/model/EnumDeclaration.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package kotlinx.rpc.protobuf.model
66

77
data class EnumDeclaration(
8+
val outerClassName: FqName,
89
val name: FqName,
910
val originalEntries: List<Entry>,
1011
val aliases: List<Alias>,

protobuf-plugin/src/test/kotlin/kotlinx/rpc/protobuf/test/TestReferenceService.kt

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,16 @@ class ReferenceTestServiceImpl : ReferenceTestService {
2626
primitive = message.other.arg
2727
}
2828
}
29+
30+
override suspend fun Enum(message: UsingEnum): UsingEnum {
31+
return message
32+
}
2933
}
3034

3135
class TestReferenceService {
3236
@Test
3337
fun testReferenceService()= runBlocking {
34-
val grpcClient = GrpcClient("localhost", 8080) {
35-
usePlaintext()
36-
}
37-
38-
val grpcServer = GrpcServer(8080) {
39-
registerService<ReferenceTestService> { ReferenceTestServiceImpl() }
40-
}
41-
42-
grpcServer.start()
38+
val grpcClient = initializeServerAndClient()
4339

4440
val service = grpcClient.withService<ReferenceTestService>()
4541
val result = service.Get(References {
@@ -51,4 +47,29 @@ class TestReferenceService {
5147
assertEquals("42", result.primitive)
5248
assertEquals(42, result.other.field)
5349
}
50+
51+
@Test
52+
fun testEnum() = runBlocking {
53+
val grpcClient = initializeServerAndClient()
54+
55+
val service = grpcClient.withService<ReferenceTestService>()
56+
val result = service.Enum(UsingEnum {
57+
enum = Enum.ONE
58+
})
59+
60+
assertEquals(Enum.ONE, result.enum)
61+
}
62+
63+
private fun initializeServerAndClient(): GrpcClient {
64+
val grpcClient = GrpcClient("localhost", 8080) {
65+
usePlaintext()
66+
}
67+
68+
val grpcServer = GrpcServer(8080) {
69+
registerService<ReferenceTestService> { ReferenceTestServiceImpl() }
70+
}
71+
72+
grpcServer.start()
73+
return grpcClient
74+
}
5475
}

protobuf-plugin/src/test/proto/enum.proto

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,15 @@ syntax = "proto3";
22

33
package kotlinx.rpc.protobuf.test;
44

5-
import "google/protobuf/descriptor.proto";
6-
import "options.proto";
7-
8-
extend google.protobuf.EnumValueOptions {
9-
optional Options options = 50000;
10-
}
11-
125
enum Enum {
136
option allow_alias = true;
147
ZERO = 0;
158
ONE = 1;
169
ONE_SECOND = 1;
1710
TWO = 2 [deprecated = true];
18-
THREE = 3 [
19-
(options).string = "three",
20-
(options).inner.string = "inner three"
21-
];
11+
THREE = 3;
12+
}
13+
14+
message UsingEnum {
15+
Enum enum = 1;
2216
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
syntax = "proto3";
2+
3+
package kotlinx.rpc.protobuf.test;
4+
5+
import "google/protobuf/descriptor.proto";
6+
import "options.proto";
7+
8+
extend google.protobuf.EnumValueOptions {
9+
optional Options options = 50000;
10+
}
11+
12+
enum EnumOptions {
13+
option allow_alias = true;
14+
ZERO = 0;
15+
ONE = 1;
16+
ONE_SECOND = 1;
17+
TWO = 2 [deprecated = true];
18+
THREE = 3 [
19+
(options).string = "three",
20+
(options).inner.string = "inner three"
21+
];
22+
}

protobuf-plugin/src/test/proto/reference_service.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ syntax = "proto3";
22

33
import "reference.proto";
44
import "reference_package.proto";
5+
import "enum.proto";
56

67
service ReferenceTestService {
78
rpc Get(References) returns (kotlinx.rpc.protobuf.test.References);
9+
10+
rpc Enum(kotlinx.rpc.protobuf.test.UsingEnum) returns (kotlinx.rpc.protobuf.test.UsingEnum);
811
}

0 commit comments

Comments
 (0)