Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f2dd4e5
Move repeating Gradle config to the root project
bencehornak Jun 6, 2025
515dffa
Add OfrepProvider from go-feature-flag-kotlin-provider
bencehornak Jun 6, 2025
1707219
Extract getResourceAsString()
bencehornak Jun 6, 2025
e14a616
Upgrade to latest OpenFeature kotlin-sdk
bencehornak Jun 6, 2025
e7f4392
Fix test flakiness
bencehornak Jun 6, 2025
07a07d6
Fix error handling
bencehornak Jun 6, 2025
cfd6d46
Remove redundant suspend keywords
bencehornak Jun 7, 2025
a8d2fac
Use MockWebServer as a jUnit External Resource
bencehornak Jun 7, 2025
acebf32
Make the API more idiomatic to Kotlin
bencehornak Jun 7, 2025
4ea8334
Rename base package
bencehornak Jun 7, 2025
ec61be4
Replace OkHttp + GSON with Ktor + kontlinx-serialization
bencehornak Jun 9, 2025
7457e1a
Upgrade the OpenFeature Kotlin SDK to 0.5.0
bencehornak Jun 14, 2025
65b434d
Add JVM target
bencehornak Jun 14, 2025
cdd7b43
Replace okhttp mockwebserver with ktor client mock
bencehornak Jun 14, 2025
bd5a65c
Replace jUnit with kotlin-test
bencehornak Jun 14, 2025
d0cecf0
Replace Java UUID with Kotlin Uuid
bencehornak Jun 14, 2025
507678b
Shut down OpenFeatureAPI after each test case
bencehornak Jun 18, 2025
dd91e58
Remove/replace runBlocking
bencehornak Jun 19, 2025
93810f3
Replace Java scheduling with Kotlin one
bencehornak Jun 19, 2025
c2d1afa
Convert resource files into const val fields
bencehornak Jun 20, 2025
b1db377
Target multiple platforms
bencehornak Jun 23, 2025
4b2ba6f
Merge remote-tracking branch 'origin/main' into ofrep-provider
bencehornak Aug 27, 2025
87870db
Add apiDump
bencehornak Aug 27, 2025
a895a3f
Fix ktlint issue
bencehornak Aug 27, 2025
98ccba5
Add basic integration tests
bencehornak Aug 30, 2025
fe27c90
Fix context serialization
bencehornak Aug 30, 2025
5876240
Use different ktor implementation for JS
bencehornak Aug 30, 2025
a992cac
Merge branch 'main' into ofrep-provider
bencehornak Aug 30, 2025
efc6b40
Merge remote-tracking branch 'origin/main' into ofrep-provider
bencehornak Sep 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
implementation(plugin(libs.plugins.kotlin.multiplatform))
implementation(plugin(libs.plugins.dokka))
implementation(plugin(libs.plugins.vanniktech.maven.publish))
implementation(plugin(libs.plugins.android.library))
}

/**
Expand All @@ -16,4 +17,4 @@ dependencies {
* https://docs.gradle.org/current/userguide/version_catalogs.html#sec:buildsrc-version-catalog
*/
private fun DependencyHandlerScope.plugin(plugin: Provider<PluginDependency>) =
plugin.map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" }
plugin.map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" }
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
version=0.1.0
org.gradle.configuration-cache=true
org.gradle.jvmargs=-Xmx1g "-XX:MaxMetaspaceSize=768m

# This seems to be necessary for Coroutines to work on JS. Otherwise getting the following error:
# > Task :providers:env-var:compileTestDevelopmentExecutableKotlinJs FAILED
Expand Down
11 changes: 11 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
kotlin = "2.2.10"
kotlinx-coroutines = "1.10.2"
open-feature-kotlin-sdk = "0.6.2"
android = "8.10.1"
ktor = "3.1.3"

