Skip to content

Commit

Permalink
Generator for .proto files based on serializable Kotlin classes (#1255)
Browse files Browse the repository at this point in the history
Fixes #34

Co-authored-by: Vsevolod Tolstopyatov <qwwdfsad@gmail.com>
  • Loading branch information
shanshin and qwwdfsad authored Apr 26, 2021
1 parent 38adb1b commit 75566cc
Show file tree
Hide file tree
Showing 21 changed files with 1,484 additions and 1 deletion.
8 changes: 8 additions & 0 deletions formats/protobuf/api/kotlinx-serialization-protobuf.api
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,11 @@ public final class kotlinx/serialization/protobuf/ProtoType$Impl : kotlinx/seria
public final synthetic fun type ()Lkotlinx/serialization/protobuf/ProtoIntegerType;
}

public final class kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator {
public static final field INSTANCE Lkotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator;
public final fun generateSchemaText (Ljava/util/List;Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String;
public final fun generateSchemaText (Lkotlinx/serialization/descriptors/SerialDescriptor;Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String;
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;
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;
}

2 changes: 1 addition & 1 deletion formats/protobuf/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ kotlin {
}

sourceSets.test.proto {
srcDirs = ['testProto']
srcDirs = ['testProto', 'jvmTest/resources/common']
}

compileTestKotlinJvm {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package kotlinx.serialization.protobuf.schema

import kotlinx.serialization.*
import kotlinx.serialization.protobuf.ProtoNumber
import kotlin.test.Test
import kotlin.test.assertFailsWith

class SchemaValidationsTest {
@Serializable
data class ValidClass(val i: Int)

@Serializable
@SerialName("ValidClass")
data class DuplicateClass(val l: Long)

@Serializable
@SerialName("invalid serial name")
data class InvalidClassName(val i: Int)

@Serializable
data class InvalidClassFieldName(@SerialName("invalid serial name") val i: Int)

@Serializable
data class FieldNumberDuplicates(@ProtoNumber(42) val i: Int, @ProtoNumber(42) val j: Int)

@Serializable
data class FieldNumberImplicitlyDuplicates(@ProtoNumber(2) val i: Int, val j: Int)

@Serializable
@SerialName("invalid serial name")
enum class InvalidEnumName { SINGLETON }

@Serializable
enum class InvalidEnumElementName {
FIRST,

@SerialName("invalid serial name")
SECOND
}


@Test
fun testInvalidEnumElementSerialName() {
val descriptors = listOf(InvalidEnumElementName.serializer().descriptor)
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
}

@Test
fun testInvalidClassSerialName() {
val descriptors = listOf(InvalidClassName.serializer().descriptor)
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
}

@Test
fun testInvalidClassFieldSerialName() {
val descriptors = listOf(InvalidClassFieldName.serializer().descriptor)
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
}

@Test
fun testDuplicateSerialNames() {
val descriptors = listOf(InvalidClassFieldName.serializer().descriptor)
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
}

@Test
fun testInvalidEnumSerialName() {
val descriptors = listOf(InvalidEnumName.serializer().descriptor)
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
}

@Test
fun testDuplicationSerialName() {
val descriptors = listOf(ValidClass.serializer().descriptor, DuplicateClass.serializer().descriptor)
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
}

@Test
fun testInvalidOptionName() {
val descriptors = listOf(ValidClass.serializer().descriptor)
assertFailsWith(IllegalArgumentException::class) {
ProtoBufSchemaGenerator.generateSchemaText(
descriptors,
options = mapOf("broken name" to "value")
)
}
}

@Test
fun testIllegalPackageNames() {
val descriptors = listOf(ValidClass.serializer().descriptor)
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "") }
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, ".") }
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, ".first.dot") }
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "ended.with.dot.") }
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "first._underscore") }
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "first.1digit") }
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors, "illegal.sym+bol") }
}

@Test
fun testValidPackageNames() {
val descriptors = listOf(ValidClass.serializer().descriptor)
ProtoBufSchemaGenerator.generateSchemaText(descriptors, "singleIdent")
ProtoBufSchemaGenerator.generateSchemaText(descriptors, "double.ident")
ProtoBufSchemaGenerator.generateSchemaText(descriptors, "with.digits0123")
ProtoBufSchemaGenerator.generateSchemaText(descriptors, "with.underscore_")
}

