Skip to content

Commit a7bcacc

Browse files
committed
KRPC-143 Repeated types in gRPC (#328)
1 parent e541e1c commit a7bcacc

File tree

6 files changed

+103
-31
lines changed

6 files changed

+103
-31
lines changed

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

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,15 @@ class ModelToKotlinGenerator(
179179
scope("return $platformType.newBuilder().apply", ".build()") {
180180
declaration.actualFields.forEach { field ->
181181
val uppercaseName = field.name.replaceFirstChar { ch -> ch.uppercase() }
182-
val setFieldCall = "set$uppercaseName"
182+
val setFieldCall = when (field.type) {
183+
is FieldType.List -> "addAll$uppercaseName"
184+
else -> "set$uppercaseName"
185+
}
183186

184187
if (field.nullable) {
185-
code("this@toPlatform.${field.name}?.let { $setFieldCall(it${field.toPlatformCast()}) }")
188+
code("this@toPlatform.${field.name}?.let { $setFieldCall(it${field.type.toPlatformCast()}) }")
186189
} else {
187-
code("$setFieldCall(this@toPlatform.${field.name}${field.toPlatformCast()})")
190+
code("$setFieldCall(this@toPlatform.${field.name}${field.type.toPlatformCast()})")
188191
}
189192
}
190193
}
@@ -197,7 +200,12 @@ class ModelToKotlinGenerator(
197200
) {
198201
scope("return ${declaration.name.simpleName}") {
199202
declaration.actualFields.forEach { field ->
200-
val getter = "this@toKotlin.${field.name}${field.toKotlinCast()}"
203+
val javaName = when (field.type) {
204+
is FieldType.List -> "${field.name}List"
205+
else -> field.name
206+
}
207+
208+
val getter = "this@toKotlin.$javaName${field.type.toKotlinCast()}"
201209
if (field.nullable) {
202210
ifBranch(
203211
prefix = "${field.name} = ",
@@ -217,33 +225,53 @@ class ModelToKotlinGenerator(
217225
}
218226
}
219227

220-
private fun FieldDeclaration.toPlatformCast(): String {
221-
return when (type) {
228+
private fun FieldType.toPlatformCast(): String {
229+
return when (this) {
222230
FieldType.IntegralType.FIXED32 -> ".toInt()"
223231
FieldType.IntegralType.FIXED64 -> ".toLong()"
224232
FieldType.IntegralType.UINT32 -> ".toInt()"
225233
FieldType.IntegralType.UINT64 -> ".toLong()"
226234
FieldType.IntegralType.BYTES -> ".let { bytes -> com.google.protobuf.ByteString.copyFrom(bytes) }"
227235
is FieldType.Reference -> ".toPlatform()".also {
228-
val fq by type.value
236+
val fq by value
229237
importRootDeclarationIfNeeded(fq, "toPlatform", true)
230238
}
239+
is FieldType.List -> {
240+
when (value) {
241+
is FieldType.Reference -> ".map { it.toPlatform() }".also {
242+
val fq by value.value
243+
importRootDeclarationIfNeeded(fq, "toPlatform", true)
244+
}
245+
is FieldType.IntegralType -> ""
246+
else -> error("Unsupported type: $value")
247+
}
248+
}
231249

232250
else -> ""
233251
}
234252
}
235253

236-
private fun FieldDeclaration.toKotlinCast(): String {
237-
return when (type) {
254+
private fun FieldType.toKotlinCast(): String {
255+
return when (this) {
238256
FieldType.IntegralType.FIXED32 -> ".toUInt()"
239257
FieldType.IntegralType.FIXED64 -> ".toULong()"
240258
FieldType.IntegralType.UINT32 -> ".toUInt()"
241259
FieldType.IntegralType.UINT64 -> ".toULong()"
242260
FieldType.IntegralType.BYTES -> ".toByteArray()"
243261
is FieldType.Reference -> ".toKotlin()".also {
244-
val fq by type.value
262+
val fq by value
245263
importRootDeclarationIfNeeded(fq, "toKotlin", true)
246264
}
265+
is FieldType.List -> {
266+
when (value) {
267+
is FieldType.Reference -> ".map { it.toKotlin() }".also {
268+
val fq by value.value
269+
importRootDeclarationIfNeeded(fq, "toKotlin", true)
270+
}
271+
is FieldType.IntegralType -> ".toList()"
272+
else -> error("Unsupported type: $value")
273+
}
274+
}
247275

248276
else -> ""
249277
}
@@ -265,17 +293,22 @@ class ModelToKotlinGenerator(
265293
type.fqName.simpleName
266294
}
267295

268-
// KRPC-143 Repeated Types
269-
// is FieldType.List -> {
270-
// "List<${type.valueName.simpleName}>"
271-
// }
272-
//
296+
is FieldType.List -> {
297+
val fqValue = when (val value = type.value) {
298+
is FieldType.Reference -> value.value.value
299+
is FieldType.IntegralType -> value.fqName
300+
else -> error("Unsupported type: $value")
301+
}
302+
303+
"List<${fqValue.safeFullName()}>"
304+
}
305+
273306
// KRPC-145 Map Types
274307
// is FieldType.Map -> {
275308
// "Map<${type.keyName.simpleName}, ${type.valueName.simpleName}>"
276309
// }
277310
else -> {
278-
error("Unsupported type: $type")
311+
error("Unsupported type: $this")
279312
}
280313
}.withNullability(nullable)
281314
}

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ class ProtoToModelInterpreter(
112112
name = fqName,
113113
actualFields = fields,
114114
oneOfDeclarations = oneofDeclList.asSequence().mapIndexedNotNull { i, desc -> desc.toModel(i, resolver) },
115-
enumDeclarations = enumTypeList.asSequence().map { it.toModel(resolver, outerClass, parent ?: packageName) },
115+
enumDeclarations = enumTypeList.asSequence()
116+
.map { it.toModel(resolver, outerClass, parent ?: packageName) },
116117
nestedDeclarations = nestedTypeList.asSequence().map { it.toModel(resolver, outerClass, fqName) },
117118
deprecated = options.deprecated,
118119
doc = null,
@@ -178,25 +179,27 @@ class ProtoToModelInterpreter(
178179
.fullProtoNameToKotlin(firstLetterUpper = true)
179180
// TODO KRPC-146 Nested Types: parent full type resolution
180181
// KRPC-144 Import types
181-
.let { wrapWithLabel(lazy { resolver.resolve(it) }) }
182+
.asReference { resolver.resolve(it) }
183+
.let { wrapWithLabel(it) }
182184
}
183185

184186
hasTypeName() -> {
185187
typeName
186188
.fullProtoNameToKotlin(firstLetterUpper = true)
187189
// TODO KRPC-146 Nested Types: parent full type resolution
188190
// KRPC-144 Import types: we assume local types now
189-
.let { wrapWithLabel(lazy { resolver.resolve(it) }) }
191+
.asReference { resolver.resolve(it) }
192+
.let { wrapWithLabel(it) }
190193
}
191194

192195
else -> {
193-
primitiveType()
196+
wrapWithLabel(primitiveType())
194197
}
195198
}
196199
}
197200

198201
@Suppress("detekt.CyclomaticComplexMethod")
199-
private fun DescriptorProtos.FieldDescriptorProto.primitiveType(): FieldType {
202+
private fun DescriptorProtos.FieldDescriptorProto.primitiveType(): IntegralType {
200203
return when (type) {
201204
Type.TYPE_STRING -> IntegralType.STRING
202205
Type.TYPE_BYTES -> IntegralType.BYTES
@@ -219,15 +222,18 @@ class ProtoToModelInterpreter(
219222
}
220223
}
221224

222-
private fun DescriptorProtos.FieldDescriptorProto.wrapWithLabel(fqName: Lazy<FqName>): FieldType {
225+
private fun String.asReference(resolver: (String) -> FqName) =
226+
FieldType.Reference(lazy { resolver(this) })
227+
228+
private fun DescriptorProtos.FieldDescriptorProto.wrapWithLabel(fieldType: FieldType): FieldType {
223229
return when (label) {
224230
DescriptorProtos.FieldDescriptorProto.Label.LABEL_REPEATED -> {
225-
FieldType.List(fqName)
231+
FieldType.List(fieldType)
226232
}
227233
// LABEL_OPTIONAL is not actually optional in proto3.
228234
// Actual optional is oneOf with one option and same name
229235
else -> {
230-
FieldType.Reference(fqName)
236+
fieldType
231237
}
232238
}
233239
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ data class FieldDeclaration(
1515
sealed interface FieldType {
1616
val defaultValue: String
1717

18-
data class List(val valueName: Lazy<FqName>) : FieldType {
18+
data class List(val value: FieldType) : FieldType {
1919
override val defaultValue: String = "emptyList()"
2020
}
2121

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

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@ import ReferenceTestService
88
import References
99
import invoke
1010
import Other
11-
import kotlinx.coroutines.runBlocking
12-
import kotlinx.coroutines.sync.Mutex
13-
import kotlinx.coroutines.sync.withLock
14-
import kotlinx.coroutines.test.runTest
1511
import kotlinx.rpc.RpcServer
16-
import kotlinx.rpc.grpc.GrpcClient
17-
import kotlinx.rpc.grpc.GrpcServer
1812
import kotlinx.rpc.registerService
1913
import kotlinx.rpc.withService
2014
import kotlin.test.Test
@@ -38,6 +32,10 @@ class ReferenceTestServiceImpl : ReferenceTestService {
3832
override suspend fun Optional(message: OptionalTypes): OptionalTypes {
3933
return message
4034
}
35+
36+
override suspend fun Repeated(message: Repeated): Repeated {
37+
return message
38+
}
4139
}
4240

4341
class TestReferenceService : GrpcServerTest() {
@@ -93,4 +91,26 @@ class TestReferenceService : GrpcServerTest() {
9391
assertEquals(null, resultNullable.age)
9492
assertEquals(null, resultNullable.reference)
9593
}
94+
95+
@Test
96+
fun testRepeated() = runGrpcTest { grpcClient ->
97+
val service = grpcClient.withService<ReferenceTestService>()
98+
val result = service.Repeated(Repeated {
99+
listString = listOf("test", "hello")
100+
listReference = listOf(kotlinx.rpc.protobuf.test.References {
101+
other = kotlinx.rpc.protobuf.test.Other {
102+
field = 42
103+
}
104+
})
105+
})
106+
107+
assertEquals(listOf("test", "hello"), result.listString)
108+
assertEquals(1, result.listReference.size)
109+
assertEquals(42, result.listReference[0].other.field)
110+
111+
val resultEmpty = service.Repeated(Repeated {})
112+
113+
assertEquals(emptyList(), resultEmpty.listString)
114+
assertEquals(emptyList(), resultEmpty.listReference)
115+
}
96116
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import "reference.proto";
44
import "reference_package.proto";
55
import "enum.proto";
66
import "optional.proto";
7+
import "repeated.proto";
78

89
service ReferenceTestService {
910
rpc Get(References) returns (kotlinx.rpc.protobuf.test.References);
1011

1112
rpc Enum(kotlinx.rpc.protobuf.test.UsingEnum) returns (kotlinx.rpc.protobuf.test.UsingEnum);
1213

1314
rpc Optional(kotlinx.rpc.protobuf.test.OptionalTypes) returns (kotlinx.rpc.protobuf.test.OptionalTypes);
15+
16+
rpc Repeated(kotlinx.rpc.protobuf.test.Repeated) returns (kotlinx.rpc.protobuf.test.Repeated);
1417
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
syntax = "proto3";
2+
3+
package kotlinx.rpc.protobuf.test;
4+
5+
import 'reference_package.proto';
6+
7+
message Repeated {
8+
repeated string listString = 1;
9+
repeated References listReference = 2;
10+
}

0 commit comments

Comments
 (0)