Skip to content

Commit 6e578bd

Browse files
committed
KRPC-146 Nested types in gRPC
1 parent adab7e6 commit 6e578bd

File tree

9 files changed

+180
-41
lines changed

9 files changed

+180
-41
lines changed

protobuf-plugin/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ sourceSets {
4040
"**/funny_types.proto",
4141
"**/map.proto",
4242
"**/multiple_files.proto",
43-
"**/nested.proto",
4443
"**/one_of.proto",
4544
"**/options.proto",
4645
"**/with_comments.proto",

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

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ 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
1110

1211
private const val RPC_INTERNAL_PACKAGE_SUFFIX = "_rpc_internal"
1312

@@ -93,8 +92,27 @@ class ModelToKotlinGenerator(
9392

9493
private fun CodeGenerator.generateInternalDeclaredEntities(fileDeclaration: FileDeclaration) {
9594
fileDeclaration.messageDeclarations.forEach { generateInternalMessage(it) }
96-
fileDeclaration.enumDeclarations.forEach { generateInternalEnum(it) }
9795
fileDeclaration.serviceDeclarations.forEach { generateInternalService(it) }
96+
97+
fileDeclaration.messageDeclarations.forEach {
98+
generateToAndFromPlatformCastsRec(it)
99+
}
100+
101+
fileDeclaration.enumDeclarations.forEach {
102+
generateToAndFromPlatformCastsEnum(it)
103+
}
104+
}
105+
106+
private fun CodeGenerator.generateToAndFromPlatformCastsRec(declaration: MessageDeclaration) {
107+
generateToAndFromPlatformCasts(declaration)
108+
109+
declaration.nestedDeclarations.forEach { nested ->
110+
generateToAndFromPlatformCastsRec(nested)
111+
}
112+
113+
declaration.enumDeclarations.forEach { nested ->
114+
generateToAndFromPlatformCastsEnum(nested)
115+
}
98116
}
99117

100118
private fun MessageDeclaration.fields() = actualFields.map {
@@ -112,20 +130,20 @@ class ModelToKotlinGenerator(
112130
newLine()
113131
}
114132

133+
newLine()
134+
115135
// KRPC-147 OneOf Types
116136
// declaration.oneOfDeclarations.forEach { oneOf ->
117137
// generateOneOf(oneOf)
118138
// }
119139
//
120-
// KRPC-146 Nested Types
121-
// declaration.nestedDeclarations.forEach { nested ->
122-
// generateMessage(nested)
123-
// }
124-
//
125-
// KRPC-141 Enum Types
126-
// declaration.enumDeclarations.forEach { enum ->
127-
// generateEnum(enum)
128-
// }
140+
declaration.nestedDeclarations.forEach { nested ->
141+
generatePublicMessage(nested)
142+
}
143+
144+
declaration.enumDeclarations.forEach { enum ->
145+
generatePublicEnum(enum)
146+
}
129147

130148
clazz("", modifiers = "companion", declarationType = DeclarationType.Object)
131149
}
@@ -157,20 +175,25 @@ class ModelToKotlinGenerator(
157175
code("override var $fieldDeclaration $value")
158176
newLine()
159177
}
178+
179+
declaration.nestedDeclarations.forEach { nested ->
180+
generateInternalMessage(nested)
181+
}
160182
}
183+
}
161184

