Skip to content

Commit 75b793e

Browse files
committed
WIP: states added
Signed-off-by: Nicklas Lundin <[email protected]> Signed-off-by: Nicklas Lundin <[email protected]> fix: mark as throws Signed-off-by: Nicklas Lundin <[email protected]> Signed-off-by: Nicklas Lundin <[email protected]> Signed-off-by: Nicklas Lundin <[email protected]> Signed-off-by: Nicklas Lundin <[email protected]> fix: add extra buffer capacity for the status flow Signed-off-by: Nicklas Lundin <[email protected]>
1 parent 732bf79 commit 75b793e

16 files changed

+223
-519
lines changed

android/src/main/java/dev/openfeature/sdk/FeatureProvider.kt

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

3-
import dev.openfeature.sdk.events.EventObserver
4-
import dev.openfeature.sdk.events.ProviderStatus
3+
import dev.openfeature.sdk.exceptions.OpenFeatureError
4+
import kotlin.jvm.Throws
55

6-
interface FeatureProvider : EventObserver, ProviderStatus {
6+
interface FeatureProvider {
77
val hooks: List<Hook<*>>
88
val metadata: ProviderMetadata
99

10-
// Called by OpenFeatureAPI whenever the new Provider is registered
11-
// This function should never throw
12-
fun initialize(initialContext: EvaluationContext?)
10+
/**
11+
* Called by OpenFeatureAPI whenever the new Provider is registered
12+
* This function should block until ready and throw exceptions if it fails to initialize
13+
* @param initialContext any initial context to be set before the provider is ready
14+
*/
15+
@Throws(OpenFeatureError::class)
16+
suspend fun initialize(initialContext: EvaluationContext?)
1317

14-
// Called when the lifecycle of the OpenFeatureClient is over
15-
// to release resources/threads
18+
/**
19+
* Called when the lifecycle of the OpenFeatureClient is over to release resources/threads
20+
*/
1621
fun shutdown()
1722

18-
// Called by OpenFeatureAPI whenever a new EvaluationContext is set by the application
19-
fun onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext)
23+
/**
24+
* Called by OpenFeatureAPI whenever a new EvaluationContext is set by the application
25+
* Perform blocking work here until the provider is ready again or throws an exception
26+
* @param oldContext The old EvaluationContext
27+
* @param newContext The new EvaluationContext
28+
* @throws OpenFeatureError if the provider cannot perform the task
29+
*/
30+
@Throws(OpenFeatureError::class)
31+
suspend fun onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext)
32+
2033
fun getBooleanEvaluation(
2134
key: String,
2235
defaultValue: Boolean,

android/src/main/java/dev/openfeature/sdk/NoOpProvider.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
package dev.openfeature.sdk
22

3-
import dev.openfeature.sdk.events.OpenFeatureEvents
4-
import kotlinx.coroutines.flow.Flow
5-
import kotlinx.coroutines.flow.flowOf
6-
73
open class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureProvider {
84
override val metadata: ProviderMetadata = NoOpProviderMetadata("No-op provider")
9-
override fun initialize(initialContext: EvaluationContext?) {
5+
override suspend fun initialize(initialContext: EvaluationContext?) {
106
// no-op
117
}
128

139
override fun shutdown() {
1410
// no-op
1511
}
1612

17-
override fun onContextSet(
13+
override suspend fun onContextSet(
1814
oldContext: EvaluationContext?,
1915
newContext: EvaluationContext
2016
) {
@@ -61,9 +57,5 @@ open class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureP
6157
return ProviderEvaluation(defaultValue, "Passed in default", Reason.DEFAULT.toString())
6258
}
6359

64-
override fun observe(): Flow<OpenFeatureEvents> = flowOf()
65-
66-
override fun getProviderStatus(): OpenFeatureEvents = OpenFeatureEvents.ProviderReady
67-
6860
data class NoOpProviderMetadata(override val name: String?) : ProviderMetadata
6961
}
Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,45 @@
11
package dev.openfeature.sdk
22

3-
import dev.openfeature.sdk.events.OpenFeatureEvents
4-
import dev.openfeature.sdk.events.awaitReadyOrError
5-
import dev.openfeature.sdk.events.observe
6-
import kotlinx.coroutines.CoroutineDispatcher
7-
import kotlinx.coroutines.ExperimentalCoroutinesApi
8-
import kotlinx.coroutines.flow.Flow
3+
import dev.openfeature.sdk.exceptions.OpenFeatureError
94
import kotlinx.coroutines.flow.MutableSharedFlow
105
import kotlinx.coroutines.flow.SharedFlow
11-
import kotlinx.coroutines.flow.flatMapLatest
126

137
@Suppress("TooManyFunctions")
148
object OpenFeatureAPI {
159
private val NOOP_PROVIDER = NoOpProvider()
1610
private var provider: FeatureProvider = NOOP_PROVIDER
1711
private var context: EvaluationContext? = null
18-
private val providersFlow: MutableSharedFlow<FeatureProvider> = MutableSharedFlow(replay = 1)
19-
internal val sharedProvidersFlow: SharedFlow<FeatureProvider> get() = providersFlow
12+
13+
private val _statusFlow: MutableSharedFlow<OpenFeatureStatus> =
14+
MutableSharedFlow<OpenFeatureStatus>(replay = 1, extraBufferCapacity = 5)
15+
.apply {
16+
tryEmit(OpenFeatureStatus.NotReady)
17+
}
18+
val statusFlow: SharedFlow<OpenFeatureStatus> get() = _statusFlow
2019

2120
var hooks: List<Hook<*>> = listOf()
2221
private set
2322

24-
fun setProvider(provider: FeatureProvider, initialContext: EvaluationContext? = null) {
23+
suspend fun setProvider(provider: FeatureProvider, initialContext: EvaluationContext? = null) {
2524
this@OpenFeatureAPI.provider = provider
26-
providersFlow.tryEmit(provider)
25+
// TODO consider if stale status should emit? _statusFlow.tryEmit(OpenFeatureStatus.Stale)
2726
if (initialContext != null) context = initialContext
2827
try {
29-
provider.initialize(context)
28+
getProvider().initialize(context)
29+
_statusFlow.tryEmit(OpenFeatureStatus.Ready)
30+
} catch (e: OpenFeatureError) {
31+
_statusFlow.tryEmit(OpenFeatureStatus.Error(e))
3032
} catch (e: Throwable) {
31-
// This is not allowed to happen
33+
_statusFlow.tryEmit(OpenFeatureStatus.Error(OpenFeatureError.GeneralError(e.message ?: e.javaClass.name)))
34+
// TODO deal with things by setting status to Error or Fatal
3235
}
3336
}
3437

3538
suspend fun setProviderAndWait(
3639
provider: FeatureProvider,
37-
dispatcher: CoroutineDispatcher,
3840
initialContext: EvaluationContext? = null
3941
) {
4042
setProvider(provider, initialContext)
41-
provider.awaitReadyOrError(dispatcher)
4243
}
4344

4445
fun getProvider(): FeatureProvider {
@@ -49,18 +50,31 @@ object OpenFeatureAPI {
4950
provider = NOOP_PROVIDER
5051
}
5152

52-
fun setEvaluationContext(evaluationContext: EvaluationContext) {
53+
suspend fun setEvaluationContext(evaluationContext: EvaluationContext) {
5354
val oldContext = context
5455
context = evaluationContext
55-
getProvider().onContextSet(oldContext, evaluationContext)
56+
if (oldContext != evaluationContext) {
57+
_statusFlow.tryEmit(OpenFeatureStatus.Reconciling)
58+
try {
59+
getProvider().onContextSet(oldContext, evaluationContext)
60+
_statusFlow.tryEmit(OpenFeatureStatus.Ready)
61+
} catch (e: OpenFeatureError) {
62+
_statusFlow.tryEmit(OpenFeatureStatus.Error(e))
63+
// TODO how do we handle fatal errors?
64+
} catch (e: Throwable) {
65+
_statusFlow.tryEmit(
66+
OpenFeatureStatus.Error(OpenFeatureError.GeneralError(e.message ?: e.javaClass.name))
67+
)
68+
}
69+
}
5670
}
5771

5872
fun getEvaluationContext(): EvaluationContext? {
5973
return context
6074
}
6175

6276
fun getProviderMetadata(): ProviderMetadata? {
63-
return provider.metadata
77+
return getProvider().metadata
6478
}
6579

6680
fun getClient(name: String? = null, version: String? = null): Client {
@@ -76,16 +90,9 @@ object OpenFeatureAPI {
7690
}
7791

7892
fun shutdown() {
79-
provider.shutdown()
93+
_statusFlow.tryEmit(OpenFeatureStatus.NotReady)
94+
getProvider().shutdown()
8095
}
8196

82-
/*
83-
Observe events from currently configured Provider.
84-
*/
85-
@OptIn(ExperimentalCoroutinesApi::class)
86-
internal inline fun <reified T : OpenFeatureEvents> observe(): Flow<T> {
87-
return sharedProvidersFlow.flatMapLatest { provider ->
88-
provider.observe<T>()
89-
}
90-
}
97+
fun getStatus(): OpenFeatureStatus = statusFlow.replayCache.first()
9198
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dev.openfeature.sdk
2+
3+
import dev.openfeature.sdk.exceptions.OpenFeatureError
4+
5+
sealed interface OpenFeatureStatus {
6+
/**
7+
* The provider has not been initialized and cannot yet evaluate flags.
8+
*/
9+
object NotReady : OpenFeatureStatus
10+
11+
/**
12+
* The provider is ready to resolve flags.
13+
*/
14+
object Ready : OpenFeatureStatus
15+
16+
/**
17+
* The provider is in an error state and unable to evaluate flags.
18+
*/
19+
class Error(val error: OpenFeatureError) : OpenFeatureStatus
20+
21+
/**
22+
* The provider's cached state is no longer valid and may not be up-to-date with the source of truth.
23+
*/
24+
object Stale : OpenFeatureStatus
25+
26+
/**
27+
* The provider has entered an irrecoverable error state.
28+
*/
29+
object Fatal : OpenFeatureStatus
30+
31+
/**
32+
* The provider is reconciling its state with a context change.
33+
*/
34+
object Reconciling : OpenFeatureStatus
35+
}

android/src/main/java/dev/openfeature/sdk/events/EventHandler.kt

Lines changed: 0 additions & 64 deletions
This file was deleted.

android/src/main/java/dev/openfeature/sdk/events/FeatureProviderExtensions.kt

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)