Repository: config4k/config4k
Pull request #160 (2023-01-02 13:34:45)

Support `Enum` as key of `Map`
This PR allows to deserialize config map keys as enums

red-avtovo: @ihostage can you please take a look when you have some time?
codecov[bot]: # [Codecov](https://codecov.io/gh/config4k/config4k/pull/160?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k) Report
Base: **83.88**% // Head: **83.63**% // Decreases project coverage by **`-0.25%`** :warning:
> Coverage data is based on head [(`2f8493f`)](https://codecov.io/gh/config4k/config4k/pull/160?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k) compared to base [(`84b96a4`)](https://codecov.io/gh/config4k/config4k/commit/84b96a49dfbfd19c1dbf87b75764fda15871809f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k).
> Patch coverage: 81.81% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##               main     #160      +/-   ##
============================================
- Coverage     83.88%   83.63%   -0.25%     
  Complexity       60       60              
============================================
  Files            33       33              
  Lines           211      220       +9     
  Branches         81       83       +2     
============================================
+ Hits            177      184       +7     
- Misses           18       19       +1     
- Partials         16       17       +1     
```


| [Impacted Files](https://codecov.io/gh/config4k/config4k/pull/160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k) | Coverage Δ | |
|---|---|---|
| [...ain/kotlin/io/github/config4k/readers/MapReader.kt](https://codecov.io/gh/config4k/config4k/pull/160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k#diff-c3JjL21haW4va290bGluL2lvL2dpdGh1Yi9jb25maWc0ay9yZWFkZXJzL01hcFJlYWRlci5rdA==) | `75.00% <81.81%> (+2.27%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/config4k/config4k/pull/160?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=config4k).


Patch:
diff --git a/src/main/kotlin/io/github/config4k/readers/EnumReader.kt b/src/main/kotlin/io/github/config4k/readers/EnumReader.kt
index c003f93..1b9977a 100644
--- a/src/main/kotlin/io/github/config4k/readers/EnumReader.kt
+++ b/src/main/kotlin/io/github/config4k/readers/EnumReader.kt
@@ -5,9 +5,13 @@ import io.github.config4k.extract
 import kotlin.reflect.KClass
 
 internal class EnumReader(clazz: KClass<*>) : Reader<Enum<*>>({ config, path ->
-    val enumName = config.extract<String>(path)
-    val enumConstants = clazz.java.enumConstants
-    enumConstants.find { it.toString().equals(enumName, true) } as? Enum<*>
-        ?: throw Config4kException
-            .WrongEnum(enumConstants.map(Any::toString), enumName)
-})
+    stringToEnum(config.extract(path), clazz)
+}) {
+    companion object {
+        fun stringToEnum(value: String, clazz: KClass<*>): Enum<*> =
+            clazz.java.enumConstants.let { enumValues ->
+                enumValues.find { it.toString().equals(value, true) } as? Enum<*>
+                    ?: throw Config4kException.WrongEnum(enumValues.map(Any::toString), value)
+            }
+    }
+}
diff --git a/src/main/kotlin/io/github/config4k/readers/MapReader.kt b/src/main/kotlin/io/github/config4k/readers/MapReader.kt
index 3d59f36..0da84af 100644
--- a/src/main/kotlin/io/github/config4k/readers/MapReader.kt
+++ b/src/main/kotlin/io/github/config4k/readers/MapReader.kt
@@ -3,13 +3,23 @@ package io.github.config4k.readers
 import com.typesafe.config.ConfigUtil
 import io.github.config4k.ClassContainer
 import io.github.config4k.MapEntry
+import kotlin.reflect.full.isSubclassOf
 
 internal class MapReader(keyClass: ClassContainer, valueClass: ClassContainer, mutable: Boolean = false) : Reader<Map<*, *>?>({ config, path ->
-    when (keyClass.mapperClass) {
-        String::class -> {
+    val keyMapperClass = keyClass.mapperClass
+    when {
+        keyMapperClass == String::class -> {
             val child = config.getConfig(path)
             child.root().keys.associateWith { key -> SelectReader.getReader(valueClass)(child, ConfigUtil.joinPath(key)) }
         }
+        keyMapperClass.isSubclassOf(Enum::class) -> {
+            val child = config.getConfig(path)
+            child.root().keys.associate { key ->
+                val resultKey = EnumReader.stringToEnum(key, keyMapperClass)
+                val resultValue = SelectReader.getReader(valueClass)(child, ConfigUtil.joinPath(key))
+                resultKey to resultValue
+            }
+        }
         else -> {
             val mapEntryClassContainer = ClassContainer(MapEntry::class, mapOf("K" to keyClass, "V" to valueClass))
             ListReader(mapEntryClassContainer).getValue(config, path)?.associate {


Test patch:
diff --git a/src/test/kotlin/io/github/config4k/TestMap.kt b/src/test/kotlin/io/github/config4k/TestMap.kt
index b94dba1..22a113c 100644
--- a/src/test/kotlin/io/github/config4k/TestMap.kt
+++ b/src/test/kotlin/io/github/config4k/TestMap.kt
@@ -88,4 +88,18 @@ class TestMap : WordSpec({
             map shouldBe mapOf("key1" to 10, "key2" to 20, "key3" to 30)
         }
     }
+
+    "Config.extract<Map<Size, T>>" should {
+        "return Map<Size, T>" {
+            val mapConfig =
+                """
+                nest = {
+                  Small = 1
+                  MEDIUM = 35
+                  large = 42
+                }""".toConfig()
+            val map = mapConfig.extract<Map<Size, Int>>("nest")
+            map shouldBe mapOf(Size.SMALL to 1, Size.MEDIUM to 35, Size.LARGE to 42)
+        }
+    }
 })