@Test
fun testFieldNumberDuplicates() {
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(listOf(FieldNumberDuplicates.serializer().descriptor)) }
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(listOf(FieldNumberImplicitlyDuplicates.serializer().descriptor)) }
}
}
14 changes: 14 additions & 0 deletions formats/protobuf/jvmTest/resources/AbstractHolder.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.AbstractHolder'
message AbstractHolder {
required KotlinxSerializationPolymorphic abs = 1;
}

// This message was generated to support polymorphic types and does not present in Kotlin.
message KotlinxSerializationPolymorphic {
required string type = 1;
required bytes value = 2;
}
8 changes: 8 additions & 0 deletions formats/protobuf/jvmTest/resources/ContextualHolder.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.ContextualHolder'
message ContextualHolder {
required bytes value = 1;
}
10 changes: 10 additions & 0 deletions formats/protobuf/jvmTest/resources/FieldNumberClass.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.FieldNumberClass'
message FieldNumberClass {
required int32 a = 1;
required int32 b = 5;
required int32 c = 3;
}
71 changes: 71 additions & 0 deletions formats/protobuf/jvmTest/resources/LegacyMapHolder.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.LegacyMapHolder'
message LegacyMapHolder {
repeated LegacyMapHolder_keyAsMessage keyAsMessage = 1;
repeated LegacyMapHolder_keyAsEnum keyAsEnum = 2;
repeated LegacyMapHolder_keyAsBytes keyAsBytes = 3;
repeated LegacyMapHolder_keyAsList keyAsList = 4;
repeated LegacyMapHolder_keyAsDeepList keyAsDeepList = 5;
repeated LegacyMapHolder_nullableKeyAndValue nullableKeyAndValue = 6;
}

// This message was generated to support legacy map and does not present in Kotlin.
// Containing message 'LegacyMapHolder', field 'keyAsMessage'
message LegacyMapHolder_keyAsMessage {
required OptionsClass key = 1;
required int32 value = 2;
}

// This message was generated to support legacy map and does not present in Kotlin.
// Containing message 'LegacyMapHolder', field 'keyAsEnum'
message LegacyMapHolder_keyAsEnum {
required OverriddenEnumName key = 1;
required OptionsClass value = 2;
}

// This message was generated to support legacy map and does not present in Kotlin.
// Containing message 'LegacyMapHolder', field 'keyAsBytes'
message LegacyMapHolder_keyAsBytes {
required bytes key = 1;
required bytes value = 2;
}

// This message was generated to support legacy map and does not present in Kotlin.
// Containing message 'LegacyMapHolder', field 'keyAsList'
message LegacyMapHolder_keyAsList {
repeated int32 key = 1;
required bytes value = 2;
}

// This message was generated to support legacy map and does not present in Kotlin.
// Containing message 'LegacyMapHolder', field 'keyAsDeepList'
message LegacyMapHolder_keyAsDeepList {
repeated LegacyMapHolder_keyAsDeepList_key key = 1;
required bytes value = 2;
}

// This message was generated to support legacy map and does not present in Kotlin.
// Containing message 'LegacyMapHolder', field 'nullableKeyAndValue'
message LegacyMapHolder_nullableKeyAndValue {
required OptionsClass key = 1;
required OptionsClass value = 2;
}

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionsClass'
message OptionsClass {
required int32 i = 1;
}

enum OverriddenEnumName {
FIRST = 0;
OverriddenElementName = 1;
}

// This message was generated to support nested collection in list and does not present in Kotlin.
// Containing message 'LegacyMapHolder', field 'keyAsDeepList'
message LegacyMapHolder_keyAsDeepList_key {
repeated int32 value = 1;
}
23 changes: 23 additions & 0 deletions formats/protobuf/jvmTest/resources/ListClass.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.ListClass'
message ListClass {
repeated int32 intList = 1;
repeated int32 intArray = 2;
// WARNING: nullable elements of collections can not be represented in protobuf
repeated int32 boxedIntArray = 3;
repeated OptionsClass messageList = 4;
repeated OverriddenEnumName enumList = 5;
}

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionsClass'
message OptionsClass {
required int32 i = 1;
}

