Closed kotlinlukas closed 1 year ago
Can you give me an example config class and file so I can see this in action ?
Here is the most minimal example I could come up with:
object MyDecoder: Decoder<MyClass> {
override fun decode(node: Node, type: KType, context: DecoderContext): ConfigResult<MyClass> =
when(node) {
is StringNode -> MyClass(node.value).valid()
else -> ConfigFailure.DecodeError(node, type).invalid()
}
override fun supports(type: KType): Boolean = type.isSubtypeOf(MyClass::class.starProjectedType)
}
val CONFIG = ConfigLoaderBuilder.default()
.addResourceSource("/application.yml")
.addDecoder(MyDecoder)
.build()
.loadConfigOrThrow<MyConfig>()
data class MyConfig(
val someInt: Int,
val test: MyClass,
)
class MyClass(val myString: String) {
override fun hashCode(): Int =
myString.hashCode() + CONFIG.someInt
}
fun main() {
println(CONFIG)
}
Error is:
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException: Cannot invoke "MyConfig.getSomeInt()" because the return value of "MyConfigKt.getCONFIG()" is null
at MyClass.hashCode(MyConfig.kt:32)
at com.sksamuel.hoplite.NodeState.hashCode(DecoderContext.kt)
at java.base/java.util.HashMap.hash(HashMap.java:338)
...
I think with this kind of circular dependency you're always going to run the risk of having brittle code, so I would first try to avoid it. But that being said, we can avoid calling hash code on your own class. That's not really needed and I have a PR up that avoids it.
Hello,
I'm running into a bit of an issue with classes whose hashcode implementation relies on the Configuration, but who are themselves part of the Configuration.
For example, I have a ValueClass that wraps a String with some validation, which I use - among other places - in my configuration. However, the hashcode implementation of that class calls
.lowercase(MyConfig.configuredLocale)
on the string, to make sure strings are treated case-insensitively. Now this crashes on load, because Hoplite calls the hashcode method on the object during config loading (com.sksamuel.hoplite.NodeState.hashCode(DecoderContext.kt)
), and finds that the CONFIG does not exist yet and throws a runtime NPE (on startup) because of it.I can fix this inside the hashcode method by doing something like
.lowercase(MyConfig?.configuredLocale ?: Locale.US)
, but this is highlighted by the IDE as bad because MyConfig is not nullable and thus "can never be null". Alternatively I can abstract the configuration in some method that returns a nullable config, but then I have to deal with nullability and call chains on nested propertiesMyConfig?.look?.like?.this?.and?.the?.question?.marks?.are?.making?.me?.question?.my ?: life
This all feels bad to just work around the case that some functions are called during config initialisation on a value that regularly is guaranteed non-null. Do you have any ideas on how I can do this cleanly?