From 504605cc5343face14f86e8f1c37ed3b1da85338 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Fri, 26 Jan 2024 09:48:30 +0100 Subject: [PATCH] chore: Remove AsyncClient and refactor extensions Signed-off-by: Fabrizio Demaria --- .../dev/openfeature/sdk/OpenFeatureAPI.kt | 26 ++++++ .../dev/openfeature/sdk/async/AsyncClient.kt | 46 ----------- .../FeatureProviderExtensions.kt} | 39 +-------- .../sdk/DeveloperExperienceTests.kt | 2 - .../dev/openfeature/sdk/EventsHandlerTest.kt | 81 +------------------ settings.gradle.kts | 2 - 6 files changed, 28 insertions(+), 168 deletions(-) delete mode 100644 android/src/main/java/dev/openfeature/sdk/async/AsyncClient.kt rename android/src/main/java/dev/openfeature/sdk/{async/Extensions.kt => events/FeatureProviderExtensions.kt} (57%) diff --git a/android/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.kt b/android/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.kt index ed35c67a..56be1c7c 100644 --- a/android/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.kt +++ b/android/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.kt @@ -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 { @@ -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 } @@ -61,4 +77,14 @@ object OpenFeatureAPI { fun shutdown() { provider?.shutdown() } + + /* + Observe events from currently configured Provider. + */ + @OptIn(ExperimentalCoroutinesApi::class) + internal inline fun observe(): Flow { + return sharedProvidersFlow.flatMapLatest { provider -> + provider.observe() + } + } } \ No newline at end of file diff --git a/android/src/main/java/dev/openfeature/sdk/async/AsyncClient.kt b/android/src/main/java/dev/openfeature/sdk/async/AsyncClient.kt deleted file mode 100644 index 47721680..00000000 --- a/android/src/main/java/dev/openfeature/sdk/async/AsyncClient.kt +++ /dev/null @@ -1,46 +0,0 @@ -package dev.openfeature.sdk.async - -import dev.openfeature.sdk.Client -import dev.openfeature.sdk.FeatureProvider -import dev.openfeature.sdk.Value -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map - -interface AsyncClient { - fun observeBooleanValue(key: String, default: Boolean): Flow - fun observeIntValue(key: String, default: Int): Flow - fun observeStringValue(key: String, default: String): Flow - fun observeDoubleValue(key: String, default: Double): Flow - fun observeValue(key: String, default: Value): Flow -} - -internal class AsyncClientImpl( - private val client: Client, - private val provider: FeatureProvider -) : AsyncClient { - private fun observeEvents(callback: () -> T) = provider - .observeProviderReady() - .map { callback() } - .distinctUntilChanged() - - override fun observeBooleanValue(key: String, default: Boolean) = observeEvents { - client.getBooleanValue(key, default) - } - - override fun observeIntValue(key: String, default: Int) = observeEvents { - client.getIntegerValue(key, default) - } - - override fun observeStringValue(key: String, default: String) = observeEvents { - client.getStringValue(key, default) - } - - override fun observeDoubleValue(key: String, default: Double): Flow = observeEvents { - client.getDoubleValue(key, default) - } - - override fun observeValue(key: String, default: Value): Flow = observeEvents { - client.getObjectValue(key, default) - } -} \ No newline at end of file diff --git a/android/src/main/java/dev/openfeature/sdk/async/Extensions.kt b/android/src/main/java/dev/openfeature/sdk/events/FeatureProviderExtensions.kt similarity index 57% rename from android/src/main/java/dev/openfeature/sdk/async/Extensions.kt rename to android/src/main/java/dev/openfeature/sdk/events/FeatureProviderExtensions.kt index 39b96e5a..d777b8f9 100644 --- a/android/src/main/java/dev/openfeature/sdk/async/Extensions.kt +++ b/android/src/main/java/dev/openfeature/sdk/events/FeatureProviderExtensions.kt @@ -1,42 +1,15 @@ -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() .onStart { if (getProviderStatus() == OpenFeatureEvents.ProviderReady) { @@ -44,16 +17,6 @@ internal fun FeatureProvider.observeProviderReady() = observe OpenFeatureAPI.observe(): Flow { - return sharedProvidersFlow.flatMapLatest { provider -> - provider.observe() - } -} - internal fun FeatureProvider.observeProviderError() = observe() .onStart { val status = getProviderStatus() diff --git a/android/src/test/java/dev/openfeature/sdk/DeveloperExperienceTests.kt b/android/src/test/java/dev/openfeature/sdk/DeveloperExperienceTests.kt index 5acd88a1..dedb8fd5 100644 --- a/android/src/test/java/dev/openfeature/sdk/DeveloperExperienceTests.kt +++ b/android/src/test/java/dev/openfeature/sdk/DeveloperExperienceTests.kt @@ -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 diff --git a/android/src/test/java/dev/openfeature/sdk/EventsHandlerTest.kt b/android/src/test/java/dev/openfeature/sdk/EventsHandlerTest.kt index 97b52910..2b93bedd 100644 --- a/android/src/test/java/dev/openfeature/sdk/EventsHandlerTest.kt +++ b/android/src/test/java/dev/openfeature/sdk/EventsHandlerTest.kt @@ -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 @@ -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) @@ -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() - - OpenFeatureAPI.setProvider( - mock { - on { getProviderStatus() } doReturn provider.getProviderStatus() - on { observeProviderReady() } doReturn provider.observeProviderReady() - } - ) - - val mockOpenFeatureClient = mock { - 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() - - val mockOpenFeatureClient = mock { - 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) diff --git a/settings.gradle.kts b/settings.gradle.kts index d7702e5a..effded2d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.api.initialization.resolve.RepositoriesMode - pluginManagement { repositories { google()