Skip to content

Commit

Permalink
Support serializing nulls for a property of a parameterized type with…
Browse files Browse the repository at this point in the history
… a nullable upper bound but break some existing tests

This is done by setting `nullableMode` depending on `serializer.descriptor.isNullable` in `encodeSerializableElement`.

Some common test functions are extracted to `TestFunctions.kt` to eliminate duplicate code.

This breaks some existing tests in `ProtobufCollectionsTest`.
  • Loading branch information
ShreckYe committed Feb 3, 2024
1 parent 41c0bb1 commit 16fc6de
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,16 @@ internal abstract class ProtobufTaggedEncoder : ProtobufTaggedBase(), Encoder, C
serializer: SerializationStrategy<T>,
value: T
) {
nullableMode = NullableMode.NOT_NULL
val elementKind = descriptor.getElementDescriptor(index).kind
nullableMode =
if (descriptor.isElementOptional(index)) {
NullableMode.OPTIONAL
} else if (elementKind == StructureKind.MAP || elementKind == StructureKind.LIST) {
NullableMode.COLLECTION
} else if (serializer.descriptor.isNullable) // or: descriptor.getElementDescriptor(index)
NullableMode.ACCEPTABLE
else
NullableMode.NOT_NULL

pushTag(descriptor.getTag(index))
encodeSerializableValue(serializer, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,23 @@
package kotlinx.serialization.protobuf

import kotlinx.serialization.*
import kotlinx.serialization.test.*
import kotlin.test.*

class ProtobufNothingTest {
@Serializable
/*private*/ data class NullableNothingBox(val value: Nothing?) // `private` doesn't work on the JS legacy target

@Serializable
private data class ParameterizedBox<T : Any>(val value: T?)
private data class NullablePropertyNotNullUpperBoundParameterizedBox<T : Any>(val value: T?)

@Serializable
private data class NullableUpperBoundParameterizedBox<T : Any?>(val value: T)

private inline fun <reified T> testConversion(data: T, expectedHexString: String) {
val string = ProtoBuf.encodeToHexString(data).uppercase()
assertEquals(expectedHexString, string)
assertEquals(data, ProtoBuf.decodeFromHexString(string))
}

@Test
fun testNothing() {
testConversion(NullableNothingBox(null), "")
testConversion(ParameterizedBox(null), "")
testConversion(NullablePropertyNotNullUpperBoundParameterizedBox(null), "")
testConversion(NullableUpperBoundParameterizedBox(null), "")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,10 @@
*/
package kotlinx.serialization.protobuf

import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlin.test.*

class ProtobufPrimitivesTest {

private fun <T> testConversion(data: T, serializer: KSerializer<T>, expectedHexString: String) {
val string = ProtoBuf.encodeToHexString(serializer, data).uppercase()
assertEquals(expectedHexString, string)
assertEquals(data, ProtoBuf.decodeFromHexString(serializer, string))
}

@Test
fun testPrimitiveTypes() {
testConversion(true, Boolean.serializer(), "01")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.protobuf

import kotlinx.serialization.*
import kotlin.test.*

class ProtobufTypeParameterTest {
@Serializable
data class Box<T>(val value: T)

@Serializable
data class ExplicitNullableUpperBoundBox<T : Any?>(val value: T)

@Serializable
data class ExplicitNullableUpperNullablePropertyBoundBox<T : Any?>(val value: T?)

inline fun <reified T> testBox(value: T, expectedHexString: String) {
testConversion(Box(value), expectedHexString)
testConversion(ExplicitNullableUpperBoundBox(value), expectedHexString)
testConversion(ExplicitNullableUpperNullablePropertyBoundBox(value), expectedHexString)
}

@Serializable
private data class DefaultArgPair<T>(val first: T, val second: T = first)

companion object {
val testList0 = emptyList<Int>()
val testList1 = listOf(0)
val testMap0 = emptyMap<Int, Int>()
val testMap1 = mapOf(0 to 0)
}


@Test
fun testNothingBoxesWithNull() {
// Cannot use 'Nothing?' as reified type parameter
//testBox(null, "")
testConversion(Box(null), "")
testConversion(ExplicitNullableUpperBoundBox(null), "")
@Suppress("RemoveExplicitTypeArguments")
testConversion(ExplicitNullableUpperNullablePropertyBoundBox<Nothing>(null), "")
testConversion(ExplicitNullableUpperNullablePropertyBoundBox<Nothing?>(null), "")
}

@Test
fun testIntBoxes() {
testBox(0, "0800")
testBox(1, "0801")
}

@Test
fun testNullableIntBoxes() {
testBox<Int?>(null, "")
testBox<Int?>(0, "0800")
}

@Test
fun testCollectionBoxes() {
testBox(testList0, "")
testBox(testList1, "0800")
testBox(testMap0, "")
testBox(testMap1, "0A0408001000")
}

@Test
fun testNullableCollectionBoxes() {
fun assertFailsForNullForCollectionTypes(block: () -> Unit) {
try {
block()
fail()
} catch (e: SerializationException) {
assertEquals(
"'null' is not supported for collection types in ProtoBuf", e.message
)
}
}
assertFailsForNullForCollectionTypes {
testBox<List<Int>?>(null, "")
}
assertFailsForNullForCollectionTypes {
testBox<Map<Int, Int>?>(null, "")
}
testBox<List<Int>?>(testList0, "")
testBox<Map<Int, Int>?>(testMap0, "")
}

@Test
fun testWithDefaultArguments() {
testConversion(DefaultArgPair(null), "")
testConversion(DefaultArgPair(1), "0801")
testConversion(DefaultArgPair(null, null), "")
testConversion(DefaultArgPair(null, 1), "1001")
assertFailsWith<SerializationException> {
testConversion(DefaultArgPair(1, null), "0801")
}
testConversion(DefaultArgPair(1, 1), "0801")
testConversion(DefaultArgPair(1, 2), "08011002")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.protobuf

import kotlinx.serialization.*
import kotlin.test.*

fun <T> testConversion(data: T, serializer: KSerializer<T>, expectedHexString: String) {
val string = ProtoBuf.encodeToHexString(serializer, data).uppercase()
assertEquals(expectedHexString, string)
assertEquals(data, ProtoBuf.decodeFromHexString(serializer, string))
}

inline fun <reified T> testConversion(data: T, expectedHexString: String) {
val string = ProtoBuf.encodeToHexString(data).uppercase()
assertEquals(expectedHexString, string)
assertEquals(data, ProtoBuf.decodeFromHexString(string))
}

0 comments on commit 16fc6de

Please sign in to comment.