sksamuel / hoplite

A boilerplate-free Kotlin config library for loading configuration files as data classes
Apache License 2.0
919 stars 74 forks source link

Unable to load configuration for a data class containing a `kotlin.time.Duration?` #453

Open chrisalbright opened 1 week ago

chrisalbright commented 1 week ago

I've been using Kotlin 2.0.20, but I've tested with 1.6.21 and get the same error regardless. I've included a sample project illustrating the problem. Ironically, I created this project (hoplite-test.zip) in order to demonstrate the issue and I'm seeing the same behavior but the underlying cause is different.

The original project I'm working on is manifesting with a ClassCastException:

Exception in thread "main" com.sksamuel.hoplite.ConfigException: Error loading config because:

    Could not instantiate class Simple from args [kotlin.time.Duration]: Expected args are [class kotlin.time.Duration]. Underlying error was java.lang.ClassCastException: Cannot cast java.lang.Long to kotlin.time.Duration
    at com.sksamuel.hoplite.ConfigLoaderKt$returnOrThrow$1.invoke(ConfigLoader.kt:315)
    at com.sksamuel.hoplite.ConfigLoaderKt$returnOrThrow$1.invoke(ConfigLoader.kt:312)
    at com.sksamuel.hoplite.fp.ValidatedKt.getOrElse(Validated.kt:115)
    at com.sksamuel.hoplite.ConfigLoaderKt.returnOrThrow(ConfigLoader.kt:312)
    at com.sksamuel.hoplite.ConfigLoader.returnOrThrow(ConfigLoader.kt:273)
    at SimpleKt.main(Simple.kt:33)
    at SimpleKt.main(Simple.kt)

The code that creates this stacktrace is

import com.sksamuel.hoplite.ConfigLoaderBuilder
import com.sksamuel.hoplite.ExperimentalHoplite
import com.sksamuel.hoplite.addResourceSource
import kotlin.time.Duration

data class Simple(val duration: Duration?)

@OptIn(ExperimentalHoplite::class)
fun main() {
    val simple = ConfigLoaderBuilder
        .default()
        .withExplicitSealedTypes()
        .addResourceSource("/duration.yml").build()
        .loadConfigOrThrow<Simple>()

    println(simple)
}

With this configuration file

duration: 5 minutes

The (slightly modified) build configuration is

plugins {
    kotlin("jvm") version "2.0.20"
    `java-library`
    `maven-publish`
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(11))
    }
}

repositories {
    mavenCentral()
    mavenLocal()
}

dependencies {
    kotlin("stdlib")
    val hopliteVersion = "2.8.2"
    implementation("com.sksamuel.hoplite:hoplite-core:$hopliteVersion")
    implementation("com.sksamuel.hoplite:hoplite-toml:$hopliteVersion")
    implementation("com.sksamuel.hoplite:hoplite-yaml:$hopliteVersion")
}

The demonstration project included here is also failing, but with a different underlying error:

com.sksamuel.hoplite.ConfigException: Error loading config because:

    Could not instantiate class org.example.NotWorking$Simple from args [kotlin.time.Duration]: Expected args are [class kotlin.time.Duration]. Underlying error was java.lang.IllegalArgumentException: argument type mismatch
    at com.sksamuel.hoplite.ConfigLoaderKt$returnOrThrow$1.invoke(ConfigLoader.kt:315)
    at com.sksamuel.hoplite.ConfigLoaderKt$returnOrThrow$1.invoke(ConfigLoader.kt:312)
    at com.sksamuel.hoplite.fp.ValidatedKt.getOrElse(Validated.kt:115)
    at com.sksamuel.hoplite.ConfigLoaderKt.returnOrThrow(ConfigLoader.kt:312)
    at com.sksamuel.hoplite.ConfigLoader.returnOrThrow(ConfigLoader.kt:273)
    at org.example.DemoKt.main(Demo.kt:186)
    at org.example.DemoKt.main(Demo.kt)

I've been trying to get it to fail the same way, but no success - maybe you'll see some difference in the configuration that isn't standing out to me.

In both cases the error is coming from the DataClassDecoder class (/com/sksamuel/hoplite/decoder/DataClassDecoder.kt:126, /com/sksamuel/hoplite/decoder/DataClassDecoder.kt:140, /com/sksamuel/hoplite/decoder/DataClassDecoder.kt:144)

I have only encountered this problem with kotlin.time.Duration?. kotlin.time.Duration works without any problem.

sksamuel commented 1 week ago

So it only happens on nullable durations ?

chrisalbright commented 1 week ago

That's right. I supposed I could have phrased that more clearly.