Skip to content

Commit

Permalink
Support prefix in context resolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
sksamuel committed May 27, 2023
1 parent 0391dab commit edf34aa
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class AwsSecretsManagerResolverTest : FunSpec() {
client.createSecret(CreateSecretRequest().withName("foo").withSecretString("secret!"))
client.createSecret(CreateSecretRequest().withName("bubble").withSecretString("""{"f": "1", "g": "2"}"""))

test("placeholder should be detected and used") {
test("context pattern should be detected and used") {
val props = Properties()
props["a"] = "\${{ aws-secrets-manager:foo }}"
ConfigLoaderBuilder.create()
Expand All @@ -48,6 +48,17 @@ class AwsSecretsManagerResolverTest : FunSpec() {
.a.shouldBe("secret!")
}

test("prefix pattern should be detected and used") {
val props = Properties()
props["a"] = "aws-secrets-manager://foo"
ConfigLoaderBuilder.create()
.addResolver(createAwsSecretsManagerResolver { client })
.addPropertySource(PropsPropertySource(props))
.build()
.loadConfigOrThrow<ConfigHolder>()
.a.shouldBe("secret!")
}

test("unknown secret should return error and include key") {
val props = Properties()
props["a"] = "\${{ aws-secrets-manager:qwerty }}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import com.sksamuel.hoplite.fp.valid
* A [ContextResolver] applies substitutions to context patterns in a [StringNode]'s value.
*
* Context patterns are of the form ${{ context:path }} where `context` indicates the context
* resolver to use, and `path` is provided to that resolver for runtime resolution.
* resolver to use, and `path` is provided to that resolver for runtime resolution. Context
* patterns can be placed anywhere in a string, and can be nested.
*
* Additionally, we can use context://path to match a full string.
*
* For example, the [EnvVarContextResolver] will replace ${{ env:name }} with the env variable `name`,
* and the [SystemPropertyContextResolver] will replace ${{ sysprop:name }} with the system property `name`.
Expand All @@ -31,7 +34,11 @@ abstract class ContextResolver : Resolver {

// this regex will match most nested replacements first (inside to outside)
// redundant escaping required for Android support
fun regex() = "\\$\\{\\{\\s*$contextKey:([^{}]*)\\}\\}".toRegex()
private fun contextRegex() = "\\$\\{\\{\\s*$contextKey:([^{}]*)\\}\\}".toRegex()

// this regex will match if the context is a prefex
// redundant escaping required for Android support
private fun prefixRegex() = "$contextKey://(.*)".toRegex()

private val valueWithDefaultRegex = "(.+):-(.+)".toRegex()

Expand All @@ -57,7 +64,7 @@ abstract class ContextResolver : Resolver {

private fun resolve(node: StringNode, root: Node, context: DecoderContext): ConfigResult<StringNode> {

val result = regex().find(node.value) ?: return node.valid()
val result = prefixRegex().matchEntire(node.value) ?: contextRegex().find(node.value) ?: return node.valid()
val path = result.groupValues[1].trim()

val matchWithDefault = valueWithDefaultRegex.matchEntire(path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.extensions.system.withSystemProperty
import io.kotest.matchers.shouldBe

class SystemPropertyResolverTest : FunSpec({
class SystemPropertyContextResolverTest : FunSpec({

test("should find matches") {
test("should support context matches") {

val node = StringNode("boaty\${{sysprop:bar}}face", Pos.NoPos, DotPath.root)

Expand All @@ -21,6 +21,17 @@ class SystemPropertyResolverTest : FunSpec({
(config.getUnsafe() as StringNode).value shouldBe "boatymcboatface"
}

test("should support prefix matches") {

val node = StringNode("sysprop://bar", Pos.NoPos, DotPath.root)

val config = withSystemProperty(key = "bar", value = "mcboat") {
SystemPropertyContextResolver.resolve(node, node, DecoderContext.zero)
}

(config.getUnsafe() as StringNode).value shouldBe "mcboat"
}

test("support leading whitespace") {

val node = StringNode("boaty\${{ sysprop:bar}}face", Pos.NoPos, DotPath.root)
Expand Down

0 comments on commit edf34aa

Please sign in to comment.