Add support for transformer function in config property delegate#3676
Add support for transformer function in config property delegate#3676picklebento merged 2 commits intodetekt:mainfrom
Conversation
Codecov Report
@@ Coverage Diff @@
## main #3676 +/- ##
============================================
+ Coverage 78.75% 78.80% +0.04%
- Complexity 2900 2901 +1
============================================
Files 473 473
Lines 9336 9335 -1
Branches 1722 1723 +1
============================================
+ Hits 7353 7356 +3
+ Misses 1075 1073 -2
+ Partials 908 906 -2
Continue to review full report at Codecov.
|
|
I think that we need a it more flexibility. Something like: And we need to think about caching these values. |
|
I think that should be possible. As long as we do not expand the types that are used for |
+1 |
|
What do you think about this: fun config(
defaultValue: List<String>
): ReadOnlyProperty<ConfigAware, List<String>> = ListConfigProperty(defaultValue) { it }
fun <T : Any> config(
defaultValue: List<String>,
transform: (List<String>) -> T
): ReadOnlyProperty<ConfigAware, T> = ListConfigProperty(defaultValue, transform)
fun <T : Any> config(
defaultValue: T
): ReadOnlyProperty<ConfigAware, T> = TransformedConfigProperty(defaultValue) { it }
fun <T : Any, U : Any> config(
defaultValue: T,
transform: (T) -> U,
): ReadOnlyProperty<ConfigAware, U> = TransformedConfigProperty(defaultValue, transform)
private class TransformedConfigProperty<T : Any, U : Any>(
private val defaultValue: T,
private val transform: (T) -> U
) : ReadOnlyProperty<ConfigAware, U> {
override fun getValue(thisRef: ConfigAware, property: KProperty<*>): U {
return transform(thisRef.valueOrDefault(property.name, defaultValue))
}
}
private class ListConfigProperty<T : Any>(
private val defaultValue: List<String>,
private val transform: (List<String>) -> T
) : ReadOnlyProperty<ConfigAware, T> {
override fun getValue(thisRef: ConfigAware, property: KProperty<*>): T {
return transform(thisRef.valueOrDefaultCommaSeparated(property.name, defaultValue))
}
}It would be nice to use default values intead of overloads but in this case it's not possible. But, for the consumer, they are the same. |
d199981 to
e20c497
Compare
|
I really like the idea, especially after renaming them to |
|
Thank you for pointing out that we need to make sure that the transformer is only evaluated once. I adapted the code from |
f5d49f2 to
8559e32
Compare
8559e32 to
098f9c0
Compare
| val fallbackValue = getValueOrDefault(thisRef, fallbackPropertyName, defaultValue) | ||
| return transform(getValueOrDefault(thisRef, property.name, fallbackValue)) |
There was a problem hiding this comment.
Not something in this PR, but I will file an issue myself on changing the fallback value of having type () -> T instead of T - The benefit is that if the value is present, we do not need to initialize the fallback property.
There was a problem hiding this comment.
Is it worth it? We will be initializing booleans, strings, List. They will have more less the same cost to instantiate than the lambda itself and we will need to run the lambda too.
There was a problem hiding this comment.
On top of that please consider that We have to extract the default value for the documentation without the benefit of a BindingContext. This might end up really complicated.
There was a problem hiding this comment.
Is it worth it? We will be initializing booleans, strings, List.
I am looking for semantic correctness: If a property exists, we shouldn't read from the fallback property at all.
On top of that please consider that We have to extract the default value for the documentation without the benefit of a BindingContext. This might end up really complicated.
I don't fully get the benefit of a BindingContext, would you mind elaborate it more?
There was a problem hiding this comment.
Never mind. Reading your comment on my mobile I thought you were talking about something else. You were talking about something like this? I could add it to this PR or feel free to create an issue afterwards.
override fun doGetValue(thisRef: ConfigAware, property: KProperty<*>): U {
val fallbackValueSupplier = { getValueOrDefault(thisRef, fallbackPropertyName) { defaultValue } }
return transform(getValueOrDefault(thisRef, property.name, fallbackValueSupplier))
}There was a problem hiding this comment.
I am afraid I can't find a solution without actually invoking the supplier in getValueOrDefault which makes this exercise useless as we evaluate the fallback no matter what. I rather keep it the way it is if that is ok.
There was a problem hiding this comment.
No problem. We can table this for now
| } | ||
| it("extracts default value with method reference") { | ||
| val items = subject.run(code) | ||
| assertThat(items[0].configuration[1].defaultValue).isEqualTo("'false'") |
There was a problem hiding this comment.
Related to val needsQuotes = declaredTypeOrNull in TYPES_THAT_NEED_QUOTATION_FOR_DEFAULT, should we add quotes only when the source type is String?
I imagine the default value should be false without quotes.
There was a problem hiding this comment.
You are right. The problem here is that without a binding context or reflection I don't now how to reliably determine if the default value is a string or not. This is "best effort" by looking at the property type which (with transformi) might be something else.
There was a problem hiding this comment.
One possible alternative is using reified types for ConfigProperty.
|
Is there anything that needs to be done here before merging? I would like to continue with #3670 if possible. |
|
All good. Thank you for the groundwork. |
…ekt#3676) Co-authored-by: Markus Schwarz <post@markus-schwarz.net>
This relates to a comment by @BraisGabin in #3670 addressing situations in which the configured values must be transformed.
This PR:
Examples:
This would also resolve #3672