Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generator for .proto files based on serializable Kotlin classes #1255

Merged
merged 6 commits into from
Apr 26, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 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,8 @@ 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/GenerationKt {
public static final fun generateProto2Schema (Ljava/util/List;Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String;
public static synthetic fun generateProto2Schema$default (Ljava/util/List;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,104 @@
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) { generateProto2Schema(descriptors) }
}

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

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

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

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

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

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

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

@Test
fun testFieldNumberDuplicates() {
assertFailsWith(IllegalArgumentException::class) { generateProto2Schema(listOf(FieldNumberDuplicates.serializer().descriptor)) }
assertFailsWith(IllegalArgumentException::class) { generateProto2Schema(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;
}

// serial name 'KotlinxSerializationPolymorphic'
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;
}
59 changes: 59 additions & 0 deletions formats/protobuf/jvmTest/resources/LegacyMapHolder.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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;
}

// serial name 'LegacyMapHolder_keyAsMessage'
message LegacyMapHolder_keyAsMessage {
required OptionsClass key = 1;
required int32 value = 2;
}

// serial name 'LegacyMapHolder_keyAsEnum'
message LegacyMapHolder_keyAsEnum {
required OverriddenEnumName key = 1;
required OptionsClass value = 2;
}

// serial name 'LegacyMapHolder_keyAsBytes'
message LegacyMapHolder_keyAsBytes {
required bytes key = 1;
required bytes value = 2;
}

// serial name 'LegacyMapHolder_keyAsList'
message LegacyMapHolder_keyAsList {
repeated int32 key = 1;
required bytes value = 2;
}

// serial name 'LegacyMapHolder_keyAsDeepList'
message LegacyMapHolder_keyAsDeepList {
repeated LegacyMapHolder_keyAsDeepList_key key = 1;
required bytes value = 2;
}

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

// serial name 'OverriddenEnumName'
enum OverriddenEnumName {
FIRST = 0;
OverriddenElementName = 1;
}


// serial name 'LegacyMapHolder_keyAsDeepList_key'
message LegacyMapHolder_keyAsDeepList_key {
repeated int32 value = 1;
}
24 changes: 24 additions & 0 deletions formats/protobuf/jvmTest/resources/ListClass.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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: null value is not supported for list elements
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;
}

// serial name 'OverriddenEnumName'
enum OverriddenEnumName {
FIRST = 0;
OverriddenElementName = 1;
}
22 changes: 22 additions & 0 deletions formats/protobuf/jvmTest/resources/MapClass.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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;
}

// serial name 'OverriddenEnumName'
enum OverriddenEnumName {
FIRST = 0;
OverriddenElementName = 1;
}
37 changes: 37 additions & 0 deletions formats/protobuf/jvmTest/resources/NestedCollections.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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;
}

// serial name 'NestedCollections_intList'
message NestedCollections_intList {
repeated int32 value = 1;
}

// serial name 'NestedCollections_messageList'
message NestedCollections_messageList {
repeated OptionsClass value = 1;
}

// serial name 'NestedCollections_mapInList'
message NestedCollections_mapInList {
map<string, OptionsClass> value = 1;
}

// serial name 'NestedCollections_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 {
// WARNING: null value is not supported for nested collections
repeated NullableNestedCollections_nullableIntList nullableIntList = 1;
// WARNING: null value is not supported for nested collections
map<string, NullableNestedCollections_nullableIntMap> nullableIntMap = 2;
map<string, NullableNestedCollections_intMap> intMap = 3;
repeated NullableNestedCollections_intList intList = 4;
repeated NullableNestedCollections_legacyMap legacyMap = 5;
}

// serial name 'NullableNestedCollections_nullableIntList'
message NullableNestedCollections_nullableIntList {
// WARNING: This field is marked as nullable but it does not support null values
repeated int32 value = 1;
}

// serial name 'NullableNestedCollections_nullableIntMap'
message NullableNestedCollections_nullableIntMap {
// WARNING: This field is marked as nullable but it does not support null values
repeated int32 value = 1;
}

// serial name 'NullableNestedCollections_intMap'
message NullableNestedCollections_intMap {
// WARNING: null value is not supported for list elements
repeated int32 value = 1;
}

// serial name 'NullableNestedCollections_intList'
message NullableNestedCollections_intList {
// WARNING: null value is not supported for list elements
repeated int32 value = 1;
}

// serial name 'NullableNestedCollections_legacyMap'
message NullableNestedCollections_legacyMap {
// WARNING: This field is marked as nullable but it does not support null values
repeated int32 key = 1;
// WARNING: This field is marked as nullable but it does not support null values
repeated int32 value = 2;
}
14 changes: 14 additions & 0 deletions formats/protobuf/jvmTest/resources/OptionalClass.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.OptionalClass'
message OptionalClass {
required int32 requiredInt = 1;
// WARNING: an absence value is decoded as a default value that is not present in the schema
optional int32 optionalInt = 2;
optional int32 nullableInt = 3;
// WARNING: this field is nullable and has default value, it's impossible to unambiguously interpret an absence value.
// For this field null value does not support and absence value denotes as default value. Default value is not present in the schema.
optional int32 nullableOptionalInt = 4;
}
25 changes: 25 additions & 0 deletions formats/protobuf/jvmTest/resources/OptionalCollections.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
syntax = "proto2";

package kotlinx.serialization.protobuf.schema.generator;

// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionalCollections'
message OptionalCollections {
repeated int32 requiredList = 1;
// WARNING: This field does not support empty list
// An absence value is decoded as a default value that is not present in the schema
repeated int32 optionalList = 2;
// WARNING: This field is marked as nullable but it does not support null values
repeated int32 nullableList = 3;
// WARNING: This field is marked as nullable and has a default value but it does not support null values and empty list
// An absence value is decoded as a default value that is not present in the schema
repeated int32 nullableOptionalList = 4;
map<int32, int32> requiredMap = 5;
// WARNING: This field does not support empty map
// An absence value is decoded as a default value that is not present in the schema
map<int32, int32> optionalMap = 6;
// WARNING: This field is marked as nullable but it does not support null values
map<int32, int32> nullableMap = 7;
// WARNING: This field is marked as nullable and has a default value but it does not support null values and empty map
// An absence value is decoded as a default value that is not present in the schema
map<int32, int32> nullableOptionalMap = 8;
}
Loading