-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add POSIX environment variable integration (#48)
- Loading branch information
1 parent
f44af02
commit 825c4d4
Showing
8 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
plugins { | ||
alias(libs.plugins.dokka) | ||
alias(libs.plugins.kotlin.multiplatform) | ||
alias(libs.plugins.publish) | ||
} | ||
|
||
kotlin { | ||
|
||
explicitApi() | ||
|
||
jvm() | ||
linuxArm64() | ||
linuxX64() | ||
macosArm64() | ||
macosX64() | ||
mingwX64() | ||
|
||
sourceSets { | ||
commonMain.dependencies { | ||
api(project(":core")) | ||
} | ||
commonTest.dependencies { | ||
implementation(libs.kotlin.test) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
POM_ARTIFACT_ID=environment-variable-integration | ||
POM_NAME=Monarch System Environment Variable Integration | ||
POM_DESCRIPTION=Multiplatform integration with environment variables |
63 changes: 63 additions & 0 deletions
63
.../io/github/kevincianfarini/monarch/environment/EnvironmentVariableFeatureFlagDataStore.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package io.github.kevincianfarini.monarch.environment | ||
|
||
import io.github.kevincianfarini.monarch.FeatureFlagDataStore | ||
|
||
/** | ||
* A [FeatureFlagDataStore] implementation that provides values from environment variables. | ||
*/ | ||
public class EnvironmentVariableFeatureFlagDataStore internal constructor( | ||
private val strictlyTyped: Boolean = true, | ||
private val getEnvironmentVariable: (String) -> String?, | ||
) : FeatureFlagDataStore { | ||
|
||
/** | ||
* Create an [EnvironmentVariableFeatureFlagDataStore] which reads from the environment. | ||
* If [strictlyTyped] is true, this store will throw exceptions when the raw string value | ||
* of the environment variable cannot be coerced to a specific type. Otherwise, this store | ||
* will return the default value. | ||
*/ | ||
public constructor(strictlyTyped: Boolean = true) : this(strictlyTyped, ::getSystemEnvVar) | ||
|
||
override fun getBoolean(key: String, default: Boolean): Boolean { | ||
val env = getEnvironmentVariable(key) | ||
val boolean = if (strictlyTyped) env?.toBooleanStrict() else env?.toBooleanStrictOrNull() | ||
return boolean ?: default | ||
} | ||
|
||
override fun getString(key: String, default: String): String { | ||
return getEnvironmentVariable(key) ?: default | ||
} | ||
|
||
override fun getDouble(key: String, default: Double): Double { | ||
val env = getEnvironmentVariable(key) | ||
val double = if (strictlyTyped) env?.toDouble() else env?.toDoubleOrNull() | ||
return double ?: default | ||
} | ||
|
||
override fun getLong(key: String, default: Long): Long { | ||
val env = getEnvironmentVariable(key) | ||
val long = if (strictlyTyped) env?.toLong() else env?.toLongOrNull() | ||
return long ?: default | ||
} | ||
|
||
override fun getByteArray(key: String, default: ByteArray): ByteArray { | ||
val env = getEnvironmentVariable(key) | ||
val bytes = if (strictlyTyped) env?.decodeHexToByteArray() else env?.decodeHexToByteArrayOrNull() | ||
return bytes ?: default | ||
} | ||
} | ||
|
||
private fun String.decodeHexToByteArrayOrNull(): ByteArray? = takeIf { it.length % 2 == 0 }?.let { string -> | ||
ByteArray(string.length / 2) { index -> | ||
val startIndex = index * 2 | ||
val endIndex = startIndex + 1 | ||
val byteString = string.substring(startIndex, endIndex + 1) | ||
byteString.toByte(16) | ||
} | ||
} | ||
|
||
private fun String.decodeHexToByteArray(): ByteArray { | ||
return requireNotNull(decodeHexToByteArrayOrNull()) { | ||
"The input string $this is not a valid hex string." | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
...riable/src/commonMain/kotlin/io/github/kevincianfarini/monarch/environment/environment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package io.github.kevincianfarini.monarch.environment | ||
|
||
internal expect fun getSystemEnvVar(key: String): String? |
131 changes: 131 additions & 0 deletions
131
...github/kevincianfarini/monarch/environment/EnvironmentVariableFeatureFlagDataStoreTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package io.github.kevincianfarini.monarch.environment | ||
|
||
import kotlin.test.* | ||
|
||
class EnvironmentVariableFeatureFlagDataStoreTest { | ||
|
||
@Test | ||
fun strictly_typed_boolean_returns_value() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "true" } | ||
assertTrue(store.getBoolean(key = "key", default = false)) | ||
} | ||
|
||
@Test | ||
fun strictly_typed_boolean_fails() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "tRuE" } | ||
assertFailsWith<IllegalArgumentException> { | ||
store.getBoolean(key = "key", default = false) | ||
} | ||
} | ||
|
||
@Test | ||
fun loosely_typed_boolean_returns_default_on_failure() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = false) { "tRuE" } | ||
assertFalse(store.getBoolean(key = "key", default = false)) | ||
} | ||
|
||
@Test | ||
fun no_underlying_value_boolean_returns_default() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = false) { null } | ||
assertFalse(store.getBoolean(key = "key", default = false)) | ||
} | ||
|
||
@Test | ||
fun string_returns_environment_variable() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "some string" } | ||
assertEquals("some string", store.getString(key = "key", default = "default")) | ||
} | ||
|
||
@Test | ||
fun string_returns_default_when_no_environment_variable() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { null } | ||
assertEquals("default", store.getString(key = "key", default = "default")) | ||
} | ||
|
||
@Test | ||
fun strictly_typed_double_returns_value() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "1.2" } | ||
assertEquals(1.2, store.getDouble(key = "key", default = 0.0)) | ||
} | ||
|
||
@Test | ||
fun strictly_typed_double_fails() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "Not a number" } | ||
assertFailsWith<NumberFormatException> { | ||
store.getDouble(key = "key", default = 0.0) | ||
} | ||
} | ||
|
||
@Test | ||
fun loosely_typed_double_returns_default_on_failure() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = false) { "Not a number" } | ||
assertEquals(0.0, store.getDouble(key = "key", default = 0.0)) | ||
} | ||
|
||
@Test | ||
fun no_underlying_value_double_returns_default() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { null } | ||
assertEquals(0.0, store.getDouble(key = "key", default = 0.0)) | ||
} | ||
|
||
@Test | ||
fun strictly_typed_long_returns_value() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "1000" } | ||
assertEquals(1000, store.getLong(key = "key", default = 0)) | ||
} | ||
|
||
@Test | ||
fun strictly_typed_long_fails() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "Not a number" } | ||
assertFailsWith<NumberFormatException> { | ||
store.getLong(key = "key", default = 0) | ||
} | ||
} | ||
|
||
@Test | ||
fun loosely_typed_long_returns_default_on_failure() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = false) { "Not a number" } | ||
assertEquals(0, store.getLong(key = "key", default = 0)) | ||
} | ||
|
||
@Test | ||
fun no_underlying_value_long_returns_default() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { null } | ||
assertEquals(0, store.getLong(key = "key", default = 0)) | ||
} | ||
|
||
@Test | ||
fun strictly_typed_byte_array_returns_value() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "0f00" } | ||
assertContentEquals( | ||
byteArrayOf(0x0f, 0x00), | ||
store.getByteArray(key = "key", default = byteArrayOf()) | ||
) | ||
} | ||
|
||
@Test | ||
fun strictly_typed_byte_array_fails() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { "0f0" } | ||
assertFailsWith<IllegalArgumentException> { | ||
store.getByteArray(key = "key", default = byteArrayOf()) | ||
} | ||
} | ||
|
||
@Test | ||
fun loosely_typed_byte_array_returns_default_on_failure() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = false) { "0f0" } | ||
assertContentEquals( | ||
byteArrayOf(), | ||
store.getByteArray(key = "key", default = byteArrayOf()) | ||
) | ||
} | ||
|
||
@Test | ||
fun no_underlying_value_byte_array_returns_default() { | ||
val store = EnvironmentVariableFeatureFlagDataStore(strictlyTyped = true) { null } | ||
assertContentEquals( | ||
byteArrayOf(), | ||
store.getByteArray(key = "key", default = byteArrayOf()) | ||
) | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
...iable/src/jvmMain/kotlin/io/github/kevincianfarini/monarch/environment/environment.jvm.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package io.github.kevincianfarini.monarch.environment | ||
|
||
internal actual fun getSystemEnvVar(key: String): String? { | ||
return System.getenv(key) | ||
} |
10 changes: 10 additions & 0 deletions
10
...src/nativeMain/kotlin/io/github/kevincianfarini/monarch/environment/environment.native.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package io.github.kevincianfarini.monarch.environment | ||
|
||
import kotlinx.cinterop.ExperimentalForeignApi | ||
import kotlinx.cinterop.toKString | ||
import platform.posix.getenv | ||
|
||
@OptIn(ExperimentalForeignApi::class) | ||
internal actual fun getSystemEnvVar(key: String): String? { | ||
return getenv(key)?.toKString() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters