jdiazcano / cfg4k

Flexible and easy to use config library written in kotlin
Apache License 2.0
80 stars 6 forks source link

ClassNotFoundException on $DefaultImpls #48

Closed cojo closed 6 years ago

cojo commented 6 years ago

Hi, Love the concept of this library and that it appears to be actively maintained.

I'm trying to use it in a project, and running into an issue where it seems that it relies on interfaces having a $DefaultImpls which as far as I can tell will not be the case in the basic use cases listed in e.g. your examples?

I end up with a ClassNotFoundException on Config$DefaultImpls in https://github.com/jdiazcano/cfg4k/blob/master/cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/binders/BindingInvocationHandler.kt#L63 when using the following code: Config.kt:

enum class Environment {
    DEVELOPMENT,
    STAGING,
    PRODUCTION;

    fun slug() = name.toLowerCase()
}

interface Config {
    val database: DatabaseConfig
    val server: ServerConfig
}

interface DatabaseConfig {
    val url: String
    val username: String
    val password: String
}

interface ServerConfig {
    val port: Int
    val host: String
    val environment: Environment
}

App.kt (redacted):

        val sysLoader = SystemPropertyConfigLoader()
        val envLoader = EnvironmentConfigLoader()
        val baseProvider = OverrideConfigProvider(
            DefaultConfigProvider(sysLoader),
            DefaultConfigProvider(envLoader)
        )
        val baseConfig = baseProvider.bind<Config>()

        val propsDefaultLoader = PropertyConfigLoader(ClasspathConfigSource("/app.properties"))
        val propsEnvLoader = PropertyConfigLoader(ClasspathConfigSource("/app.${baseConfig.server.environment}.properties"))
        val provider = OverrideConfigProvider(
            DefaultConfigProvider(sysLoader),
            DefaultConfigProvider(envLoader),
            DefaultConfigProvider(propsDefaultLoader),
            DefaultConfigProvider(propsEnvLoader)
        )
        val config = provider.bind<Config>()

        print(config.server.host)
        print(config.database.url)

Am I missing something obvious? If I make a stubbed default function on the objects I can get past the first error (missing $DefaultImpls) but it doesn't get me much further as it still seems to expect Kotlin to auto-stub all of these, which isn't happening for me.

Thanks in advance for your help!

cojo commented 6 years ago

Before I forget - I'm using Kotlin 1.2.30 and Java 8 / 1.8 if that's helpful or important for whatever reason.

jdiazcano commented 6 years ago

Taking a look. I'm not sure if inside an enum the DefaultImpls is different or something. Actually that's one of the things that I didn't think of, an enum with methods and default.

cojo commented 6 years ago

Thanks for taking a look! Yeah that was one thought I had, but what was weird was that it seemed to be failing on this issue in the the base Config class regardless of the enum's involvement as I was testing different things out.

jdiazcano commented 6 years ago

Ohhh, I think I know what's happening.

An enum class is a real class (not an interface) and it will not have DefaultImpl but real implementations. Let me try it and maybe there's a workaround defining an interface for the Enum.

jdiazcano commented 6 years ago

Yes, that is exactly what is happening (I'll try to fix it to work with classes anyways though)

enum class TestEnum : Interf {
    TEST, TEST1, TEST2
}

interface Interf {
    val name: String
    val slug: String get() = name.toLowerCase()
}

If you use it like that you won't have any problems

        it("binding test") {
            val testBinder = provider.bind<BindedEnum>("")
            testBinder.thisWillBeEnum().should.be.equal(TestEnum.TEST)
            testBinder.thisWillBeEnum().slug.should.be.equal(TestEnum.TEST.name.toLowerCase())
        }
cojo commented 6 years ago

I tried stripping out the enum entirely and actually get the same thing:

com.jdiazcano.cfg4k.utils.SettingNotFound: Setting server not found
    at com.jdiazcano.cfg4k.binders.BindingInvocationHandler.invoke(BindingInvocationHandler.kt:65)
    at com.sun.proxy.$Proxy0.getServer(Unknown Source)
    at AppServer.run(AppServer.kt:33)
    at AppServer.main(AppServer.kt:16)

Note for context, that this error setting server not found is a red herring as it is actually swallowing the ClassNotFoundException I mentioned in the original post image

Any chance there might be two separate issues going on here? One the enum but the other the cause of the original Exception?

cojo commented 6 years ago

Current code is the same for the server portion, but this is all there is now in the Config.kt file:

interface Config {
    val database: DatabaseConfig
    val server: ServerConfig
}

interface DatabaseConfig {
    val url: String
    val username: String
    val password: String
}

interface ServerConfig {
    val port: Int
    val host: String
    val environment: String
}
jdiazcano commented 6 years ago

Could you paste also the properties file changing all the strings for something else? It might be related to naming then

And if you want I'm available in kotlin slack.

jdiazcano commented 6 years ago

So the real issue is that Environment and System loaders right now do not support binding due to the way that it is designed right now.

They still can be used with a normal get<String>("server.host")

jdiazcano commented 6 years ago

Now both extends DefaultConfigLoader so merges and binding are supported.

The only caveat is that if you try to add value to an already set object a warning will be emitted.