Skip to content

Commit

Permalink
Merge pull request #10 from valderman/enum-support
Browse files Browse the repository at this point in the history
Enum support
  • Loading branch information
valderman committed Jun 23, 2022
2 parents 3bec60e + 0b0b65b commit 8ff89a5
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 1 deletion.
22 changes: 21 additions & 1 deletion src/main/kotlin/cc/ekblad/toml/transcoding/TomlDecoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.KVisibility
import kotlin.reflect.full.createType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
Expand Down Expand Up @@ -75,10 +76,29 @@ fun <T : Any?> TomlDecoder.decode(value: TomlValue, target: KType): T {
return when (value) {
is TomlValue.List -> toList(value, target)
is TomlValue.Map -> toObject(value, target)
else -> throw TomlException.DecodingError("no decoder registered for value/target pair", value, target)
is TomlValue.String -> toEnum(value, target)
else -> throw noDecoder(value, target)
}
}

private fun noDecoder(value: TomlValue, target: KType) =
TomlException.DecodingError("no decoder registered for value/target pair", value, target)

fun <T : Any> toEnum(value: TomlValue.String, target: KType): T {
val kClass = requireKClass(target.classifier)
if (!kClass.isSubclassOf(Enum::class)) {
throw noDecoder(value, target)
}
val enumValues = kClass.java.enumConstants as Array<Enum<*>>
val enumValue = enumValues.singleOrNull { it.name == value.value }
?: throw TomlException.DecodingError(
"${value.value} is not a constructor of enum class ${kClass.simpleName}",
value,
target
)
return enumValue as T
}

private val anyKType: KType = Any::class.createType()
private val stringKType: KType = String::class.createType()

Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/cc/ekblad/toml/transcoding/TomlEncoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import cc.ekblad.toml.util.TomlName
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible

Expand Down Expand Up @@ -59,6 +60,7 @@ fun TomlEncoder.encode(value: Any): TomlValue {
value is Map<*, *> -> fromMap(value)
value is Iterable<*> -> TomlValue.List(value.mapNotNull { it?.let(::encode) })
value::class.isData -> fromDataClass(value)
value::class.isSubclassOf(Enum::class) -> TomlValue.String((value as Enum<*>).name)
else -> throw TomlException.EncodingError(value, null)
}
}
Expand Down
55 changes: 55 additions & 0 deletions src/test/kotlin/cc/ekblad/toml/transcoder/BuiltinDecoderTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class BuiltinDecoderTests : StringTest {
val dad: Person?,
)

enum class PublicEnum { Foo, Bar }
private enum class PrivateEnum { Foo, Bar }
private enum class EnumWithArgs(someValue: String) { Foo("hello"), Bar("goodbye") }

@Test
fun `throws on decode to invalid type`() {
listOf(
Expand Down Expand Up @@ -65,6 +69,57 @@ class BuiltinDecoderTests : StringTest {
assertContains(error.message, error.reason!!)
}

@Test
fun `can decode strings to enums`() {
assertEquals(PublicEnum.Foo, mapper.decode(TomlValue.String("Foo")))
assertEquals(PublicEnum.Bar, mapper.decode(TomlValue.String("Bar")))
assertEquals(PrivateEnum.Foo, mapper.decode(TomlValue.String("Foo")))
assertEquals(PrivateEnum.Bar, mapper.decode(TomlValue.String("Bar")))
assertEquals(EnumWithArgs.Foo, mapper.decode(TomlValue.String("Foo")))
assertEquals(EnumWithArgs.Bar, mapper.decode(TomlValue.String("Bar")))
}

@Test
fun `decoding string to nonexistent enum constructor throws`() {
val error = assertFailsWith<TomlException.DecodingError> {
mapper.decode<PublicEnum>(TomlValue.String("Not Foo"))
}
assertNotNull(error.reason)
assertNull(error.cause)
assertContains(error.reason!!, "not a constructor of enum class")
assertEquals(TomlValue.String("Not Foo"), error.sourceValue)
assertEquals(typeOf<PublicEnum>(), error.targetType)
}

@Test
fun `enum decoding is case sensitive`() {
val error = assertFailsWith<TomlException.DecodingError> {
mapper.decode<PublicEnum>(TomlValue.String("foo"))
}
}

@Test
fun `enum decoding compares the entire strings`() {
assertFailsWith<TomlException.DecodingError> {
mapper.decode<PublicEnum>(TomlValue.String("Fooo"))
}
assertFailsWith<TomlException.DecodingError> {
mapper.decode<PublicEnum>(TomlValue.String("oFoo"))
}
assertFailsWith<TomlException.DecodingError> {
mapper.decode<PublicEnum>(TomlValue.String("oFooo"))
}
assertFailsWith<TomlException.DecodingError> {
mapper.decode<PublicEnum>(TomlValue.String("oo"))
}
assertFailsWith<TomlException.DecodingError> {
mapper.decode<PublicEnum>(TomlValue.String("Fo"))
}
assertFailsWith<TomlException.DecodingError> {
mapper.decode<PublicEnum>(TomlValue.String("o"))
}
}

@Test
fun `can decode objects to data classes`() {
val expected = Person(
Expand Down
13 changes: 13 additions & 0 deletions src/test/kotlin/cc/ekblad/toml/transcoder/BuiltinEncoderTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ import kotlin.test.assertNull

class BuiltinEncoderTests : UnitTest {
private val mapper = tomlMapper { }
enum class PublicEnum { Foo, Bar }
private enum class PrivateEnum { Foo, Bar }
private enum class EnumWithArgs(val number: Int) { Foo(12), Bar(32) }

@Test
fun `can encode enum values`() {
assertEncodesTo(PublicEnum.Foo, TomlValue.String("Foo"))
assertEncodesTo(PublicEnum.Bar, TomlValue.String("Bar"))
assertEncodesTo(PrivateEnum.Foo, TomlValue.String("Foo"))
assertEncodesTo(PrivateEnum.Bar, TomlValue.String("Bar"))
assertEncodesTo(EnumWithArgs.Foo, TomlValue.String("Foo"))
assertEncodesTo(EnumWithArgs.Bar, TomlValue.String("Bar"))
}

@Test
fun `can encode integers`() {
Expand Down

0 comments on commit 8ff89a5

Please sign in to comment.