Skip to content

Commit 75566cc

Browse files
shanshinqwwdfsad
andauthored
Generator for .proto files based on serializable Kotlin classes (#1255)
Fixes #34 Co-authored-by: Vsevolod Tolstopyatov <qwwdfsad@gmail.com>
1 parent 38adb1b commit 75566cc

21 files changed

+1484
-1
lines changed

formats/protobuf/api/kotlinx-serialization-protobuf.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,11 @@ public final class kotlinx/serialization/protobuf/ProtoType$Impl : kotlinx/seria
4747
public final synthetic fun type ()Lkotlinx/serialization/protobuf/ProtoIntegerType;
4848
}
4949

50+
public final class kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator {
51+
public static final field INSTANCE Lkotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator;
52+
public final fun generateSchemaText (Ljava/util/List;Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String;
53+
public final fun generateSchemaText (Lkotlinx/serialization/descriptors/SerialDescriptor;Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String;
54+
public static synthetic fun generateSchemaText$default (Lkotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator;Ljava/util/List;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/String;
55+
public static synthetic fun generateSchemaText$default (Lkotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator;Lkotlinx/serialization/descriptors/SerialDescriptor;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/String;
56+
}
57+

formats/protobuf/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ kotlin {
4040
}
4141

4242
sourceSets.test.proto {
43-
srcDirs = ['testProto']
43+
srcDirs = ['testProto', 'jvmTest/resources/common']
4444
}
4545

4646
compileTestKotlinJvm {

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

Lines changed: 511 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package kotlinx.serialization.protobuf.schema
2+
3+
import kotlinx.serialization.*
4+
import kotlinx.serialization.protobuf.ProtoNumber
5+
import kotlin.test.Test
6+
import kotlin.test.assertFailsWith
7+
8+
class SchemaValidationsTest {
9+
@Serializable
10+
data class ValidClass(val i: Int)
11+
12+
@Serializable
13+
@SerialName("ValidClass")
14+
data class DuplicateClass(val l: Long)
15+
16+
@Serializable
17+
@SerialName("invalid serial name")
18+
data class InvalidClassName(val i: Int)
19+
20+
@Serializable
21+
data class InvalidClassFieldName(@SerialName("invalid serial name") val i: Int)
22+
23+
@Serializable
24+
data class FieldNumberDuplicates(@ProtoNumber(42) val i: Int, @ProtoNumber(42) val j: Int)
25+
26+
@Serializable
27+
data class FieldNumberImplicitlyDuplicates(@ProtoNumber(2) val i: Int, val j: Int)
28+
29+
@Serializable
30+
@SerialName("invalid serial name")
31+
enum class InvalidEnumName { SINGLETON }
32+
33+
@Serializable
34+
enum class InvalidEnumElementName {
35+
FIRST,
36+
37+
@SerialName("invalid serial name")
38+
SECOND
39+
}
40+
41+
42+
@Test
43+
fun testInvalidEnumElementSerialName() {
44+
val descriptors = listOf(InvalidEnumElementName.serializer().descriptor)
45+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
46+
}
47+
48+
@Test
49+
fun testInvalidClassSerialName() {
50+
val descriptors = listOf(InvalidClassName.serializer().descriptor)
51+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
52+
}
53+
54+
@Test
55+
fun testInvalidClassFieldSerialName() {
56+
val descriptors = listOf(InvalidClassFieldName.serializer().descriptor)
57+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
58+
}
59+
60+
@Test
61+
fun testDuplicateSerialNames() {
62+
val descriptors = listOf(InvalidClassFieldName.serializer().descriptor)
63+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
64+
}
65+
66+
@Test
67+
fun testInvalidEnumSerialName() {
68+
val descriptors = listOf(InvalidEnumName.serializer().descriptor)
69+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
70+
}
71+
72+
@Test
73+
fun testDuplicationSerialName() {
74+
val descriptors = listOf(ValidClass.serializer().descriptor, DuplicateClass.serializer().descriptor)
75+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
76+
}
77+
78+
@Test
79+
fun testInvalidOptionName() {
80+
val descriptors = listOf(ValidClass.serializer().descriptor)
81+
assertFailsWith(IllegalArgumentException::class) {
82+
ProtoBufSchemaGenerator.generateSchemaText(
83+
descriptors,
84+
options = mapOf("broken name" to "value")
85+
)
86+
}
87+
}
88+
89+
@Test
90+
fun testIllegalPackageNames() {
91+
val descriptors = listOf(ValidClass.serializer().descriptor)
92+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "") }
93+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, ".") }
94+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, ".first.dot") }
95+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "ended.with.dot.") }
96+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "first._underscore") }
97+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "first.1digit") }
98+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "illegal.sym+bol") }
99+
}
100+
101+
@Test
102+
fun testValidPackageNames() {
103+
val descriptors = listOf(ValidClass.serializer().descriptor)
104+
ProtoBufSchemaGenerator.generateSchemaText(descriptors, "singleIdent")
105+
ProtoBufSchemaGenerator.generateSchemaText(descriptors, "double.ident")
106+
ProtoBufSchemaGenerator.generateSchemaText(descriptors, "with.digits0123")
107+
ProtoBufSchemaGenerator.generateSchemaText(descriptors, "with.underscore_")
108+
}
109+
110+
@Test
111+
fun testFieldNumberDuplicates() {
112+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(listOf(FieldNumberDuplicates.serializer().descriptor)) }
113+
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(listOf(FieldNumberImplicitlyDuplicates.serializer().descriptor)) }
114+
}
115+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
syntax = "proto2";
2+
3+
package kotlinx.serialization.protobuf.schema.generator;
4+
5+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.AbstractHolder'
6+
message AbstractHolder {
7+
required KotlinxSerializationPolymorphic abs = 1;
8+
}
9+
10+
// This message was generated to support polymorphic types and does not present in Kotlin.
11+
message KotlinxSerializationPolymorphic {
12+
required string type = 1;
13+
required bytes value = 2;
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
syntax = "proto2";
2+
3+
package kotlinx.serialization.protobuf.schema.generator;
4+
5+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.ContextualHolder'
6+
message ContextualHolder {
7+
required bytes value = 1;
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
syntax = "proto2";
2+
3+
package kotlinx.serialization.protobuf.schema.generator;
4+
5+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.FieldNumberClass'
6+
message FieldNumberClass {
7+
required int32 a = 1;
8+
required int32 b = 5;
9+
required int32 c = 3;
10+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
syntax = "proto2";
2+
3+
package kotlinx.serialization.protobuf.schema.generator;
4+
5+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.LegacyMapHolder'
6+
message LegacyMapHolder {
7+
repeated LegacyMapHolder_keyAsMessage keyAsMessage = 1;
8+
repeated LegacyMapHolder_keyAsEnum keyAsEnum = 2;
9+
repeated LegacyMapHolder_keyAsBytes keyAsBytes = 3;
10+
repeated LegacyMapHolder_keyAsList keyAsList = 4;
11+
repeated LegacyMapHolder_keyAsDeepList keyAsDeepList = 5;
12+
repeated LegacyMapHolder_nullableKeyAndValue nullableKeyAndValue = 6;
13+
}
14+
15+
// This message was generated to support legacy map and does not present in Kotlin.
16+
// Containing message 'LegacyMapHolder', field 'keyAsMessage'
17+
message LegacyMapHolder_keyAsMessage {
18+
required OptionsClass key = 1;
19+
required int32 value = 2;
20+
}
21+
22+
// This message was generated to support legacy map and does not present in Kotlin.
23+
// Containing message 'LegacyMapHolder', field 'keyAsEnum'
24+
message LegacyMapHolder_keyAsEnum {
25+
required OverriddenEnumName key = 1;
26+
required OptionsClass value = 2;
27+
}
28+
29+
// This message was generated to support legacy map and does not present in Kotlin.
30+
// Containing message 'LegacyMapHolder', field 'keyAsBytes'
31+
message LegacyMapHolder_keyAsBytes {
32+
required bytes key = 1;
33+
required bytes value = 2;
34+
}
35+
36+
// This message was generated to support legacy map and does not present in Kotlin.
37+
// Containing message 'LegacyMapHolder', field 'keyAsList'
38+
message LegacyMapHolder_keyAsList {
39+
repeated int32 key = 1;
40+
required bytes value = 2;
41+
}
42+
43+
// This message was generated to support legacy map and does not present in Kotlin.
44+
// Containing message 'LegacyMapHolder', field 'keyAsDeepList'
45+
message LegacyMapHolder_keyAsDeepList {
46+
repeated LegacyMapHolder_keyAsDeepList_key key = 1;
47+
required bytes value = 2;
48+
}
49+
50+
// This message was generated to support legacy map and does not present in Kotlin.
51+
// Containing message 'LegacyMapHolder', field 'nullableKeyAndValue'
52+
message LegacyMapHolder_nullableKeyAndValue {
53+
required OptionsClass key = 1;
54+
required OptionsClass value = 2;
55+
}
56+
57+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionsClass'
58+
message OptionsClass {
59+
required int32 i = 1;
60+
}
61+
62+
enum OverriddenEnumName {
63+
FIRST = 0;
64+
OverriddenElementName = 1;
65+
}
66+
67+
// This message was generated to support nested collection in list and does not present in Kotlin.
68+
// Containing message 'LegacyMapHolder', field 'keyAsDeepList'
69+
message LegacyMapHolder_keyAsDeepList_key {
70+
repeated int32 value = 1;
71+
}
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.ListClass'
6+
message ListClass {
7+
repeated int32 intList = 1;
8+
repeated int32 intArray = 2;
9+
// WARNING: nullable elements of collections can not be represented in protobuf
10+
repeated int32 boxedIntArray = 3;
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+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
syntax = "proto2";
2+
3+
package kotlinx.serialization.protobuf.schema.generator;
4+
5+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.MapClass'
6+
message MapClass {
7+
map<int32, float> scalarMap = 1;
8+
map<int32, bytes> bytesMap = 2;
9+
map<string, OptionsClass> messageMap = 3;
10+
map<bool, OverriddenEnumName> enumMap = 4;
11+
}
12+
13+
// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionsClass'
14+
message OptionsClass {
15+
required int32 i = 1;
16+
}
17+
18+
enum OverriddenEnumName {
19+
FIRST = 0;
20+
OverriddenElementName = 1;
21+
}

0 commit comments

Comments
 (0)