enum OverriddenEnumName {
FIRST = 0;
OverriddenElementName = 1;
}
21 changes: 21 additions & 0 deletions formats/protobuf/jvmTest/resources/MapClass.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.MapClass'
message MapClass {
map<int32, float> scalarMap = 1;
map<int32, bytes> bytesMap = 2;
map<string, OptionsClass> messageMap = 3;
map<bool, OverriddenEnumName> enumMap = 4;
}

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionsClass'
message OptionsClass {
required int32 i = 1;
}

enum OverriddenEnumName {
FIRST = 0;
OverriddenElementName = 1;
}
40 changes: 40 additions & 0 deletions formats/protobuf/jvmTest/resources/NestedCollections.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.NestedCollections'
message NestedCollections {
repeated NestedCollections_intList intList = 1;
repeated NestedCollections_messageList messageList = 2;
repeated NestedCollections_mapInList mapInList = 3;
map<string, NestedCollections_listInMap> listInMap = 4;
}

// This message was generated to support nested collection in list and does not present in Kotlin.
// Containing message 'NestedCollections', field 'intList'
message NestedCollections_intList {
repeated int32 value = 1;
}

// This message was generated to support nested collection in list and does not present in Kotlin.
// Containing message 'NestedCollections', field 'messageList'
message NestedCollections_messageList {
repeated OptionsClass value = 1;
}

// This message was generated to support nested collection in list and does not present in Kotlin.
// Containing message 'NestedCollections', field 'mapInList'
message NestedCollections_mapInList {
map<string, OptionsClass> value = 1;
}

// This message was generated to support nested collection in map value and does not present in Kotlin.
// Containing message 'NestedCollections', field 'listInMap'
message NestedCollections_listInMap {
repeated int32 value = 1;
}

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionsClass'
message OptionsClass {
required int32 i = 1;
}
46 changes: 46 additions & 0 deletions formats/protobuf/jvmTest/resources/NullableNestedCollections.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.NullableNestedCollections'
message NullableNestedCollections {
repeated NullableNestedCollections_nullableIntList nullableIntList = 1;
// WARNING: nullable map values can not be represented in protobuf
map<string, NullableNestedCollections_nullableIntMap> nullableIntMap = 2;
map<string, NullableNestedCollections_intMap> intMap = 3;
repeated NullableNestedCollections_intList intList = 4;
repeated NullableNestedCollections_legacyMap legacyMap = 5;
}

// This message was generated to support nested collection in list and does not present in Kotlin.
// Containing message 'NullableNestedCollections', field 'nullableIntList'
message NullableNestedCollections_nullableIntList {
repeated int32 value = 1;
}

// This message was generated to support nested collection in map value and does not present in Kotlin.
// Containing message 'NullableNestedCollections', field 'nullableIntMap'
message NullableNestedCollections_nullableIntMap {
repeated int32 value = 1;
}

// This message was generated to support nested collection in map value and does not present in Kotlin.
// Containing message 'NullableNestedCollections', field 'intMap'
message NullableNestedCollections_intMap {
// WARNING: nullable elements of collections can not be represented in protobuf
repeated int32 value = 1;
}

// This message was generated to support nested collection in list and does not present in Kotlin.
// Containing message 'NullableNestedCollections', field 'intList'
message NullableNestedCollections_intList {
// WARNING: nullable elements of collections can not be represented in protobuf
repeated int32 value = 1;
}

// This message was generated to support legacy map and does not present in Kotlin.
// Containing message 'NullableNestedCollections', field 'legacyMap'
message NullableNestedCollections_legacyMap {
repeated int32 key = 1;
repeated int32 value = 2;
}
13 changes: 13 additions & 0 deletions formats/protobuf/jvmTest/resources/OptionalClass.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionalClass'
message OptionalClass {
required int32 requiredInt = 1;
// WARNING: a default value decoded when value is missing
optional int32 optionalInt = 2;
optional int32 nullableInt = 3;
// WARNING: a default value decoded when value is missing
optional int32 nullableOptionalInt = 4;
}
Loading

0 comments on commit 75566cc

Please sign in to comment.