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

ProtoName is now supported, allowing the overriding of the field name for ProtoBuf only. #2847

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ package kotlinx.serialization.protobuf
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*

/**
* Specifies protobuf field name assigned to a Kotlin property or class.
*
* Used to get around invalid naming conventions.
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
@ExperimentalSerializationApi
public annotation class ProtoName(public val name: String)

/**
* Specifies protobuf field number (a unique number for a field in the protobuf message)
* assigned to a Kotlin property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import kotlinx.serialization.protobuf.internal.*
* An arbitrary Kotlin class represent much wider object domain than the ProtoBuf specification, thus schema generator
* has the following list of restrictions:
*
* * Serial name of the class and all its fields should be a valid Proto [identifier](https://developers.google.com/protocol-buffers/docs/reference/proto2-spec)
* * Serial name of the class and all its fields should be a valid Proto [identifier](https://developers.google.com/protocol-buffers/docs/reference/proto2-spec).
* If this is a problem for you, you many override serial names for ProtoBuf only by using the [ProtoName] annotation for specific fields or classes.
* * Nullable values are allowed only for Kotlin [nullable][SerialDescriptor.isNullable] types, but not [optional][SerialDescriptor.isElementOptional]
* in order to properly distinguish "default" and "absent" values.
* * The name of the type without the package directive uniquely identifies the proto message type and two or more fields with the same serial name
Expand Down Expand Up @@ -182,7 +183,7 @@ public object ProtoBufSchemaGenerator {
getAnnotations: (Int) -> List<Annotation> = { parentType.descriptor.getElementAnnotations(it) },
getChildType: (Int) -> TypeDefinition = { parentType.descriptor.getElementDescriptor(it).let(::TypeDefinition) },
getChildNumber: (Int) -> Int = { parentType.descriptor.getElementAnnotations(it).filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: (it + 1) },
getChildName: (Int) -> String = { parentType.descriptor.getElementName(it) },
getChildName: (Int) -> String = { parentType.descriptor.getElementNameProtobuf(it) },
inOneOfStruct: Boolean = false,
) {
val messageDescriptor = parentType.descriptor
Expand Down Expand Up @@ -216,7 +217,7 @@ public object ProtoBufSchemaGenerator {
getAnnotations = { desc.annotations },
getChildType = { desc.elementDescriptors.single().let(::TypeDefinition) },
getChildNumber = { desc.getElementAnnotations(0).filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: (it + 1) },
getChildName = { desc.getElementName(0) },
getChildName = { desc.getElementNameProtobuf(0) },
inOneOfStruct = true,
)
}
Expand Down Expand Up @@ -392,13 +393,15 @@ public object ProtoBufSchemaGenerator {
val usedNumbers: MutableSet<Int> = mutableSetOf()
val duplicatedNumbers: MutableSet<Int> = mutableSetOf()
enumDescriptor.elementDescriptors.forEachIndexed { index, element ->
val elementName = element.protobufEnumElementName
val annotations = enumDescriptor.getElementAnnotations(index)

val elementName = annotations.filterIsInstance<ProtoName>().singleOrNull()?.name
?: element.serialName.substringAfterLast('.', element.serialName)
elementName.checkIsValidIdentifier {
"The enum element name '$elementName' is invalid in the " +
"protobuf schema. Serial name of the enum class '${enumDescriptor.serialName}'"
"protobuf schema. Serial name of the enum class '${enumDescriptor.serialName}'"
}

val annotations = enumDescriptor.getElementAnnotations(index)
val number = annotations.filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: index
if (!usedNumbers.add(number)) {
duplicatedNumbers.add(number)
Expand All @@ -416,6 +419,13 @@ public object ProtoBufSchemaGenerator {
appendLine('}')
}

private fun SerialDescriptor.getElementNameProtobuf(index: Int): String {
getElementAnnotations(index).forEach {
if(it is ProtoName) return it.name
}
return getElementName(index)
}

private val SerialDescriptor.isOpenPolymorphic: Boolean
get() = kind == PolymorphicKind.OPEN

Expand Down Expand Up @@ -453,8 +463,16 @@ public object ProtoBufSchemaGenerator {
get() = kind == PrimitiveKind.INT || kind == PrimitiveKind.LONG || kind == PrimitiveKind.BOOLEAN || kind == PrimitiveKind.STRING


@Suppress("RecursivePropertyAccessor")
private val SerialDescriptor.messageOrEnumName: String
get() = (serialName.substringAfterLast('.', serialName)).removeSuffix("?")
get() {
// Try to get the original descriptor to retrieve the ProtoName
if (this is NotNullSerialDescriptor) return this.original.messageOrEnumName
if (nonNullOriginal != this) return nonNullOriginal.messageOrEnumName

annotations.forEach { if(it is ProtoName) return it.name }
return serialName.substringAfterLast('.', serialName).removeSuffix("?")
}

private fun SerialDescriptor.isChildOneOfMessage(index: Int): Boolean =
this.getElementDescriptor(index).isSealedPolymorphic && this.getElementAnnotations(index).any { it is ProtoOneOf }
Expand All @@ -467,9 +485,6 @@ public object ProtoBufSchemaGenerator {
}
}

private val SerialDescriptor.protobufEnumElementName: String
get() = serialName.substringAfterLast('.', serialName)

private fun SerialDescriptor.scalarTypeName(annotations: List<Annotation> = emptyList()): String {
val integerType = annotations.filterIsInstance<ProtoType>().firstOrNull()?.type ?: ProtoIntegerType.DEFAULT

Expand Down Expand Up @@ -548,7 +563,7 @@ public object ProtoBufSchemaGenerator {
): TypeDefinition {
val messageDescriptor = messageType.descriptor
val fieldDescriptor = messageDescriptor.getElementDescriptor(index)
val fieldName = messageDescriptor.getElementName(index)
val fieldName = messageDescriptor.getElementNameProtobuf(index)
val messageName = messageDescriptor.messageOrEnumName

val wrapperName = "${messageName}_${fieldName}"
Expand All @@ -573,7 +588,7 @@ public object ProtoBufSchemaGenerator {
description: String
): TypeDefinition {
val messageDescriptor = messageType.descriptor
val fieldName = messageDescriptor.getElementName(index)
val fieldName = messageDescriptor.getElementNameProtobuf(index)
val messageName = messageDescriptor.messageOrEnumName

val wrapperName = "${messageName}_${fieldName}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ class SchemaValidationsTest {
@SerialName("invalid serial name")
data class InvalidClassName(val i: Int)

@Serializable
@SerialName("invalid serial name")
@ProtoName("ValidSerialName")
data class InvalidClassNameFixed(val i: Int)

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

@Serializable
data class InvalidClassFieldNameFixed(@ProtoName("okProtoName") @SerialName("invalid serial name") val i: Int)

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

Expand All @@ -30,6 +38,11 @@ class SchemaValidationsTest {
@SerialName("invalid serial name")
enum class InvalidEnumName { SINGLETON }

@Serializable
@SerialName("invalid serial name")
@ProtoName("ValidEnumNameFixed")
enum class InvalidEnumNameFixed { SINGLETON }

@Serializable
enum class InvalidEnumElementName {
FIRST,
Expand All @@ -38,6 +51,15 @@ class SchemaValidationsTest {
SECOND
}

@Serializable
enum class InvalidEnumElementNameFixed {
FIRST,

@SerialName("invalid serial name")
@ProtoName("validSerialName")
SECOND
}

@Serializable
enum class EnumWithExplicitProtoNumberDuplicate {
@ProtoNumber(2)
Expand Down Expand Up @@ -71,18 +93,36 @@ class SchemaValidationsTest {
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
}

@Test
fun testInvalidEnumElementSerialNameFixed() {
val descriptors = listOf(InvalidEnumElementNameFixed.serializer().descriptor)
println(ProtoBufSchemaGenerator.generateSchemaText(descriptors))
}

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

@Test
fun testInvalidClassSerialNameFixed() {
val descriptors = listOf(InvalidClassNameFixed.serializer().descriptor)
println(ProtoBufSchemaGenerator.generateSchemaText(descriptors))
}

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

@Test
fun testInvalidClassFieldSerialNameFixed() {
val descriptors = listOf(InvalidClassFieldNameFixed.serializer().descriptor)
println(ProtoBufSchemaGenerator.generateSchemaText(descriptors))
}

@Test
fun testDuplicateSerialNames() {
val descriptors = listOf(InvalidClassFieldName.serializer().descriptor)
Expand All @@ -95,6 +135,12 @@ class SchemaValidationsTest {
assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
}

@Test
fun testInvalidEnumSerialNameFixed() {
val descriptors = listOf(InvalidEnumNameFixed.serializer().descriptor)
println(ProtoBufSchemaGenerator.generateSchemaText(descriptors))
}

@Test
fun testDuplicationSerialName() {
val descriptors = listOf(ValidClass.serializer().descriptor, DuplicateClass.serializer().descriptor)
Expand Down