Skip to content

Commit 96568be

Browse files
committed
feat: add provider status to client (#205)
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
1 parent da9d2c8 commit 96568be

5 files changed

Lines changed: 69 additions & 1 deletion

File tree

kotlin-sdk/api/android/kotlin-sdk.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public abstract interface class dev/openfeature/kotlin/sdk/Client : dev/openfeat
1919
public abstract fun addHooks (Ljava/util/List;)V
2020
public abstract fun getHooks ()Ljava/util/List;
2121
public abstract fun getMetadata ()Ldev/openfeature/kotlin/sdk/ClientMetadata;
22+
public abstract fun getProviderStatus ()Ldev/openfeature/kotlin/sdk/OpenFeatureStatus;
2223
public abstract fun getStatusFlow ()Lkotlinx/coroutines/flow/Flow;
2324
}
2425

@@ -313,6 +314,7 @@ public final class dev/openfeature/kotlin/sdk/OpenFeatureClient : dev/openfeatur
313314
public fun getObjectDetails (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/FlagEvaluationOptions;)Ldev/openfeature/kotlin/sdk/FlagEvaluationDetails;
314315
public fun getObjectValue (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;)Ldev/openfeature/kotlin/sdk/Value;
315316
public fun getObjectValue (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/FlagEvaluationOptions;)Ldev/openfeature/kotlin/sdk/Value;
317+
public fun getProviderStatus ()Ldev/openfeature/kotlin/sdk/OpenFeatureStatus;
316318
public fun getStatusFlow ()Lkotlinx/coroutines/flow/Flow;
317319
public fun getStringDetails (Ljava/lang/String;Ljava/lang/String;)Ldev/openfeature/kotlin/sdk/FlagEvaluationDetails;
318320
public fun getStringDetails (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/FlagEvaluationOptions;)Ldev/openfeature/kotlin/sdk/FlagEvaluationDetails;

kotlin-sdk/api/jvm/kotlin-sdk.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public abstract interface class dev/openfeature/kotlin/sdk/Client : dev/openfeat
1919
public abstract fun addHooks (Ljava/util/List;)V
2020
public abstract fun getHooks ()Ljava/util/List;
2121
public abstract fun getMetadata ()Ldev/openfeature/kotlin/sdk/ClientMetadata;
22+
public abstract fun getProviderStatus ()Ldev/openfeature/kotlin/sdk/OpenFeatureStatus;
2223
public abstract fun getStatusFlow ()Lkotlinx/coroutines/flow/Flow;
2324
}
2425

@@ -313,6 +314,7 @@ public final class dev/openfeature/kotlin/sdk/OpenFeatureClient : dev/openfeatur
313314
public fun getObjectDetails (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/FlagEvaluationOptions;)Ldev/openfeature/kotlin/sdk/FlagEvaluationDetails;
314315
public fun getObjectValue (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;)Ldev/openfeature/kotlin/sdk/Value;
315316
public fun getObjectValue (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/FlagEvaluationOptions;)Ldev/openfeature/kotlin/sdk/Value;
317+
public fun getProviderStatus ()Ldev/openfeature/kotlin/sdk/OpenFeatureStatus;
316318
public fun getStatusFlow ()Lkotlinx/coroutines/flow/Flow;
317319
public fun getStringDetails (Ljava/lang/String;Ljava/lang/String;)Ldev/openfeature/kotlin/sdk/FlagEvaluationDetails;
318320
public fun getStringDetails (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/FlagEvaluationOptions;)Ldev/openfeature/kotlin/sdk/FlagEvaluationDetails;

kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/Client.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,9 @@ interface Client : Features, Tracking {
88
val statusFlow: Flow<OpenFeatureStatus>
99

1010
fun addHooks(hooks: List<Hook<*>>)
11+
12+
/**
13+
* Get the current [OpenFeatureStatus] of the Provider handling this client's evaluations.
14+
*/
15+
fun getProviderStatus(): OpenFeatureStatus
1116
}

kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureClient.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ class OpenFeatureClient(
2626

2727
override val statusFlow = openFeatureAPI.statusFlow
2828

29+
override fun getProviderStatus(): OpenFeatureStatus {
30+
return openFeatureAPI.getStatus()
31+
}
32+
2933
override fun getBooleanValue(key: String, defaultValue: Boolean): Boolean {
3034
return getBooleanDetails(key, defaultValue).value
3135
}
@@ -219,7 +223,7 @@ class OpenFeatureClient(
219223
}
220224

221225
private fun shortCircuitIfNotReady() {
222-
val providerStatus = openFeatureAPI.getStatus()
226+
val providerStatus = getProviderStatus()
223227
if (providerStatus == OpenFeatureStatus.NotReady) {
224228
throw OpenFeatureError.ProviderNotReadyError()
225229
} else if (providerStatus is OpenFeatureStatus.Fatal) {

kotlin-sdk/src/commonTest/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureClientTests.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package dev.openfeature.kotlin.sdk
22

3+
import dev.openfeature.kotlin.sdk.helpers.BrokenInitProvider
34
import dev.openfeature.kotlin.sdk.helpers.GenericSpyHookMock
5+
import dev.openfeature.kotlin.sdk.helpers.SlowProvider
6+
import kotlinx.coroutines.ExperimentalCoroutinesApi
7+
import kotlinx.coroutines.test.StandardTestDispatcher
8+
import kotlinx.coroutines.test.advanceTimeBy
9+
import kotlinx.coroutines.test.runCurrent
410
import kotlinx.coroutines.test.runTest
511
import kotlin.test.AfterTest
612
import kotlin.test.Test
713
import kotlin.test.assertEquals
14+
import kotlin.test.assertTrue
815

916
class OpenFeatureClientTests {
1017

@@ -20,4 +27,52 @@ class OpenFeatureClientTests {
2027
val stringValue = OpenFeatureAPI.getClient().getStringValue("test", "defaultTest")
2128
assertEquals(stringValue, "defaultTest")
2229
}
30+
31+
/**
32+
* Spec 1.7.1: The client MUST define a provider status accessor.
33+
*/
34+
@Test
35+
fun testClientGetProviderStatusShouldReturnReadyWhenProviderIsInitialized() = runTest {
36+
OpenFeatureAPI.setProviderAndWait(NoOpProvider())
37+
val client = OpenFeatureAPI.getClient()
38+
assertEquals(OpenFeatureStatus.Ready, client.getProviderStatus())
39+
}
40+
41+
/**
42+
* Spec 1.7.1: The client MUST define a provider status accessor.
43+
*/
44+
@Test
45+
fun testClientGetProviderStatusShouldReturnErrorWhenProviderFailsToInitialize() = runTest {
46+
OpenFeatureAPI.setProviderAndWait(BrokenInitProvider())
47+
val client = OpenFeatureAPI.getClient()
48+
assertTrue(client.getProviderStatus() is OpenFeatureStatus.Error)
49+
}
50+
51+
/**
52+
* Spec 1.7.2.1: Provider status accessor must support RECONCILING state (static-context paradigm).
53+
*/
54+
@OptIn(ExperimentalCoroutinesApi::class)
55+
@Test
56+
fun testClientGetProviderStatusShouldReturnReconcilingWhileContextIsBeingUpdated() = runTest {
57+
val dispatcher = StandardTestDispatcher(testScheduler)
58+
val slowProvider = SlowProvider(dispatcher = dispatcher)
59+
OpenFeatureAPI.setProvider(slowProvider, dispatcher = dispatcher)
60+
61+
// Wait for SlowProvider initialized (2000ms delay)
62+
advanceTimeBy(2001)
63+
64+
val client = OpenFeatureAPI.getClient()
65+
assertEquals(OpenFeatureStatus.Ready, client.getProviderStatus())
66+
67+
// Trigger a context update (takes 2000ms natively)
68+
OpenFeatureAPI.setEvaluationContext(ImmutableContext(targetingKey = "user-123"), dispatcher)
69+
70+
// Run execution queue deterministically to ensure coroutine reaches emit(Reconciling)
71+
runCurrent()
72+
assertEquals(OpenFeatureStatus.Reconciling, client.getProviderStatus())
73+
74+
// Wait out the remaining 2000ms for slow context update finish
75+
advanceTimeBy(2001)
76+
assertEquals(OpenFeatureStatus.Ready, client.getProviderStatus())
77+
}
2378
}

0 commit comments

Comments
 (0)