[libraries]
ktor-core = { module="io.ktor:ktor-client-core", version.ref="ktor" }
ktor-cio = { module="io.ktor:ktor-client-cio", version.ref="ktor" }
ktor-js = { module="io.ktor:ktor-client-js", version.ref="ktor" }
ktor-client-content-negotiation = { module="io.ktor:ktor-client-content-negotiation", version.ref="ktor" }
ktor-serialization-kotlinx-json = { module="io.ktor:ktor-serialization-kotlinx-json", version.ref="ktor" }
ktor-client-mock = { module="io.ktor:ktor-client-mock", version.ref="ktor" }
openfeature-kotlin-sdk = { group="dev.openfeature", name="kotlin-sdk", version.ref="open-feature-kotlin-sdk" }
kotlin-test = { group="org.jetbrains.kotlin", name="kotlin-test", version.ref="kotlin" }
kotlinx-coroutines-core = { group="org.jetbrains.kotlinx", name="kotlinx-coroutines-core", version.ref="kotlinx-coroutines" }
Expand All @@ -13,7 +21,10 @@ kotlinx-coroutines-test = { group="org.jetbrains.kotlinx", name="kotlinx-corouti
dokka = { id="org.jetbrains.dokka", version="2.0.0" }
kotlin-multiplatform = { id="org.jetbrains.kotlin.multiplatform", version.ref="kotlin" }
kotlinx-atomicfu = { id="org.jetbrains.kotlinx.atomicfu", version="0.29.0" }
kotlinx-serialization = { id="org.jetbrains.kotlin.plugin.serialization", version="2.1.21" }
ktlint = { id="org.jlleitschuh.gradle.ktlint", version="13.1.0" }
nexus-publish = { id="io.github.gradle-nexus.publish-plugin", version="2.0.0" }
binary-compatibility-validator = { id="org.jetbrains.kotlinx.binary-compatibility-validator", version="0.18.1" }
vanniktech-maven-publish = { id="com.vanniktech.maven.publish", version="0.34.0" }
android-library = { id="com.android.library", version.ref="android" }
docker-compose = { id="com.avast.gradle.docker-compose", version="0.17.12" }
1,640 changes: 1,633 additions & 7 deletions kotlin-js-store/yarn.lock

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions providers/ofrep/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Kotlin OFREP Provider

This provider is designed to use the [OpenFeature Remote Evaluation Protocol (OFREP)](https://openfeature.dev/specification/appendix-c).

## Supported platforms

| Supported | Platform | Supported versions |
|-----------|----------------------|--------------------------------------------------------------------------------|
| ✅ | Android | |
| ✅ | JVM | JDK 11+ |
| ✅ | Native | Linux x64 |
| ❌ | Native | [Other native targets](https://kotlinlang.org/docs/native-target-support.html) |
| ✅ | Javascript (Node.js) | |
| ✅ | Javascript (Browser) | |
| ❌ | Wasm | |


## Installation

```kotlin
implementation("dev.openfeature.kotlin.contrib.providers:ofrep:0.1.0")
```

## Usage

To use the `OfrepProvider` create an instance and use it as a provider:

```kotlin
val options = OfrepOptions(endpoint="https://localhost:8080")
val provider = OfrepProvider(options)
OpenFeatureAPI.setProviderAndWait(provider)
```
42 changes: 42 additions & 0 deletions providers/ofrep/api/android/ofrep.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
public final class dev/openfeature/kotlin/contrib/providers/ofrep/OfrepProvider : dev/openfeature/kotlin/sdk/FeatureProvider {
public fun <init> (Ldev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions;)V
public fun getBooleanEvaluation (Ljava/lang/String;ZLdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getDoubleEvaluation (Ljava/lang/String;DLdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getHooks ()Ljava/util/List;
public fun getIntegerEvaluation (Ljava/lang/String;ILdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
public fun getObjectEvaluation (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getStringEvaluation (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun initialize (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun observe ()Lkotlinx/coroutines/flow/Flow;
public fun onContextSet (Ldev/openfeature/kotlin/sdk/EvaluationContext;Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun shutdown ()V
public fun track (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;Ldev/openfeature/kotlin/sdk/TrackingEventDetails;)V
}

public final class dev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions {
public synthetic fun <init> (Ljava/lang/String;JIJLjava/util/Map;JLio/ktor/client/engine/HttpClientEngine;Lkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;JIJLjava/util/Map;JLio/ktor/client/engine/HttpClientEngine;Lkotlinx/coroutines/CoroutineDispatcher;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2-UwyO8pc ()J
public final fun component3 ()I
public final fun component4-UwyO8pc ()J
public final fun component5 ()Ljava/util/Map;
public final fun component6-UwyO8pc ()J
public final fun component7 ()Lio/ktor/client/engine/HttpClientEngine;
public final fun component8 ()Lkotlinx/coroutines/CoroutineDispatcher;
public final fun copy-Sx1y28s (Ljava/lang/String;JIJLjava/util/Map;JLio/ktor/client/engine/HttpClientEngine;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions;
public static synthetic fun copy-Sx1y28s$default (Ldev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions;Ljava/lang/String;JIJLjava/util/Map;JLio/ktor/client/engine/HttpClientEngine;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions;
public fun equals (Ljava/lang/Object;)Z
public final fun getEndpoint ()Ljava/lang/String;
public final fun getHeaders ()Ljava/util/Map;
public final fun getHttpClientEngine ()Lio/ktor/client/engine/HttpClientEngine;
public final fun getKeepAliveDuration-UwyO8pc ()J
public final fun getMaxIdleConnections ()I
public final fun getPollingDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;
public final fun getPollingInterval-UwyO8pc ()J
public final fun getTimeout-UwyO8pc ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

42 changes: 42 additions & 0 deletions providers/ofrep/api/jvm/ofrep.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
public final class dev/openfeature/kotlin/contrib/providers/ofrep/OfrepProvider : dev/openfeature/kotlin/sdk/FeatureProvider {
public fun <init> (Ldev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions;)V
public fun getBooleanEvaluation (Ljava/lang/String;ZLdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getDoubleEvaluation (Ljava/lang/String;DLdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getHooks ()Ljava/util/List;
public fun getIntegerEvaluation (Ljava/lang/String;ILdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
public fun getObjectEvaluation (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getStringEvaluation (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun initialize (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun observe ()Lkotlinx/coroutines/flow/Flow;
public fun onContextSet (Ldev/openfeature/kotlin/sdk/EvaluationContext;Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun shutdown ()V
public fun track (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;Ldev/openfeature/kotlin/sdk/TrackingEventDetails;)V
}

public final class dev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions {
public synthetic fun <init> (Ljava/lang/String;JIJLjava/util/Map;JLio/ktor/client/engine/HttpClientEngine;Lkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;JIJLjava/util/Map;JLio/ktor/client/engine/HttpClientEngine;Lkotlinx/coroutines/CoroutineDispatcher;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2-UwyO8pc ()J
public final fun component3 ()I
public final fun component4-UwyO8pc ()J
public final fun component5 ()Ljava/util/Map;
public final fun component6-UwyO8pc ()J
public final fun component7 ()Lio/ktor/client/engine/HttpClientEngine;
public final fun component8 ()Lkotlinx/coroutines/CoroutineDispatcher;
public final fun copy-Sx1y28s (Ljava/lang/String;JIJLjava/util/Map;JLio/ktor/client/engine/HttpClientEngine;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions;
public static synthetic fun copy-Sx1y28s$default (Ldev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions;Ljava/lang/String;JIJLjava/util/Map;JLio/ktor/client/engine/HttpClientEngine;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/openfeature/kotlin/contrib/providers/ofrep/bean/OfrepOptions;
public fun equals (Ljava/lang/Object;)Z
public final fun getEndpoint ()Ljava/lang/String;
public final fun getHeaders ()Ljava/util/Map;
public final fun getHttpClientEngine ()Lio/ktor/client/engine/HttpClientEngine;
public final fun getKeepAliveDuration-UwyO8pc ()J
public final fun getMaxIdleConnections ()I
public final fun getPollingDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;
public final fun getPollingInterval-UwyO8pc ()J
public final fun getTimeout-UwyO8pc ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

107 changes: 107 additions & 0 deletions providers/ofrep/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinTest

plugins {
id("com.android.library")
id("dev.openfeature.provider-conventions")
alias(libs.plugins.kotlinx.serialization)
// Needed for the JS coroutine support for the tests
alias(libs.plugins.kotlinx.atomicfu)
alias(libs.plugins.docker.compose)
}

kotlin {
androidTarget()
jvm {
compilations.all {
compileTaskProvider.configure {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
}
}
linuxX64 {}
js {
nodejs {}
browser {
testTask {
useKarma {
useChromeHeadless()
}
}
}
}

sourceSets {
val commonMain by getting {
dependencies {
api(libs.openfeature.kotlin.sdk)

api(libs.kotlinx.coroutines.core)
api(libs.ktor.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
}
}
commonTest.dependencies {
implementation(libs.kotlin.test)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.ktor.client.mock)
}
val nonJsMain by creating {
dependsOn(commonMain)
dependencies {
implementation(libs.ktor.cio)
}
}
androidMain.get().dependsOn(nonJsMain)
jvmMain.get().dependsOn(nonJsMain)
linuxX64Main.get().dependsOn(nonJsMain)

jsMain.dependencies {
implementation(libs.ktor.js)
}
}
}

android {
namespace = "dev.openfeature.kotlin.contrib.providers.ofrep"
compileSdk = 35

defaultConfig {
minSdk = 21
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

publishing {
singleVariant("release") {
withJavadocJar()
withSourcesJar()
}
}

testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}

// Launch test container for IntegrationTest.kt
dockerCompose {
useComposeFiles = listOf("src/integrationTest/docker-compose.yaml")
}
// Note: some Kotlin test targets extend the Test (e.g. JVM), some others the KotlinTest class (e.g. Native, JS)
tasks.withType(Test::class) {
dependsOn(tasks.named("composeUp"))
finalizedBy(tasks.named("composeDown"))
}
tasks.withType(KotlinTest::class) {
dependsOn(tasks.named("composeUp"))
finalizedBy(tasks.named("composeDown"))
}
2 changes: 2 additions & 0 deletions providers/ofrep/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Disable the default source set hierarchy to allow creating the nonJsMain source set
kotlin.mpp.applyDefaultHierarchyTemplate=false
Loading