Skip to content

Commit

Permalink
Add the ability to assign env vars to lists
Browse files Browse the repository at this point in the history
Resolves sksamuel#402.
  • Loading branch information
rocketraman committed Nov 28, 2024
1 parent 79a4be7 commit d572317
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ Hoplite maps env vars as follows:
* Underscores are separators for nested config. For example `TOPIC_NAME` would override a property `name` located in a `topic` parent.
* To bind env vars to arrays or lists, postfix with an index e.g. set env vars `TOPIC_NAME_0` and `TOPIC_NAME_1` to set two values for the `name` list property. Missing indices are ignored, which is useful for commenting out values without renumbering subsequent ones.
* To bind env vars to maps, the key is part of the nested config e.g. `TOPIC_NAME_FOO` and `TOPIC_NAME_BAR` would set the "foo" and "bar"
keys for the `name` map property. Note that keys are one exception to the idiomatic uppercase rule -- the env var name determines the
case of the map key.
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ case is still accepted), letters, and digits. **Single underscores** now separat
* Breaking: The `EnvironmentVariableOverridePropertySource` has been removed. The standard `EnvironmentVariablesPropertySource` is now
loaded by default, and takes precedence over other default sources just like the `EnvironmentVariableOverridePropertySource`
did. To maintain similar behavior, configure it with a filtering `prefix`.
* Add the ability to load a series of environment variables into arrays/lists via the `_n` syntax.

### 2.7.5

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.sksamuel.hoplite.sources

import com.sksamuel.hoplite.ArrayNode
import com.sksamuel.hoplite.ConfigResult
import com.sksamuel.hoplite.MapNode
import com.sksamuel.hoplite.Node
import com.sksamuel.hoplite.PropertySource
import com.sksamuel.hoplite.PropertySourceContext
import com.sksamuel.hoplite.fp.valid
import com.sksamuel.hoplite.parsers.toNode
import com.sksamuel.hoplite.transform

class EnvironmentVariablesPropertySource(
private val environmentVariableMap: () -> Map<String, String> = { System.getenv() },
Expand All @@ -23,6 +26,13 @@ class EnvironmentVariablesPropertySource(
.filterKeys { if (prefix == null) true else it.startsWith(prefix) }
.mapKeys { if (prefix == null) it.key else it.key.removePrefix(prefix) }

return map.toNode("env", DELIMITER).valid()
return map.toNode("env", DELIMITER).transform { node ->
if (node is MapNode && node.map.keys.all { it.toIntOrNull() != null }) {
// all they map keys are ints, so lets transform the MapNode into an ArrayNode
ArrayNode(node.map.values.toList(), node.pos, node.path, node.meta, node.delimiter, node.sourceKey)
} else {
node
}
}.valid()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,87 @@ class EnvironmentVariablesPropertySourceTest : FunSpec({
))
}

test("build env source can create lists") {
data class TestConfig(val listProp: List<String>)

val config = ConfigLoaderBuilder
.defaultWithoutPropertySources()
.addPropertySource(
EnvironmentVariablesPropertySource(
environmentVariableMap = {
mapOf(
"LISTPROP_0" to "value_a",
"LISTPROP_1" to "value_A",
"LISTPROP_2" to "value_abc",
)
},
)
)
.build()
.loadConfigOrThrow<TestConfig>()

config shouldBe TestConfig(listOf(
"value_a",
"value_A",
"value_abc",
))
}

test("build env source can create intermediate lists") {
data class ListEntry(val foo: String)
data class TestConfig(val listProp: List<ListEntry>)

val config = ConfigLoaderBuilder
.defaultWithoutPropertySources()
.addPropertySource(
EnvironmentVariablesPropertySource(
environmentVariableMap = {
mapOf(
"LISTPROP_0_FOO" to "value_a",
"LISTPROP_1_FOO" to "value_A",
"LISTPROP_2_FOO" to "value_abc",
)
},
)
)
.build()
.loadConfigOrThrow<TestConfig>()

config shouldBe TestConfig(listOf(
ListEntry("value_a"),
ListEntry("value_A"),
ListEntry("value_abc"),
))
}


test("build env source can skip missing list indices") {
// this is handy to easily comment out env vars without breaking the functionality

data class ListEntry(val foo: String)
data class TestConfig(val listProp: List<ListEntry>)

val config = ConfigLoaderBuilder
.defaultWithoutPropertySources()
.addPropertySource(
EnvironmentVariablesPropertySource(
environmentVariableMap = {
mapOf(
"LISTPROP_0_FOO" to "value_a",
"LISTPROP_2_FOO" to "value_abc",
)
},
)
)
.build()
.loadConfigOrThrow<TestConfig>()

config shouldBe TestConfig(listOf(
ListEntry("value_a"),
ListEntry("value_abc"),
))
}

test("env var source should respect config aliases that need to be normalized to match") {
data class TestConfig(@ConfigAlias("fooBar") val bazBar: String)

Expand Down

0 comments on commit d572317

Please sign in to comment.