Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package dev.openfeature.sdk

interface FeatureProvider {
import dev.openfeature.sdk.events.EventObserver
import dev.openfeature.sdk.events.ProviderStatus

interface FeatureProvider : EventObserver, ProviderStatus {
val hooks: List<Hook<*>>
val metadata: ProviderMetadata

Expand Down
8 changes: 8 additions & 0 deletions OpenFeature/src/main/java/dev/openfeature/sdk/NoOpProvider.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.OpenFeatureEvents
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureProvider {
override val metadata: ProviderMetadata = NoOpProviderMetadata("No-op provider")
override fun initialize(initialContext: EvaluationContext?) {
Expand Down Expand Up @@ -57,5 +61,9 @@ class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureProvid
return ProviderEvaluation(defaultValue, "Passed in default", Reason.DEFAULT.toString())
}

override fun observe(): Flow<OpenFeatureEvents> = flowOf()

override fun isProviderReady(): Boolean = true

data class NoOpProviderMetadata(override val name: String?) : ProviderMetadata
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.observe
import kotlinx.coroutines.CoroutineDispatcher

@Suppress("TooManyFunctions")
object OpenFeatureAPI {
private var provider: FeatureProvider? = null
Expand All @@ -23,10 +18,6 @@ object OpenFeatureAPI {
return provider
}

inline fun <reified T : OpenFeatureEvents> observeEvents(dispatcher: CoroutineDispatcher) =
EventHandler.eventsObserver(dispatcher)
.observe<T>()

fun clearProvider() {
provider = null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package dev.openfeature.sdk.async

import dev.openfeature.sdk.OpenFeatureClient
import dev.openfeature.sdk.Client
import dev.openfeature.sdk.FeatureProvider
import dev.openfeature.sdk.Value
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
Expand All @@ -16,10 +16,11 @@ interface AsyncClient {
}

internal class AsyncClientImpl(
private val client: OpenFeatureClient,
private val dispatcher: CoroutineDispatcher
private val client: Client,
private val provider: FeatureProvider
) : AsyncClient {
private fun <T> observeEvents(callback: () -> T) = observeProviderReady(dispatcher)
private fun <T> observeEvents(callback: () -> T) = provider
.observeProviderReady()
.map { callback() }
.distinctUntilChanged()

Expand Down
38 changes: 27 additions & 11 deletions OpenFeature/src/main/java/dev/openfeature/sdk/async/Extensions.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,50 @@
package dev.openfeature.sdk.async

import dev.openfeature.sdk.FeatureProvider
import dev.openfeature.sdk.OpenFeatureAPI
import dev.openfeature.sdk.OpenFeatureClient
import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.observe
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine

Copy link
Member

@nicklasl nicklasl Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to put some proper effort in documenting the API's in this file but I think this can go in a separate PR.

fun OpenFeatureClient.toAsync(dispatcher: CoroutineDispatcher = Dispatchers.IO): AsyncClient {
return AsyncClientImpl(this, dispatcher)
fun OpenFeatureClient.toAsync(): AsyncClient? {
val provider = OpenFeatureAPI.getProvider()
return provider?.let {
AsyncClientImpl(
this,
it
)
}
}

internal fun observeProviderReady(
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = EventHandler.eventsObserver(dispatcher)
.observe<OpenFeatureEvents.ProviderReady>()
suspend fun OpenFeatureAPI.setProviderAndWait(
provider: FeatureProvider,
dispatcher: CoroutineDispatcher
) {
setProvider(provider)
provider.awaitReady(dispatcher)
}

internal fun FeatureProvider.observeProviderReady() = observe<OpenFeatureEvents.ProviderReady>()
.onStart {
if (EventHandler.providerStatus().isProviderReady()) {
if (isProviderReady()) {
this.emit(OpenFeatureEvents.ProviderReady)
}
}

suspend fun awaitProviderReady(
inline fun <reified T : OpenFeatureEvents> OpenFeatureAPI.observeEvents(): Flow<T>? {
return getProvider()?.observe<T>()
}

suspend fun FeatureProvider.awaitReady(
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = suspendCancellableCoroutine { continuation ->
val coroutineScope = CoroutineScope(dispatcher)
Expand All @@ -40,8 +57,7 @@ suspend fun awaitProviderReady(
}

coroutineScope.launch {
EventHandler.eventsObserver()
.observe<OpenFeatureEvents.ProviderError>()
observe<OpenFeatureEvents.ProviderError>()
.take(1)
.collect {
continuation.resumeWith(Result.failure(it.error))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,28 @@ package dev.openfeature.sdk.events

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import kotlin.reflect.KClass

interface ProviderStatus {
fun isProviderReady(): Boolean
interface EventObserver {
fun observe(): Flow<OpenFeatureEvents>
}

interface EventObserver {
fun <T : OpenFeatureEvents> observe(kClass: KClass<T>): Flow<T>
interface ProviderStatus {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would expect an interface such as this to have a getStatus(): Status function.

fun isProviderReady(): Boolean
}

interface EventsPublisher {
fun publish(event: OpenFeatureEvents)
}

inline fun <reified T : OpenFeatureEvents> EventObserver.observe() = observe(T::class)
inline fun <reified T : OpenFeatureEvents> EventObserver.observe() = observe()
.filterIsInstance<T>()

class EventHandler(dispatcher: CoroutineDispatcher) : EventObserver, EventsPublisher, ProviderStatus {
private val sharedFlow: MutableSharedFlow<OpenFeatureEvents> = MutableSharedFlow()
Expand Down Expand Up @@ -56,29 +55,9 @@ class EventHandler(dispatcher: CoroutineDispatcher) : EventObserver, EventsPubli
}
}

override fun <T : OpenFeatureEvents> observe(kClass: KClass<T>): Flow<T> = sharedFlow
.filterIsInstance(kClass)
override fun observe(): Flow<OpenFeatureEvents> = sharedFlow

override fun isProviderReady(): Boolean {
return isProviderReady.value
}

companion object {
@Volatile
private var instance: EventHandler? = null

private fun getInstance(dispatcher: CoroutineDispatcher) =
instance ?: synchronized(this) {
instance ?: create(dispatcher).also { instance = it }
}

fun eventsObserver(dispatcher: CoroutineDispatcher = Dispatchers.IO): EventObserver =
getInstance(dispatcher)
internal fun providerStatus(dispatcher: CoroutineDispatcher = Dispatchers.IO): ProviderStatus =
getInstance(dispatcher)
fun eventsPublisher(dispatcher: CoroutineDispatcher = Dispatchers.IO): EventsPublisher =
getInstance(dispatcher)

private fun create(dispatcher: CoroutineDispatcher) = EventHandler(dispatcher)
}
}
Loading