Skip to content

Commit 74072c5

Browse files
authored
Fix protocol buffer enum schema generation (#1967)
fix protocol buffer schema generated from enum elements with 'ProtoNumber' annotation
1 parent 7e03eed commit 74072c5

File tree

5 files changed

+76
-4
lines changed

5 files changed

+76
-4
lines changed

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public object ProtoBufSchemaGenerator {
190190

191191

192192
val annotations = messageDescriptor.getElementAnnotations(index)
193-
val number = annotations.filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: index + 1
193+
val number = annotations.filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: (index + 1)
194194
if (!usedNumbers.add(number)) {
195195
throw IllegalArgumentException("Field number $number is repeated in the class with serial name ${messageDescriptor.serialName}")
196196
}
@@ -319,19 +319,35 @@ public object ProtoBufSchemaGenerator {
319319
}
320320
val safeSerialName = removeLineBreaks(enumDescriptor.serialName)
321321
if (safeSerialName != enumName) {
322-
append("// serial name '").append(enumName).appendLine('\'')
322+
append("// serial name '").append(safeSerialName).appendLine('\'')
323323
}
324324

325325
append("enum ").append(enumName).appendLine(" {")
326326

327-
enumDescriptor.elementDescriptors.forEachIndexed { number, element ->
327+
val usedNumbers: MutableSet<Int> = mutableSetOf()
328+
val duplicatedNumbers: MutableSet<Int> = mutableSetOf()
329+
enumDescriptor.elementDescriptors.forEachIndexed { index, element ->
328330
val elementName = element.protobufEnumElementName
329331
elementName.checkIsValidIdentifier {
330332
"The enum element name '$elementName' is invalid in the " +
331333
"protobuf schema. Serial name of the enum class '${enumDescriptor.serialName}'"
332334
}
335+
336+
val annotations = enumDescriptor.getElementAnnotations(index)
337+
val number = annotations.filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: index
338+
if (!usedNumbers.add(number)) {
339+
duplicatedNumbers.add(number)
340+
}
341+
333342
append(" ").append(elementName).append(" = ").append(number).appendLine(';')
334343
}
344+
if (duplicatedNumbers.isNotEmpty()) {
345+
throw IllegalArgumentException(
346+
"The class with serial name ${enumDescriptor.serialName} has duplicate " +
347+
"elements with numbers $duplicatedNumbers"
348+
)
349+
}
350+
335351
appendLine('}')
336352
}
337353

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package kotlinx.serialization.protobuf.schema
33
import kotlinx.serialization.*
44
import kotlinx.serialization.protobuf.*
55
import kotlin.test.Test
6-
import kotlin.test.assertContains
76
import kotlin.test.assertFailsWith
87

98
class SchemaValidationsTest {
@@ -39,6 +38,33 @@ class SchemaValidationsTest {
3938
SECOND
4039
}
4140

41+
@Serializable
42+
enum class EnumWithExplicitProtoNumberDuplicate {
43+
@ProtoNumber(2)
44+
FIRST,
45+
@ProtoNumber(2)
46+
SECOND,
47+
}
48+
49+
@Serializable
50+
enum class EnumWithImplicitProtoNumberDuplicate {
51+
FIRST,
52+
@ProtoNumber(0)
53+
SECOND,
54+
}
55+
56+
@Test
57+
fun testExplicitDuplicateEnumElementProtoNumber() {
58+
val descriptors = listOf(EnumWithExplicitProtoNumberDuplicate.serializer().descriptor)
59+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
60+
}
61+
62+
@Test
63+
fun testImplicitDuplicateEnumElementProtoNumber() {
64+
val descriptors = listOf(EnumWithImplicitProtoNumberDuplicate.serializer().descriptor)
65+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
66+
}
67+
4268
@Test
4369
fun testInvalidEnumElementSerialName() {
4470
val descriptors = listOf(InvalidEnumElementName.serializer().descriptor)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
syntax = "proto2";
2+
3+
package kotlinx.serialization.protobuf.schema.generator;
4+
5+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.EnumWithProtoNumber'
6+
enum EnumWithProtoNumber {
7+
ZERO = 0;
8+
THREE = 3;
9+
TWO = 2;
10+
FIVE = 5;
11+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@ message OptionalCollections {
131131
map<int32, int32> nullableOptionalMap = 8;
132132
}
133133

134+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.EnumWithProtoNumber'
135+
enum EnumWithProtoNumber {
136+
ZERO = 0;
137+
THREE = 3;
138+
TWO = 2;
139+
FIVE = 5;
140+
}
141+
134142
enum OverriddenEnumName {
135143
FIRST = 0;
136144
OverriddenElementName = 1;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal val commonClasses = listOf(
2525
GenerationTest.LegacyMapHolder::class,
2626
GenerationTest.NullableNestedCollections::class,
2727
GenerationTest.OptionalCollections::class,
28+
GenerationTest.EnumWithProtoNumber::class,
2829
)
2930

3031
class GenerationTest {
@@ -180,6 +181,16 @@ class GenerationTest {
180181
val legacyMap: Map<List<Int>?, List<Int>?>
181182
)
182183

184+
@Serializable
185+
enum class EnumWithProtoNumber {
186+
ZERO,
187+
@ProtoNumber(3)
188+
THREE,
189+
TWO,
190+
@ProtoNumber(5)
191+
FIVE,
192+
}
193+
183194
@Test
184195
fun testIndividuals() {
185196
assertSchemaForClass(OptionsClass::class, mapOf("java_package" to "api.proto", "java_outer_classname" to "Outer"))

0 commit comments

Comments
 (0)