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

Prepare fix for contextual deserialization of polymorphic scalars #618

Merged
merged 1 commit into from
Oct 29, 2024
Merged
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 @@ -24,7 +24,11 @@ import kotlinx.serialization.modules.SerializersModule

internal class YamlContextualInput(node: YamlNode, yaml: Yaml, context: SerializersModule, configuration: YamlConfiguration) : YamlInput(node, yaml, context, configuration) {
override fun decodeElementIndex(descriptor: SerialDescriptor): Int = throw IllegalStateException("Must call beginStructure() and use returned Decoder")
override fun decodeValue(): Any = throw IllegalStateException("Must call beginStructure() and use returned Decoder")
override fun decodeValue(): Any = when (node) {
is YamlScalar -> node.content
is YamlNull -> throw UnexpectedNullValueException(node.path)
else -> throw IllegalStateException("Must call beginStructure() and use returned Decoder")
}

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
createFor(node, yaml, serializersModule, configuration, descriptor)
Expand Down
32 changes: 24 additions & 8 deletions src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,20 @@ class YamlReadingTest : FlatFunSpec({
}
}

context("given some input for an object where the property value should be a sealed class (inline)") {
val input = """
element: !<inlineString> "abcdef"
""".trimIndent()

context("parsing that input") {
val result = polymorphicYaml.decodeFromString(SealedWrapper.serializer(), input)

test("deserializes it to a Kotlin object") {
result shouldBe SealedWrapper(TestSealedStructure.InlineSealedString("abcdef"))
}
}
}

context("given some input for an object where the property value is a literal") {
val input = """
test: !<simpleInt> 42
Expand All @@ -1492,6 +1506,7 @@ class YamlReadingTest : FlatFunSpec({
value: -987
- !<sealedInt>
value: 654
- !<inlineString> "testing"
- !<sealedString>
value: "tests"
""".trimIndent()
Expand All @@ -1505,6 +1520,7 @@ class YamlReadingTest : FlatFunSpec({
TestSealedStructure.SimpleSealedString(null),
TestSealedStructure.SimpleSealedInt(-987),
TestSealedStructure.SimpleSealedInt(654),
TestSealedStructure.InlineSealedString("testing"),
TestSealedStructure.SimpleSealedString("tests"),
)
}
Expand Down Expand Up @@ -1603,11 +1619,11 @@ class YamlReadingTest : FlatFunSpec({
val exception = shouldThrow<UnknownPolymorphicTypeException> { polymorphicYaml.decodeFromString(TestSealedStructure.serializer(), input) }

exception.asClue {
it.message shouldBe "Unknown type 'someOtherType'. Known types are: sealedInt, sealedString"
it.message shouldBe "Unknown type 'someOtherType'. Known types are: inlineString, sealedInt, sealedString"
it.line shouldBe 1
it.column shouldBe 1
it.typeName shouldBe "someOtherType"
it.validTypeNames shouldBe setOf("sealedInt", "sealedString")
it.validTypeNames shouldBe setOf("inlineString", "sealedInt", "sealedString")
it.path shouldBe YamlPath.root
}
}
Expand All @@ -1624,11 +1640,11 @@ class YamlReadingTest : FlatFunSpec({
val exception = shouldThrow<UnknownPolymorphicTypeException> { polymorphicYaml.decodeFromString(TestSealedStructure.serializer(), input) }

exception.asClue {
it.message shouldBe "Unknown type 'someOtherType'. Known types are: sealedInt, sealedString"
it.message shouldBe "Unknown type 'someOtherType'. Known types are: inlineString, sealedInt, sealedString"
it.line shouldBe 1
it.column shouldBe 1
it.typeName shouldBe "someOtherType"
it.validTypeNames shouldBe setOf("sealedInt", "sealedString")
it.validTypeNames shouldBe setOf("inlineString", "sealedInt", "sealedString")
it.path shouldBe YamlPath.root
}
}
Expand Down Expand Up @@ -1818,11 +1834,11 @@ class YamlReadingTest : FlatFunSpec({
val exception = shouldThrow<UnknownPolymorphicTypeException> { polymorphicYaml.decodeFromString(TestSealedStructure.serializer(), input) }

exception.asClue {
it.message shouldBe "Unknown type 'someOtherType'. Known types are: sealedInt, sealedString"
it.message shouldBe "Unknown type 'someOtherType'. Known types are: inlineString, sealedInt, sealedString"
it.line shouldBe 1
it.column shouldBe 7
it.typeName shouldBe "someOtherType"
it.validTypeNames shouldBe setOf("sealedInt", "sealedString")
it.validTypeNames shouldBe setOf("inlineString", "sealedInt", "sealedString")
it.path shouldBe YamlPath.root.withMapElementKey("type", Location(1, 1)).withMapElementValue(Location(1, 7))
}
}
Expand Down Expand Up @@ -2028,11 +2044,11 @@ class YamlReadingTest : FlatFunSpec({
val exception = shouldThrow<UnknownPolymorphicTypeException> { polymorphicYaml.decodeFromString(TestSealedStructure.serializer(), input) }

exception.asClue {
it.message shouldBe "Unknown type 'someOtherType'. Known types are: sealedInt, sealedString"
it.message shouldBe "Unknown type 'someOtherType'. Known types are: inlineString, sealedInt, sealedString"
it.line shouldBe 1
it.column shouldBe 7
it.typeName shouldBe "someOtherType"
it.validTypeNames shouldBe setOf("sealedInt", "sealedString")
it.validTypeNames shouldBe setOf("inlineString", "sealedInt", "sealedString")
it.path shouldBe YamlPath.root.withMapElementKey("kind", Location(1, 1)).withMapElementValue(Location(1, 7))
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/commonTest/kotlin/com/charleskorn/kaml/YamlWritingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,18 @@ class YamlWritingTest : FlatFunSpec({
}
}

context("serializing a sealed type (inline)") {
val input = TestSealedStructure.InlineSealedString("abc")
val output = polymorphicYaml.encodeToString(TestSealedStructure.serializer(), input)
val expectedYaml = """
!<inlineString> "abc"
""".trimIndent()

test("returns the value serialized in the expected YAML form") {
output shouldBe expectedYaml
}
}

context("serializing an unsealed type") {
val input = UnsealedString("blah")
val output = polymorphicYaml.encodeToString(PolymorphicSerializer(UnsealedClass::class), input)
Expand Down Expand Up @@ -883,11 +895,24 @@ class YamlWritingTest : FlatFunSpec({
}
}

context("serializing a polymorphic value (inline) as a property value") {
val input = SealedWrapper(TestSealedStructure.InlineSealedString("abc"))
val output = polymorphicYaml.encodeToString(SealedWrapper.serializer(), input)
val expectedYaml = """
element: !<inlineString> "abc"
""".trimIndent()

test("returns the value serialized in the expected YAML form") {
output shouldBe expectedYaml
}
}

context("serializing a list of polymorphic values") {
val input = listOf(
TestSealedStructure.SimpleSealedInt(5),
TestSealedStructure.SimpleSealedString("some test"),
TestSealedStructure.SimpleSealedInt(-20),
TestSealedStructure.InlineSealedString("more test"),
TestSealedStructure.SimpleSealedString(null),
null,
)
Expand All @@ -901,6 +926,7 @@ class YamlWritingTest : FlatFunSpec({
value: "some test"
- !<sealedInt>
value: -20
- !<inlineString> "more test"
- !<sealedString>
value: null
- null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,22 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.modules.SerializersModule
import kotlin.jvm.JvmInline

@Serializable
sealed class TestSealedStructure {
sealed interface TestSealedStructure {
@Serializable
@SerialName("sealedInt")
data class SimpleSealedInt(val value: Int) : TestSealedStructure()
data class SimpleSealedInt(val value: Int) : TestSealedStructure

@Serializable
@SerialName("sealedString")
data class SimpleSealedString(val value: String?) : TestSealedStructure()
data class SimpleSealedString(val value: String?) : TestSealedStructure

@Serializable
@SerialName("inlineString")
@JvmInline
value class InlineSealedString(val value: String) : TestSealedStructure
}

@Serializable
Expand Down