Skip to content

Commit

Permalink
chore: Remove AsyncClient and refactor extensions (#96)
Browse files Browse the repository at this point in the history
Signed-off-by: Fabrizio Demaria <[email protected]>
  • Loading branch information
fabriziodemaria authored Jan 26, 2024
1 parent 2f83367 commit 53a0908
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 168 deletions.
26 changes: 26 additions & 0 deletions android/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.awaitReadyOrError
import dev.openfeature.sdk.events.observe
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.flatMapLatest

@Suppress("TooManyFunctions")
object OpenFeatureAPI {
Expand All @@ -24,6 +31,15 @@ object OpenFeatureAPI {
}
}

suspend fun setProviderAndWait(
provider: FeatureProvider,
dispatcher: CoroutineDispatcher,
initialContext: EvaluationContext? = null
) {
setProvider(provider, initialContext)
provider.awaitReadyOrError(dispatcher)
}

fun getProvider(): FeatureProvider? {
return provider
}
Expand Down Expand Up @@ -61,4 +77,14 @@ object OpenFeatureAPI {
fun shutdown() {
provider?.shutdown()
}

/*
Observe events from currently configured Provider.
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal inline fun <reified T : OpenFeatureEvents> observe(): Flow<T> {
return sharedProvidersFlow.flatMapLatest { provider ->
provider.observe<T>()
}
}
}
46 changes: 0 additions & 46 deletions android/src/main/java/dev/openfeature/sdk/async/AsyncClient.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,59 +1,22 @@
package dev.openfeature.sdk.async
package dev.openfeature.sdk.events

import dev.openfeature.sdk.EvaluationContext
import dev.openfeature.sdk.FeatureProvider
import dev.openfeature.sdk.OpenFeatureAPI
import dev.openfeature.sdk.OpenFeatureClient
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.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine

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

suspend fun OpenFeatureAPI.setProviderAndWait(
provider: FeatureProvider,
dispatcher: CoroutineDispatcher,
initialContext: EvaluationContext? = null
) {
setProvider(provider, initialContext)
provider.awaitReadyOrError(dispatcher)
}

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

/*
Observe events from currently configured Provider.
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal inline fun <reified T : OpenFeatureEvents> OpenFeatureAPI.observe(): Flow<T> {
return sharedProvidersFlow.flatMapLatest { provider ->
provider.observe<T>()
}
}

internal fun FeatureProvider.observeProviderError() = observe<OpenFeatureEvents.ProviderError>()
.onStart {
val status = getProviderStatus()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.async.observe
import dev.openfeature.sdk.async.setProviderAndWait
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.exceptions.ErrorCode
import dev.openfeature.sdk.helpers.AlwaysBrokenProvider
Expand Down
81 changes: 1 addition & 80 deletions android/src/test/java/dev/openfeature/sdk/EventsHandlerTest.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.async.observeProviderReady
import dev.openfeature.sdk.async.toAsync
import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.observe
import dev.openfeature.sdk.events.observeProviderReady
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
Expand All @@ -15,9 +14,6 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Test
import org.mockito.Mockito.`when`
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import kotlin.time.Duration.Companion.milliseconds

@OptIn(ExperimentalCoroutinesApi::class)
Expand Down Expand Up @@ -179,81 +175,6 @@ class EventsHandlerTest {
Assert.assertTrue(isProviderStale)
}

@Test
fun observe_string_value_from_client_works() = runTest {
val dispatcher = UnconfinedTestDispatcher(testScheduler)
val eventHandler = EventHandler(dispatcher)
val provider = TestFeatureProvider(dispatcher, eventHandler)

provider.emitReady()
val key = "mykey"
val default = "default"
val resultTexts = mutableListOf<String>()

OpenFeatureAPI.setProvider(
mock {
on { getProviderStatus() } doReturn provider.getProviderStatus()
on { observeProviderReady() } doReturn provider.observeProviderReady()
}
)

val mockOpenFeatureClient = mock<OpenFeatureClient> {
on { getStringValue(key, default) } doReturn "text1"
}

// observing the provider status after the provider ready event is published
val job = backgroundScope.launch(dispatcher) {
mockOpenFeatureClient.toAsync()!!
.observeStringValue(key, default)
.take(2)
.collect {
resultTexts.add(it)
}
}

`when`(mockOpenFeatureClient.getStringValue(key, default))
.thenReturn("text2")

provider.emitReady()
job.join()
Assert.assertEquals(listOf("text1", "text2"), resultTexts)
}

@Test
fun observe_string_value_from_client_waits_until_provider_ready() = runTest {
val dispatcher = UnconfinedTestDispatcher(testScheduler)
val eventHandler = EventHandler(dispatcher)
val provider = TestFeatureProvider(dispatcher, eventHandler)
val key = "mykey"
val default = "default"
val resultTexts = mutableListOf<String>()

val mockOpenFeatureClient = mock<OpenFeatureClient> {
on { getStringValue(key, default) } doReturn "text1"
}

OpenFeatureAPI.setProvider(
mock {
on { getProviderStatus() } doReturn provider.getProviderStatus()
on { observeProviderReady() } doReturn provider.observeProviderReady()
}
)

// observing the provider status after the provider ready event is published
val job = backgroundScope.launch(dispatcher) {
mockOpenFeatureClient.toAsync()!!
.observeStringValue(key, default)
.take(1)
.collect {
resultTexts.add(it)
}
}

provider.emitReady()
job.join()
Assert.assertEquals(listOf("text1"), resultTexts)
}

@Test
fun accessing_status_from_provider_works() = runTest {
val dispatcher = UnconfinedTestDispatcher(testScheduler)
Expand Down
2 changes: 0 additions & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import org.gradle.api.initialization.resolve.RepositoriesMode

pluginManagement {
repositories {
google()
Expand Down

0 comments on commit 53a0908

Please sign in to comment.