Skip to content

Commit 9e2040b

Browse files
pdvriezeshanshin
authored andcommitted
Add packed support to the ProtoBufSchemaGenerator.
1 parent 05f0dfd commit 9e2040b

File tree

5 files changed

+80
-5
lines changed

5 files changed

+80
-5
lines changed

formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import kotlinx.serialization.*
66
import kotlinx.serialization.builtins.*
77
import kotlinx.serialization.descriptors.*
88
import kotlinx.serialization.protobuf.*
9+
import kotlinx.serialization.protobuf.internal.*
910

1011
/**
1112
* Experimental generator of ProtoBuf schema that is compatible with [serializable][Serializable] Kotlin classes
@@ -175,9 +176,11 @@ public object ProtoBufSchemaGenerator {
175176

176177
val fieldDescriptor = messageDescriptor.getElementDescriptor(index)
177178

179+
val isList = fieldDescriptor.isProtobufRepeated
180+
178181
nestedTypes += when {
179182
fieldDescriptor.isProtobufNamedType -> generateNamedType(messageType, index)
180-
fieldDescriptor.isProtobufRepeated -> generateListType(messageType, index)
183+
isList -> generateListType(messageType, index)
181184
fieldDescriptor.isProtobufMap -> generateMapType(messageType, index)
182185
else -> throw IllegalStateException(
183186
"Unprocessed message field type with serial name " +
@@ -192,7 +195,16 @@ public object ProtoBufSchemaGenerator {
192195
throw IllegalArgumentException("Field number $number is repeated in the class with serial name ${messageDescriptor.serialName}")
193196
}
194197

195-
append(' ').append(fieldName).append(" = ").append(number).appendLine(';')
198+
append(' ').append(fieldName).append(" = ").append(number)
199+
200+
val isPackRequested = annotations.filterIsInstance<ProtoPacked>().singleOrNull() != null
201+
202+
when {
203+
!isPackRequested -> appendLine(';')
204+
!isList -> throw IllegalArgumentException("ProtoPacked annotation provided for ${messageDescriptor.getElementName(index)}: $fieldDescriptor, but packing is only valid on repeated fields (lists)")
205+
!fieldDescriptor.getElementDescriptor(0).isPackable -> throw IllegalArgumentException("ProtoPacked annotation provided for ${messageDescriptor.getElementName(index)}: $fieldDescriptor, but packed can only be applied to primitive numeric types")
206+
else -> appendLine(" [packed=true];")
207+
}
196208
}
197209
appendLine('}')
198210

formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/schema/SchemaValidationsTest.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package kotlinx.serialization.protobuf.schema
22

33
import kotlinx.serialization.*
4-
import kotlinx.serialization.protobuf.ProtoNumber
4+
import kotlinx.serialization.protobuf.*
55
import kotlin.test.Test
6+
import kotlin.test.assertContains
67
import kotlin.test.assertFailsWith
78

89
class SchemaValidationsTest {
@@ -38,6 +39,12 @@ class SchemaValidationsTest {
3839
SECOND
3940
}
4041

42+
@Serializable
43+
class NonListPackedField(@ProtoPacked val i: Int)
44+
45+
@Serializable
46+
class ListOfNonNumberPackedField(@ProtoPacked val s: List<String>)
47+
4148

4249
@Test
4350
fun testInvalidEnumElementSerialName() {
@@ -75,6 +82,20 @@ class SchemaValidationsTest {
7582
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
7683
}
7784

85+
@Test
86+
fun testNonListPackedField() {
87+
val descriptor = NonListPackedField.serializer().descriptor
88+
val e = assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptor) }
89+
assertContains(e.message ?: "", "only valid on repeated fields (lists)")
90+
}
91+
92+
@Test
93+
fun testNonNumberPackedField() {
94+
val descriptor = ListOfNonNumberPackedField.serializer().descriptor
95+
val e = assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptor) }
96+
assertContains(e.message ?: "", "packed can only be applied to primitive numeric types")
97+
}
98+
7899
@Test
79100
fun testInvalidOptionName() {
80101
val descriptors = listOf(ValidClass.serializer().descriptor)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
syntax = "proto2";
2+
3+
package kotlinx.serialization.protobuf.schema.generator;
4+
5+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.PackedListClass'
6+
message PackedListClass {
7+
repeated int32 intList = 1 [packed=true];
8+
repeated int32 intArray = 2 [packed=true];
9+
// WARNING: nullable elements of collections can not be represented in protobuf
10+
repeated int32 boxedIntArray = 3 [packed=true];
11+
repeated OptionsClass messageList = 4;
12+
repeated OverriddenEnumName enumList = 5;
13+
}
14+
15+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionsClass'
16+
message OptionsClass {
17+
required int32 i = 1;
18+
}
19+
20+
enum OverriddenEnumName {
21+
FIRST = 0;
22+
OverriddenElementName = 1;
23+
}

formats/protobuf/jvmTest/resources/common/schema.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ message ListClass {
4242
repeated OverriddenEnumName enumList = 5;
4343
}
4444

45+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.PackedListClass'
46+
message PackedListClass {
47+
repeated int32 intList = 1 [packed=true];
48+
repeated int32 intArray = 2 [packed=true];
49+
// WARNING: nullable elements of collections can not be represented in protobuf
50+
repeated int32 boxedIntArray = 3 [packed=true];
51+
repeated OptionsClass messageList = 4;
52+
repeated OverriddenEnumName enumList = 5;
53+
}
54+
4555
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.MapClass'
4656
message MapClass {
4757
map<int32, float> scalarMap = 1;

formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/schema/GenerationTest.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ package kotlinx.serialization.protobuf.schema
33
import kotlinx.serialization.*
44
import kotlinx.serialization.descriptors.SerialDescriptor
55
import kotlinx.serialization.protobuf.ProtoIntegerType
6-
import kotlinx.serialization.protobuf.ProtoNumber
7-
import kotlinx.serialization.protobuf.ProtoType
6+
import kotlinx.serialization.protobuf.*
87
import kotlin.reflect.KClass
98
import kotlin.test.Test
109
import kotlin.test.assertEquals
@@ -16,6 +15,7 @@ internal val commonClasses = listOf(
1615
GenerationTest.FieldNumberClass::class,
1716
GenerationTest.SerialNameClass::class,
1817
GenerationTest.ListClass::class,
18+
GenerationTest.PackedListClass::class,
1919
GenerationTest.MapClass::class,
2020
GenerationTest.OptionalClass::class,
2121
GenerationTest.ContextualHolder::class,
@@ -92,6 +92,15 @@ class GenerationTest {
9292
val enumList: List<SerialNameEnum>
9393
)
9494

95+
@Serializable
96+
class PackedListClass(
97+
@ProtoPacked val intList: List<Int>,
98+
@ProtoPacked val intArray: IntArray,
99+
@ProtoPacked val boxedIntArray: Array<Int?>,
100+
val messageList: List<OptionsClass>,
101+
val enumList: List<SerialNameEnum>
102+
)
103+
95104
@Serializable
96105
class MapClass(
97106
val scalarMap: Map<Int, Float>,

0 commit comments

Comments
 (0)