Skip to content

Commit

Permalink
Throw an exception if a contextual serializer tries to deserialize th…
Browse files Browse the repository at this point in the history
…e wrong type.
  • Loading branch information
charleskorn committed Jul 19, 2020
1 parent 8813f6a commit 897373b
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 18 deletions.
29 changes: 11 additions & 18 deletions src/main/kotlin/com/charleskorn/kaml/YamlInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,22 @@ sealed class YamlInput(val node: YamlNode, override var context: SerialModule, v
}

is YamlScalar -> when (descriptor.kind) {
is PrimitiveKind, UnionKind.ENUM_KIND, UnionKind.CONTEXTUAL -> YamlScalarInput(node, context, configuration)
is PrimitiveKind, UnionKind.ENUM_KIND -> YamlScalarInput(node, context, configuration)
is UnionKind.CONTEXTUAL -> YamlContextualInput(node, context, configuration)
is PolymorphicKind -> throw MissingTypeTagException(node.location)
else -> throw IncorrectTypeException("Expected ${descriptor.kind.friendlyDescription}, but got a scalar value", node.location)
}

is YamlList -> when (descriptor.kind) {
is StructureKind.LIST, UnionKind.CONTEXTUAL -> YamlListInput(node, context, configuration)
is StructureKind.LIST -> YamlListInput(node, context, configuration)
is UnionKind.CONTEXTUAL -> YamlContextualInput(node, context, configuration)
else -> throw IncorrectTypeException("Expected ${descriptor.kind.friendlyDescription}, but got a list", node.location)
}

is YamlMap -> when (descriptor.kind) {
is StructureKind.CLASS, StructureKind.OBJECT -> YamlObjectInput(node, context, configuration)
is StructureKind.MAP -> YamlMapInput(node, context, configuration)
is UnionKind.CONTEXTUAL -> YamlMapLikeContextualDecoder(node, context, configuration)
is UnionKind.CONTEXTUAL -> YamlContextualInput(node, context, configuration)
is PolymorphicKind -> when (configuration.polymorphismStyle) {
PolymorphismStyle.Tag -> throw MissingTypeTagException(node.location)
PolymorphismStyle.Property -> createPolymorphicMapDeserializer(node, context, configuration)
Expand Down Expand Up @@ -266,22 +268,12 @@ private class YamlListInput(val list: YamlList, context: SerialModule, configura
}
}

private class YamlMapLikeContextualDecoder(private val map: YamlMap, context: SerialModule, configuration: YamlConfiguration) : YamlInput(map, 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")
}
private class YamlContextualInput(node: YamlNode, context: SerialModule, configuration: YamlConfiguration) : YamlInput(node, 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 beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
return when (descriptor.kind) {
is StructureKind.CLASS, StructureKind.OBJECT -> YamlObjectInput(map, context, configuration)
is StructureKind.MAP -> YamlMapInput(map, context, configuration)
else -> super.beginStructure(descriptor, *typeParams)
}
}
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder =
createFor(node, context, configuration, descriptor)

override fun getCurrentLocation(): Location = node.location
}
Expand Down Expand Up @@ -502,6 +494,7 @@ private val SerialKind.friendlyDescription: String
return when (this) {
is StructureKind.MAP -> "a map"
is StructureKind.CLASS -> "an object"
is StructureKind.OBJECT -> "an object"
is StructureKind.LIST -> "a list"
is PrimitiveKind.STRING -> "a string"
is PrimitiveKind.BOOLEAN -> "a boolean"
Expand Down
83 changes: 83 additions & 0 deletions src/test/kotlin/com/charleskorn/kaml/YamlReadingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.PrimitiveKind
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerialKind
import kotlinx.serialization.Serializable
import kotlinx.serialization.StructureKind
import kotlinx.serialization.UnionKind
Expand Down Expand Up @@ -2004,6 +2005,69 @@ object YamlReadingTest : Spek({
}
}
}

describe("given the contextual serializer attempts to begin a structure that does not match the input") {
context("given the input is a map") {
val input = "a: b"

mapOf(
PrimitiveKind.STRING to "a string",
StructureKind.LIST to "a list"
).forEach { kind, description ->
context("attempting to begin $description") {
it("throws an exception with the correct location information") {
expect({ Yaml.default.parse(ContextualSerializerThatAttemptsToDeserializeIncorrectType(kind), input) }).toThrow<IncorrectTypeException> {
message { toBe("Expected $description, but got a map") }
line { toBe(1) }
column { toBe(1) }
}
}
}
}
}

context("given the input is a list") {
val input = "- a"

mapOf(
StructureKind.OBJECT to "an object",
StructureKind.CLASS to "an object",
StructureKind.MAP to "a map",
PrimitiveKind.STRING to "a string"
).forEach { kind, description ->
context("attempting to begin $description") {
it("throws an exception with the correct location information") {
expect({ Yaml.default.parse(ContextualSerializerThatAttemptsToDeserializeIncorrectType(kind), input) }).toThrow<IncorrectTypeException> {
message { toBe("Expected $description, but got a list") }
line { toBe(1) }
column { toBe(1) }
}
}
}
}
}

context("given the input is a scalar") {
val input = "2"

mapOf(
StructureKind.OBJECT to "an object",
StructureKind.CLASS to "an object",
StructureKind.MAP to "a map",
StructureKind.LIST to "a list"
).forEach { kind, description ->
context("attempting to begin $description") {
it("throws an exception with the correct location information") {
expect({ Yaml.default.parse(ContextualSerializerThatAttemptsToDeserializeIncorrectType(kind), input) }).toThrow<IncorrectTypeException> {
message { toBe("Expected $description, but got a scalar value") }
line { toBe(1) }
column { toBe(1) }
}
}
}
}
}
}
}
})

Expand Down Expand Up @@ -2071,3 +2135,22 @@ object ContextualSerializer : KSerializer<String> {

override fun serialize(encoder: Encoder, value: String): Unit = throw UnsupportedOperationException()
}

class ContextualSerializerThatAttemptsToDeserializeIncorrectType(private val kind: SerialKind) : KSerializer<String> {
val innerDescriptor = SerialDescriptor("thing", kind)

override val descriptor = SerialDescriptor("ContextualSerializer", UnionKind.CONTEXTUAL) {
element("string", SerialDescriptor("value", PrimitiveKind.STRING))
element("object", innerDescriptor)
}

override fun deserialize(decoder: Decoder): String {
val input = decoder.beginStructure(descriptor) as YamlInput

input.beginStructure(innerDescriptor)

return "Should never get to this point"
}

override fun serialize(encoder: Encoder, value: String): Unit = throw UnsupportedOperationException()
}

0 comments on commit 897373b

Please sign in to comment.