185+
private fun CodeGenerator.generateToAndFromPlatformCasts(declaration: MessageDeclaration) {
162186
function(
163187
name = "invoke",
164188
modifiers = "operator",
165-
args = "body: ${declaration.name.simpleName}Builder.() -> Unit",
189+
args = "body: ${declaration.name.safeFullName("Builder")}.() -> Unit",
166190
contextReceiver = "${declaration.name.safeFullName()}.Companion",
167191
returnType = declaration.name.safeFullName(),
168192
) {
169-
code("return ${declaration.name.simpleName}Builder().apply(body)")
193+
code("return ${declaration.name.safeFullName("Builder")}().apply(body)")
170194
}
171195

172-
val platformType = "${declaration.outerClassName.safeFullName()}.${declaration.name.simpleName}"
173-
196+
val platformType = "${declaration.outerClassName.safeFullName()}.${declaration.name.fullNestedName()}"
174197
function(
175198
name = "toPlatform",
176199
contextReceiver = declaration.name.safeFullName(),
@@ -198,7 +221,7 @@ class ModelToKotlinGenerator(
198221
contextReceiver = platformType,
199222
returnType = declaration.name.safeFullName(),
200223
) {
201-
scope("return ${declaration.name.simpleName}") {
224+
scope("return ${declaration.name.safeFullName()}") {
202225
declaration.actualFields.forEach { field ->
203226
val javaName = when (field.type) {
204227
is FieldType.List -> "${field.name}List"
@@ -361,8 +384,8 @@ class ModelToKotlinGenerator(
361384
}
362385

363386
@Suppress("unused")
364-
private fun CodeGenerator.generateInternalEnum(declaration: EnumDeclaration) {
365-
val platformType = "${declaration.outerClassName.safeFullName()}.${declaration.name.simpleName}"
387+
private fun CodeGenerator.generateToAndFromPlatformCastsEnum(declaration: EnumDeclaration) {
388+
val platformType = "${declaration.outerClassName.safeFullName()}.${declaration.name.fullNestedName()}"
366389

367390
function(
368391
name = "toPlatform",
@@ -371,11 +394,11 @@ class ModelToKotlinGenerator(
371394
) {
372395
scope("return when (this)") {
373396
declaration.aliases.forEach { field ->
374-
code("${declaration.name.simpleName}.${field.name.simpleName} -> $platformType.${field.name.simpleName}")
397+
code("${declaration.name.fullNestedName()}.${field.name.simpleName} -> $platformType.${field.name.simpleName}")
375398
}
376399

377400
declaration.originalEntries.forEach { field ->
378-
code("${declaration.name.simpleName}.${field.name.simpleName} -> $platformType.${field.name.simpleName}")
401+
code("${declaration.name.fullNestedName()}.${field.name.simpleName} -> $platformType.${field.name.simpleName}")
379402
}
380403
}
381404
}
@@ -387,11 +410,11 @@ class ModelToKotlinGenerator(
387410
) {
388411
scope("return when (this)") {
389412
declaration.aliases.forEach { field ->
390-
code("$platformType.${field.name.simpleName} -> ${declaration.name.simpleName}.${field.name.simpleName}")
413+
code("$platformType.${field.name.simpleName} -> ${declaration.name.fullNestedName()}.${field.name.simpleName}")
391414
}
392415

393416
declaration.originalEntries.forEach { field ->
394-
code("$platformType.${field.name.simpleName} -> ${declaration.name.simpleName}.${field.name.simpleName}")
417+
code("$platformType.${field.name.simpleName} -> ${declaration.name.fullNestedName()}.${field.name.simpleName}")
395418
}
396419
}
397420
}
@@ -528,13 +551,13 @@ class ModelToKotlinGenerator(
528551
}
529552

530553
private fun MessageDeclaration.toPlatformMessageType(): String {
531-
return "${outerClassName.safeFullName()}.${name.simpleName}"
554+
return "${outerClassName.safeFullName()}.${name.fullNestedName()}"
532555
}
533556

534-
private fun FqName.safeFullName(): String {
557+
private fun FqName.safeFullName(classSuffix: String = ""): String {
535558
importRootDeclarationIfNeeded(this)
536559

537-
return fullName()
560+
return fullName(classSuffix)
538561
}
539562

540563
private fun importRootDeclarationIfNeeded(

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class ProtoToModelInterpreter(
9797
val fqName = parentResolver.declarationFqName(simpleName, parent ?: packageName)
9898
val resolver = parentResolver.withScope(fqName)
9999

100-
val fields = fieldList.asSequence().mapNotNull {
100+
val fields = fieldList.mapNotNull {
101101
val oneOfName = if (it.hasOneofIndex()) {
102102
oneofDeclList[it.oneofIndex].name
103103
} else {
@@ -111,10 +111,9 @@ class ProtoToModelInterpreter(
111111
outerClassName = outerClass,
112112
name = fqName,
113113
actualFields = fields,
114-
oneOfDeclarations = oneofDeclList.asSequence().mapIndexedNotNull { i, desc -> desc.toModel(i, resolver) },
115-
enumDeclarations = enumTypeList.asSequence()
116-
.map { it.toModel(resolver, outerClass, parent ?: packageName) },
117-
nestedDeclarations = nestedTypeList.asSequence().map { it.toModel(resolver, outerClass, fqName) },
114+
oneOfDeclarations = oneofDeclList.mapIndexedNotNull { i, desc -> desc.toModel(i, resolver) },
115+
enumDeclarations = enumTypeList.map { it.toModel(resolver, outerClass, fqName) },
116+
nestedDeclarations = nestedTypeList.map { it.toModel(resolver, outerClass, fqName) },
118117
deprecated = options.deprecated,
119118
doc = null,
120119
).apply {

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,29 @@ sealed interface FqName {
5353
}
5454
}
5555

56-
internal fun FqName.fullName(): String {
56+
internal fun FqName.fullName(classSuffix: String = ""): String {
5757
val parentName = parent
58+
val name = if (this is FqName.Declaration) "$simpleName$classSuffix" else simpleName
5859
return when {
59-
parentName == this -> simpleName
60+
parentName == this -> name
6061
else -> {
61-
val fullParentName = parentName.fullName()
62+
val fullParentName = parentName.fullName(classSuffix)
63+
if (fullParentName.isEmpty()) {
64+
name
65+
} else {
66+
"$fullParentName.$name"
67+
}
68+
}
69+
}
70+
}
71+
72+
internal fun FqName.fullNestedName(): String {
73+
val parentName = parent
74+
return when (parentName) {
75+
is FqName.Package -> simpleName
76+
this -> simpleName
77+
else -> {
78+
val fullParentName = parentName.fullNestedName()
6279
if (fullParentName.isEmpty()) {
6380
simpleName
6481
} else {

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ package kotlinx.rpc.protobuf.model
77
data class MessageDeclaration(
88
val outerClassName: FqName,
99
val name: FqName,
10-
val actualFields: Sequence<FieldDeclaration>, // excludes oneOf fields, but includes oneOf itself
11-
val oneOfDeclarations: Sequence<OneOfDeclaration>,
12-
val enumDeclarations: Sequence<EnumDeclaration>,
13-
val nestedDeclarations: Sequence<MessageDeclaration>,
10+
val actualFields: List<FieldDeclaration>, // excludes oneOf fields, but includes oneOf itself
11+
val oneOfDeclarations: List<OneOfDeclaration>,
12+
val enumDeclarations: List<EnumDeclaration>,
13+
val nestedDeclarations: List<MessageDeclaration>,
1414
val deprecated: Boolean,
1515
val doc: String?,
1616
)

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,17 @@ internal class NameResolver private constructor(
177177
return _list!!
178178
}
179179
}
180+
181+
@Suppress("unused")
182+
fun pprint(): String {
183+
return buildString { pprint(root, 0) }
184+
}
185+
186+
private fun StringBuilder.pprint(node: Node, indent: Int) {
187+
val spaces = " ".repeat(indent)
188+
appendLine("$spaces${node.fqName.fullName()}")
189+
for (child in node.children.values) {
190+
pprint(child, indent + 4)
191+
}
192+
}
180193
}

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

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import kotlinx.rpc.registerService
1313
import kotlinx.rpc.withService
1414
import kotlin.test.Test
1515
import kotlin.test.assertEquals
16+
import kotlin.test.assertNotNull
1617

1718
class ReferenceTestServiceImpl : ReferenceTestService {
1819
override suspend fun Get(message: References): kotlinx.rpc.protobuf.test.References {
@@ -36,6 +37,10 @@ class ReferenceTestServiceImpl : ReferenceTestService {
3637
override suspend fun Repeated(message: Repeated): Repeated {
3738
return message
3839
}
40+
41+
override suspend fun Nested(message: Nested): Nested {
42+
return message
43+
}
3944
}
4045

4146
class TestReferenceService : GrpcServerTest() {
@@ -44,7 +49,7 @@ class TestReferenceService : GrpcServerTest() {
4449
}
4550

4651
@Test
47-
fun testReferenceService()= runGrpcTest { grpcClient ->
52+
fun testReferenceService() = runGrpcTest { grpcClient ->
4853
val service = grpcClient.withService<ReferenceTestService>()
4954
val result = service.Get(References {
5055
other = Other {
@@ -113,4 +118,83 @@ class TestReferenceService : GrpcServerTest() {
113118
assertEquals(emptyList(), resultEmpty.listString)
114119
assertEquals(emptyList(), resultEmpty.listReference)
115120
}
121+
122+
@Test
123+
fun testNested() = runGrpcTest { grpcClient ->
124+
val service = grpcClient.withService<ReferenceTestService>()
125+
val result = service.Nested(Nested {
126+
inner1 = Nested.Inner1 {
127+
inner11 = Nested.Inner1.Inner11 {
128+
reference21 = null
129+
reference12 = Nested.Inner1.Inner12 {
130+
recursion = null
131+
}
132+
enum = Nested.Inner2.NestedEnum.ZERO
133+
}
134+
135+
inner22 = Nested.Inner1.Inner12 {
136+
recursion = Nested.Inner1.Inner12 {
137+
recursion = null
138+
}
139+
}
140+
141+
string = "42_1"
142+
143+
inner1 = null
144+
}
145+
146+
inner2 = Nested.Inner2 {
147+
inner21 = Nested.Inner2.Inner21 {
148+
reference11 = Nested.Inner1.Inner11 {
149+
reference21 = null
150+
reference12 = Nested.Inner1.Inner12 {
151+
recursion = null
152+
}
153+
enum = Nested.Inner2.NestedEnum.ZERO
154+
}
155+
156+
reference22 = Nested.Inner2.Inner22 {
157+
enum = Nested.Inner2.NestedEnum.ZERO
158+
}
159+
}
160+
161+
inner22 = Nested.Inner2.Inner22 {
162+
enum = Nested.Inner2.NestedEnum.ZERO
163+
}
164+
string = "42_2"
165+
}
166+
167+
string = "42"
168+
enum = Nested.Inner2.NestedEnum.ZERO
169+
})
170+
171+
// Assert Inner1.Inner11
172+
assertEquals(null, result.inner1.inner11.reference21)
173+
assertEquals(null, result.inner1.inner11.reference12.recursion)
174+
assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner1.inner11.enum)
175+
176+
// Assert Inner1.Inner12
177+
assertNotNull(result.inner1.inner22.recursion)
178+
assertEquals(null, result.inner1.inner22.recursion?.recursion)
179+
180+
// Assert Inner1
181+
assertEquals("42_1", result.inner1.string)
182+
assertEquals(null, result.inner1.inner1)
183+
184+
// Assert Inner2.Inner21
185+
assertEquals(null, result.inner2.inner21.reference11.reference21)
186+
assertEquals(null, result.inner2.inner21.reference11.reference12.recursion)
187+
assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference11.enum)
188+
assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner21.reference22.enum)
189+
190+
// Assert Inner2.Inner22
191+
assertEquals(Nested.Inner2.NestedEnum.ZERO, result.inner2.inner22.enum)
192+
193+
// Assert Inner2
194+
assertEquals("42_2", result.inner2.string)
195+
196+
// Assert root Nested
197+
assertEquals("42", result.string)
198+
assertEquals(Nested.Inner2.NestedEnum.ZERO, result.enum)
199+
}
116200
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@ package kotlinx.rpc.protobuf.test;
55
message Nested {
66
message Inner1 {
77
message Inner11 {
8-
Nested.Inner2.Inner21 reference21 = 1;
8+
optional Nested.Inner2.Inner21 reference21 = 1;
99
Nested.Inner1.Inner12 reference12 = 2;
1010
Nested.Inner2.NestedEnum enum = 3;
1111
}
1212

1313
message Inner12 {
14-
Inner12 recursion = 1;
14+
optional Inner12 recursion = 1;
1515
}
1616

1717
Inner11 inner11 = 1;
1818
Inner12 inner22 = 2;
1919
string string = 3;
20+
optional Inner1 inner1 = 4;
2021
}
2122

2223
message Inner2 {

0 commit comments

Comments
 (0)