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 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
Next Next commit
ProtoName is now supported, allowing the overriding of the field name…
… for ProtoBuf only.
  • Loading branch information
UnknownJoe796 committed Nov 4, 2024
commit 693ea69b5d79e97edb4d699c0bc90c91f0c7ff61
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 @@ -182,7 +182,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 +216,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 +392,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 +418,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 +462,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 +484,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 +562,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 +587,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