{
+ val value: String =
+ environmentGateway.getEnvironmentVariable(keyTransformer.transformKey(key))
+ ?: throw OpenFeatureError.FlagNotFoundError(key)
+
+ try {
+ return ProviderEvaluation(
+ value = parse(value),
+ reason = Reason.STATIC.toString(),
+ )
+ } catch (e: Exception) {
+ throw OpenFeatureError.ParseError(e.message ?: "Unknown parsing error")
+ }
+ }
+
+ companion object {
+ private val NAME: String = "Environment Variables Provider"
+ }
+
+ private object Metadata : ProviderMetadata {
+ override val name: String
+ get() = NAME
+ }
+}
diff --git a/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentGateway.kt b/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentGateway.kt
new file mode 100644
index 0000000..e2b233e
--- /dev/null
+++ b/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentGateway.kt
@@ -0,0 +1,11 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+/**
+ * This is an abstraction to fetch environment variables. It can be used to support
+ * environment-specific access or provide additional functionality, like prefixes, casing and even
+ * sources like spring configurations which come from different sources. Also, a test double could
+ * implement this interface, making the tests independent of the actual environment.
+ */
+fun interface EnvironmentGateway {
+ fun getEnvironmentVariable(key: String): String?
+}
diff --git a/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentKeyTransformer.kt b/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentKeyTransformer.kt
new file mode 100644
index 0000000..a358f6f
--- /dev/null
+++ b/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentKeyTransformer.kt
@@ -0,0 +1,81 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+/**
+ * This class provides a way to transform any given key to another value. This is helpful, if keys in the code have a
+ * different representation as in the actual environment, e.g. SCREAMING_SNAKE_CASE env vars vs. hyphen-case keys
+ * for feature flags.
+ *
+ *
+ * This class also supports chaining/combining different transformers incl. self-written ones by providing
+ * a transforming function in the constructor.
+ * Currently, the following transformations are supported out of the box:
+ *
+ * * [converting to lower case][.toLowerCaseTransformer]
+ * * [converting to UPPER CASE][.toUpperCaseTransformer]
+ * * [converting hyphen-case to SCREAMING_SNAKE_CASE][.hyphenCaseToScreamingSnake]
+ * * [convert to camelCase][.toCamelCaseTransformer]
+ * * [replace '_' with '.'][.replaceUnderscoreWithDotTransformer]
+ * * [replace '.' with '_'][.replaceDotWithUnderscoreTransformer]
+ *
+ *
+ *
+ * **Examples:**
+ *
+ *
+ * 1. hyphen-case feature flag names to screaming snake-case environment variables:
+ *
+ * `// Definition of the EnvVarProvider:
+ * EnvironmentKeyTransformer transformer = EnvironmentKeyTransformer
+ * .hyphenCaseToScreamingSnake();
+ *
+ * FeatureProvider provider = new EnvVarProvider(transformer);
+` *
+
*
+ * 2. chained/composed transformations:
+ *
+ * `// Definition of the EnvVarProvider:
+ * EnvironmentKeyTransformer transformer = EnvironmentKeyTransformer
+ * .toLowerCaseTransformer()
+ * .andThen(EnvironmentKeyTransformer.replaceUnderscoreWithDotTransformer());
+ *
+ * FeatureProvider provider = new EnvVarProvider(transformer);
+` *
+
*
+ * 3. freely defined transformation function:
+ *
+ * `// Definition of the EnvVarProvider:
+ * EnvironmentKeyTransformer transformer = new EnvironmentKeyTransformer(key -> "constant");
+ *
+ * FeatureProvider provider = new EnvVarProvider(keyTransformer);
+` *
+
*
+ */
+fun interface EnvironmentKeyTransformer {
+ fun transformKey(key: String): String
+
+ fun andThen(another: EnvironmentKeyTransformer): EnvironmentKeyTransformer =
+ EnvironmentKeyTransformer { key ->
+ another.transformKey(
+ this.transformKey(key),
+ )
+ }
+
+ companion object {
+ fun toLowerCaseTransformer(): EnvironmentKeyTransformer = EnvironmentKeyTransformer { key -> key.lowercase() }
+
+ fun toUpperCaseTransformer(): EnvironmentKeyTransformer = EnvironmentKeyTransformer { key -> key.uppercase() }
+
+ fun toCamelCaseTransformer(): EnvironmentKeyTransformer = EnvironmentKeyTransformer { key -> key.camelCase() }
+
+ fun replaceUnderscoreWithDotTransformer(): EnvironmentKeyTransformer = EnvironmentKeyTransformer { key -> key.replace('_', '.') }
+
+ fun replaceDotWithUnderscoreTransformer(): EnvironmentKeyTransformer = EnvironmentKeyTransformer { key -> key.replace('.', '_') }
+
+ fun hyphenCaseToScreamingSnake(): EnvironmentKeyTransformer =
+ EnvironmentKeyTransformer { key ->
+ key.replace('-', '_').uppercase()
+ }
+
+ fun doNothing(): EnvironmentKeyTransformer = EnvironmentKeyTransformer { s -> s }
+ }
+}
diff --git a/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.kt b/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.kt
new file mode 100644
index 0000000..b3dc56b
--- /dev/null
+++ b/providers/env-var/src/commonMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.kt
@@ -0,0 +1,3 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+internal expect fun platformSpecificEnvironmentGateway(): EnvironmentGateway
diff --git a/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvVarProviderE2eTest.kt b/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvVarProviderE2eTest.kt
new file mode 100644
index 0000000..577a22f
--- /dev/null
+++ b/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvVarProviderE2eTest.kt
@@ -0,0 +1,55 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+import dev.openfeature.sdk.Client
+import dev.openfeature.sdk.OpenFeatureAPI
+import dev.openfeature.sdk.Reason
+import kotlinx.coroutines.test.runTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+// The TEST_ENVIRONMENT_VARIABLE environment variable is defined in `build.gradle.kts`
+private const val EXISTING_FEATURE_FLAG_NAME = "test.environment.variable"
+private const val EXISTING_FEATURE_FLAG_VALUE = "foo"
+private const val NON_EXISTING_FEATURE_FLAG_NAME = "non.existing.test.environment.variable"
+private const val DEFAULT_VALUE = "default value"
+
+class EnvVarProviderE2eTest {
+ // Converts `test.environment.variable` to `TEST_ENVIRONMENT_VARIABLE`
+ private val environmentKeyTransformer =
+ EnvironmentKeyTransformer
+ .toUpperCaseTransformer()
+ .andThen(EnvironmentKeyTransformer.replaceDotWithUnderscoreTransformer())
+
+ private val provider =
+ EnvVarProvider(
+ platformSpecificEnvironmentGateway(),
+ environmentKeyTransformer,
+ )
+
+ @Test
+ fun `should return feature flag value if present`() =
+ withClient { client ->
+ val result = client.getStringDetails(EXISTING_FEATURE_FLAG_NAME, DEFAULT_VALUE)
+ assertEquals(EXISTING_FEATURE_FLAG_VALUE, result.value)
+ assertEquals(Reason.STATIC.toString(), result.reason)
+ }
+
+ @Test
+ fun `should return default value if feature flag is not present`() =
+ withClient { client ->
+ val result = client.getStringDetails(NON_EXISTING_FEATURE_FLAG_NAME, DEFAULT_VALUE)
+ assertEquals(DEFAULT_VALUE, result.value)
+ assertEquals(Reason.ERROR.toString(), result.reason)
+ }
+
+ private fun withClient(callback: (client: Client) -> Unit) =
+ runTest {
+ try {
+ OpenFeatureAPI.setProviderAndWait(provider)
+ val client = OpenFeatureAPI.getClient()
+ callback(client)
+ } finally {
+ OpenFeatureAPI.shutdown()
+ }
+ }
+}
diff --git a/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvVarProviderTest.kt b/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvVarProviderTest.kt
new file mode 100644
index 0000000..4f9dddb
--- /dev/null
+++ b/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvVarProviderTest.kt
@@ -0,0 +1,302 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+import dev.openfeature.sdk.FeatureProvider
+import dev.openfeature.sdk.ImmutableContext
+import dev.openfeature.sdk.ProviderEvaluation
+import dev.openfeature.sdk.Reason
+import dev.openfeature.sdk.Value
+import dev.openfeature.sdk.exceptions.OpenFeatureError
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+
+private val REASON_STATIC = Reason.STATIC.toString()
+
+internal class EnvVarProviderTest {
+ @Test
+ fun `should throw on getObjectEvaluation`() {
+ assertFailsWith {
+ EnvVarProvider()
+ .getObjectEvaluation("any-key", Value.Null, ImmutableContext())
+ }
+ }
+
+ @Test
+ fun `should evaluate true as Boolean correctly`() =
+ evaluationTest(
+ "bool_true",
+ "true",
+ {
+ getBooleanEvaluation(
+ "bool_true",
+ false,
+ null,
+ )
+ },
+ { evaluation ->
+ evaluationChecks(
+ evaluation,
+ REASON_STATIC,
+ true,
+ )
+ },
+ )
+
+ @Test
+ fun `should evaluate false as Boolean correctly`() =
+ evaluationTest(
+ "bool_false",
+ "FaLsE",
+ {
+ getBooleanEvaluation(
+ "bool_false",
+ true,
+ null,
+ )
+ },
+ { evaluation ->
+ evaluationChecks(
+ evaluation,
+ REASON_STATIC,
+ false,
+ )
+ },
+ )
+
+ @Test
+ fun `should evaluate unrecognized Boolean as Boolean false correctly`() =
+ evaluationTest(
+ "bool_false",
+ "not-a-bool",
+ {
+ getBooleanEvaluation(
+ "bool_false",
+ true,
+ null,
+ )
+ },
+ { evaluation ->
+ evaluationChecks(
+ evaluation,
+ REASON_STATIC,
+ false,
+ )
+ },
+ )
+
+ @Test
+ fun `should evaluate String value correctly`() =
+ evaluationTest(
+ "string",
+ "value",
+ {
+ getStringEvaluation(
+ "string",
+ "",
+ null,
+ )
+ },
+ { evaluation ->
+ evaluationChecks(
+ evaluation,
+ REASON_STATIC,
+ "value",
+ )
+ },
+ )
+
+ @Test
+ fun `should evaluate Int value correctly`() =
+ evaluationTest(
+ "INT",
+ "42",
+ {
+ getIntegerEvaluation(
+ "INT",
+ 0,
+ null,
+ )
+ },
+ { evaluation ->
+ evaluationChecks(
+ evaluation,
+ REASON_STATIC,
+ 42,
+ )
+ },
+ )
+
+ @Test
+ fun `should evaluate Double value correctly`() =
+ evaluationTest(
+ "double",
+ "42.0",
+ {
+ getDoubleEvaluation(
+ "double",
+ 0.0,
+ null,
+ )
+ },
+ { evaluation ->
+ evaluationChecks(
+ evaluation,
+ REASON_STATIC,
+ 42.0,
+ )
+ },
+ )
+
+ @Test
+ fun `should throw FlagNotFound on missing Boolean env`() =
+ throwingEvaluationTest(
+ "other",
+ "other",
+ ) {
+ getBooleanEvaluation(
+ "bool_default",
+ true,
+ null,
+ )
+ }
+
+ @Test
+ fun `should throw FlagNotFound on missing String env`() =
+ throwingEvaluationTest(
+ "other",
+ "other",
+ ) {
+ getStringEvaluation(
+ "string_default",
+ "value",
+ null,
+ )
+ }
+
+ @Test
+ fun `should throw FlagNotFound on missing Int env`() =
+ throwingEvaluationTest(
+ "other",
+ "other",
+ ) {
+ getIntegerEvaluation(
+ "int_default",
+ 42,
+ null,
+ )
+ }
+
+ @Test
+ fun `should throw FlagNotFound on missing Double env`() =
+ throwingEvaluationTest(
+ "other",
+ "other",
+ ) {
+ getDoubleEvaluation(
+ "double_default",
+ 42.0,
+ null,
+ )
+ }
+
+ @Test
+ fun `should throw on unparseable values`() =
+ throwingEvaluationTest(
+ "int_incorrect",
+ "fourty-two",
+ ) {
+ getIntegerEvaluation(
+ "int_incorrect",
+ 0,
+ null,
+ )
+ }
+
+ @Test
+ fun `should throw FlagNotFound on missing double_incorrect env`() =
+ throwingEvaluationTest(
+ "double_incorrect",
+ "fourty-two",
+ ) {
+ getDoubleEvaluation(
+ "double_incorrect",
+ 0.0,
+ null,
+ )
+ }
+
+ @Test
+ fun `should transform key if configured`() {
+ val key = "key.transformed"
+ val expected = "key_transformed"
+
+ val transformer = EnvironmentKeyTransformer.replaceDotWithUnderscoreTransformer()
+ val gateway = EnvironmentGateway { s -> s }
+ val provider = EnvVarProvider(gateway, transformer)
+
+ val environmentVariableValue =
+ provider.getStringEvaluation(key, "failed", null).value
+
+ assertEquals(expected, environmentVariableValue)
+ }
+
+ @Test
+ fun `should use passed-in EnvironmentGateway`() {
+ val testFake = EnvironmentGateway { s -> "true" }
+
+ val provider = EnvVarProvider(testFake)
+
+ val actual: Boolean = provider.getBooleanEvaluation("any key", false, null).value
+
+ assertTrue(actual)
+ }
+
+ private fun evaluationTest(
+ variableName: String,
+ value: String,
+ callback: FeatureProvider.() -> ProviderEvaluation,
+ checks: (ProviderEvaluation) -> Unit,
+ ) {
+ // Given
+ val provider = provider(variableName, value)
+
+ // When
+ val evaluation = callback(provider)
+
+ // Then
+ checks(evaluation)
+ }
+
+ private inline fun throwingEvaluationTest(
+ variableName: String?,
+ value: String,
+ callback: FeatureProvider.() -> ProviderEvaluation,
+ ) {
+ // Given
+ val provider: FeatureProvider = provider(variableName, value)
+
+ // Then
+ assertFailsWith {
+ // When
+ callback(provider)
+ }
+ }
+
+ private fun provider(
+ variableName: String?,
+ value: String,
+ ): FeatureProvider =
+ EnvVarProvider(
+ environmentGateway = { name -> if (name == variableName) value else null },
+ )
+
+ private fun evaluationChecks(
+ evaluation: ProviderEvaluation,
+ reason: String?,
+ expected: T?,
+ ) {
+ assertEquals(reason, evaluation.reason)
+ assertEquals(expected, evaluation.value)
+ }
+}
diff --git a/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentKeyTransformerTest.kt b/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentKeyTransformerTest.kt
new file mode 100644
index 0000000..db67445
--- /dev/null
+++ b/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/EnvironmentKeyTransformerTest.kt
@@ -0,0 +1,154 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+internal class EnvironmentKeyTransformerTest {
+ @Test
+ fun `should transform keys to lower case prior delegating call to actual gateway`() {
+ listOf(
+ "" to "",
+ "a" to "a",
+ "A" to "a",
+ "ABC_DEF_GHI" to "abc_def_ghi",
+ "ABC.DEF.GHI" to "abc.def.ghi",
+ "aBc" to "abc",
+ ).forEach { testCase ->
+ val (originalKey, expectedTransformedKey) = testCase
+
+ val actual = EnvironmentKeyTransformer.toLowerCaseTransformer().transformKey(originalKey)
+
+ assertEquals(expectedTransformedKey, actual)
+ }
+ }
+
+ @Test
+ fun `should transform keys to upper case prior delegating call to actual gateway`() {
+ listOf(
+ "" to "",
+ "a" to "A",
+ "A" to "A",
+ "ABC_DEF_GHI" to "ABC_DEF_GHI",
+ "ABC.DEF.GHI" to "ABC.DEF.GHI",
+ "aBc" to "ABC",
+ ).forEach { testCase ->
+ val (originalKey, expectedTransformedKey) = testCase
+
+ val actual = EnvironmentKeyTransformer.toUpperCaseTransformer().transformKey(originalKey)
+
+ assertEquals(expectedTransformedKey, actual)
+ }
+ }
+
+ @Test
+ fun `should not transform key when using doNothing transformer`() {
+ listOf(
+ "" to "",
+ "a" to "a",
+ "A" to "A",
+ "ABC_DEF_GHI" to "ABC_DEF_GHI",
+ "ABC.DEF.GHI" to "ABC.DEF.GHI",
+ "aBc" to "aBc",
+ ).forEach { testCase ->
+ val (originalKey, expectedTransformedKey) = testCase
+ val actual = EnvironmentKeyTransformer.doNothing().transformKey(originalKey)
+
+ assertEquals(expectedTransformedKey, actual)
+ }
+ }
+
+ @Test
+ fun `should transform keys to camel case prior delegating call to actual gateway`() {
+ listOf(
+ "" to "",
+ "a" to "a",
+ "A" to "a",
+ "ABC_DEF_GHI" to "abcDefGhi",
+ "ABC.DEF.GHI" to "abc.def.ghi",
+ "aBc" to "abc",
+ ).forEach { testCase ->
+ val (originalKey, expectedTransformedKey) = testCase
+ val transformingGateway =
+ EnvironmentKeyTransformer.toCamelCaseTransformer()
+ val actual = transformingGateway.transformKey(originalKey)
+
+ assertEquals(expectedTransformedKey, actual)
+ }
+ }
+
+ @Test
+ fun `should transform keys according to given transformation prior delegating call to actual gateway`() {
+ listOf(
+ "" to "_",
+ "a" to "_a",
+ "A" to "_A",
+ "ABC_DEF_GHI" to "_ABC_DEF_GHI",
+ "ABC.DEF.GHI" to "_ABC.DEF.GHI",
+ "aBc_abc" to "_aBc_abc",
+ ).forEach { testCase ->
+ val (originalKey, expectedTransformedKey) = testCase
+ val transformingGateway =
+ EnvironmentKeyTransformer { "_$it" }
+
+ val actual = transformingGateway.transformKey(originalKey)
+
+ assertEquals(expectedTransformedKey, actual)
+ }
+ }
+
+ @Test
+ fun `should compose transformers`() {
+ listOf(
+ "" to "",
+ "ABC_DEF_GHI" to "abc.def.ghi",
+ "ABC.DEF.GHI" to "abc.def.ghi",
+ "aBc" to "abc",
+ ).forEach { testCase ->
+ val (originalKey, expectedTransformedKey) = testCase
+ val transformingGateway =
+ EnvironmentKeyTransformer
+ .toLowerCaseTransformer()
+ .andThen(EnvironmentKeyTransformer.replaceUnderscoreWithDotTransformer())
+
+ val actual = transformingGateway.transformKey(originalKey)
+
+ assertEquals(expectedTransformedKey, actual)
+ }
+ }
+
+ @Test
+ fun `should support screaming snake to hyphen case keys`() {
+ listOf(
+ "" to "",
+ "abc-def-ghi" to "ABC_DEF_GHI",
+ "abc.def.ghi" to "ABC.DEF.GHI",
+ "abc" to "ABC",
+ ).forEach { testCase ->
+ val (originalKey, expectedTransformedKey) = testCase
+ val transformingGateway =
+ EnvironmentKeyTransformer.hyphenCaseToScreamingSnake()
+
+ val actual = transformingGateway.transformKey(originalKey)
+
+ assertEquals(expectedTransformedKey, actual)
+ }
+ }
+
+ @Test
+ fun `should support replacing dot with underscore`() {
+ listOf(
+ "" to "",
+ "abc-def-ghi" to "abc-def-ghi",
+ "abc.def.ghi" to "abc_def_ghi",
+ "abc" to "abc",
+ ).forEach { testCase ->
+ val (originalKey, expectedTransformedKey) = testCase
+ val transformingGateway =
+ EnvironmentKeyTransformer.replaceDotWithUnderscoreTransformer()
+
+ val actual = transformingGateway.transformKey(originalKey)
+
+ assertEquals(expectedTransformedKey, actual)
+ }
+ }
+}
diff --git a/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGatewayTest.kt b/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGatewayTest.kt
new file mode 100644
index 0000000..421354f
--- /dev/null
+++ b/providers/env-var/src/commonTest/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGatewayTest.kt
@@ -0,0 +1,26 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+// The TEST_ENVIRONMENT_VARIABLE environment variable is defined in `build.gradle.kts`
+private const val EXISTING_ENVIRONMENT_VARIABLE_NAME = "TEST_ENVIRONMENT_VARIABLE"
+private const val EXISTING_ENVIRONMENT_VARIABLE_VALUE = "foo"
+private const val NON_EXISTING_ENVIRONMENT_VARIABLE_NAME = "NON_EXISTING_ENVIRONMENT_VARIABLE"
+
+class PlatformSpecificEnvironmentGatewayTest {
+ @Test
+ fun `should return existing environment variable`() {
+ val gateway = platformSpecificEnvironmentGateway()
+ val result = gateway.getEnvironmentVariable(EXISTING_ENVIRONMENT_VARIABLE_NAME)
+ assertEquals(EXISTING_ENVIRONMENT_VARIABLE_VALUE, result)
+ }
+
+ @Test
+ fun `should return null for non-existing environment variable`() {
+ val gateway = platformSpecificEnvironmentGateway()
+ val result = gateway.getEnvironmentVariable(NON_EXISTING_ENVIRONMENT_VARIABLE_NAME)
+ assertNull(result)
+ }
+}
diff --git a/providers/env-var/src/jsMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.js.kt b/providers/env-var/src/jsMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.js.kt
new file mode 100644
index 0000000..00dcf26
--- /dev/null
+++ b/providers/env-var/src/jsMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.js.kt
@@ -0,0 +1,10 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+@JsModule("process")
+@JsNonModule
+external val process: dynamic
+
+actual fun platformSpecificEnvironmentGateway(): EnvironmentGateway =
+ EnvironmentGateway { key ->
+ process.env[key]?.toString()
+ }
diff --git a/providers/env-var/src/jvmMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.jvm.kt b/providers/env-var/src/jvmMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.jvm.kt
new file mode 100644
index 0000000..81ead22
--- /dev/null
+++ b/providers/env-var/src/jvmMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.jvm.kt
@@ -0,0 +1,6 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+actual fun platformSpecificEnvironmentGateway(): EnvironmentGateway =
+ EnvironmentGateway { key ->
+ System.getenv(key)
+ }
diff --git a/providers/env-var/src/nativeMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.native.kt b/providers/env-var/src/nativeMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.native.kt
new file mode 100644
index 0000000..f328d58
--- /dev/null
+++ b/providers/env-var/src/nativeMain/kotlin/dev/openfeature/kotlin/contrib/providers/envvar/PlatformSpecificEnvironmentGateway.native.kt
@@ -0,0 +1,11 @@
+package dev.openfeature.kotlin.contrib.providers.envvar
+
+import kotlinx.cinterop.ExperimentalForeignApi
+import kotlinx.cinterop.toKString
+import platform.posix.getenv
+
+actual fun platformSpecificEnvironmentGateway(): EnvironmentGateway =
+ @OptIn(ExperimentalForeignApi::class)
+ EnvironmentGateway { key ->
+ getenv(key)?.toKString()
+ }
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..f9c2c32
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "config:base"
+ ]
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..c11ce6f
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,10 @@
+rootProject.name = "open-feature-kotlin-sdk-contrib"
+
+dependencyResolutionManagement {
+ repositories {
+ mavenLocal()
+ mavenCentral()
+ }
+}
+
+include(":providers:env-var")