Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
kotlin = "2.2.10"
kotlinx-coroutines = "1.10.2"
open-feature-kotlin-sdk = "0.6.2"
open-feature-kotlin-sdk = "0.8.0"
android = "8.10.1"
ktor = "3.1.3"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import dev.openfeature.kotlin.sdk.ProviderEvaluation
import dev.openfeature.kotlin.sdk.ProviderMetadata
import dev.openfeature.kotlin.sdk.Value
import dev.openfeature.kotlin.sdk.events.OpenFeatureProviderEvents
import dev.openfeature.kotlin.sdk.events.OpenFeatureProviderEvents.EventDetails
import dev.openfeature.kotlin.sdk.exceptions.ErrorCode
import dev.openfeature.kotlin.sdk.exceptions.OpenFeatureError
import kotlinx.coroutines.CancellationException
Expand Down Expand Up @@ -54,23 +55,30 @@ class OfrepProvider(

override fun observe(): Flow<OpenFeatureProviderEvents> = statusFlow

private fun providerError(error: Throwable): OpenFeatureProviderEvents.ProviderError {
val ofError =
error as? OpenFeatureError
?: OpenFeatureError.GeneralError(error.message ?: "Unknown error")
return OpenFeatureProviderEvents.ProviderError(
eventDetails =
EventDetails(
message = ofError.message,
errorCode = ofError.errorCode(),
),
)
}

override suspend fun initialize(initialContext: EvaluationContext?) {
this.evaluationContext = initialContext
try {
val bulkEvaluationStatus = evaluateFlags(initialContext ?: ImmutableContext())
if (bulkEvaluationStatus == BulkEvaluationStatus.RATE_LIMITED) {
statusFlow.emit(
OpenFeatureProviderEvents.ProviderError(
OpenFeatureError.GeneralError("Rate limited"),
),
)
statusFlow.emit(providerError(OpenFeatureError.GeneralError("Rate limited")))
} else {
statusFlow.emit(OpenFeatureProviderEvents.ProviderReady)
statusFlow.emit(OpenFeatureProviderEvents.ProviderReady())
}
} catch (e: OpenFeatureError) {
statusFlow.emit(OpenFeatureProviderEvents.ProviderError(e))
} catch (e: Exception) {
statusFlow.emit(OpenFeatureProviderEvents.ProviderError(OpenFeatureError.GeneralError(e.message ?: "Unknown error")))
} catch (e: Throwable) {
statusFlow.emit(providerError(e))
}
startPolling()
}
Comment thread
maxnrp marked this conversation as resolved.
Expand Down Expand Up @@ -101,23 +109,17 @@ class OfrepProvider(
BulkEvaluationStatus.SUCCESS_UPDATED -> {
// TODO: we should migrate to configuration change event when it's available
// in the kotlin SDK
statusFlow.emit(OpenFeatureProviderEvents.ProviderReady)
statusFlow.emit(OpenFeatureProviderEvents.ProviderReady())
}
}
} catch (e: CancellationException) {
// expected to happen when the job is cancelled, no need to report it via the
// statusFlow
} catch (e: OfrepError.ApiTooManyRequestsError) {
// in that case the provider is just stale because we were not able to
statusFlow.emit(OpenFeatureProviderEvents.ProviderStale)
statusFlow.emit(OpenFeatureProviderEvents.ProviderStale())
} catch (e: Throwable) {
statusFlow.emit(
OpenFeatureProviderEvents.ProviderError(
OpenFeatureError.GeneralError(
e.message ?: "",
),
),
)
statusFlow.emit(providerError(e))
}
}
}
Expand Down Expand Up @@ -157,18 +159,18 @@ class OfrepProvider(
oldContext: EvaluationContext?,
newContext: EvaluationContext,
) {
this.statusFlow.emit(OpenFeatureProviderEvents.ProviderStale)
this.statusFlow.emit(OpenFeatureProviderEvents.ProviderStale())
this.evaluationContext = newContext

try {
val postBulkEvaluateFlags = evaluateFlags(newContext)
// we don't emit event if the evaluation is rate limited because
// the provider is still stale
if (postBulkEvaluateFlags != BulkEvaluationStatus.RATE_LIMITED) {
statusFlow.emit(OpenFeatureProviderEvents.ProviderReady)
statusFlow.emit(OpenFeatureProviderEvents.ProviderReady())
}
} catch (e: Throwable) {
statusFlow.emit(OpenFeatureProviderEvents.ProviderError(OpenFeatureError.GeneralError(e.message ?: "")))
statusFlow.emit(providerError(e))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import dev.openfeature.kotlin.sdk.ImmutableContext
import dev.openfeature.kotlin.sdk.OpenFeatureAPI
import dev.openfeature.kotlin.sdk.Value
import dev.openfeature.kotlin.sdk.events.OpenFeatureProviderEvents
import dev.openfeature.kotlin.sdk.events.OpenFeatureProviderEvents.EventDetails
import dev.openfeature.kotlin.sdk.exceptions.ErrorCode
import dev.openfeature.kotlin.sdk.exceptions.OpenFeatureError
import io.ktor.client.engine.mock.MockEngine
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
Expand All @@ -36,7 +36,6 @@ import kotlinx.coroutines.test.runTest
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
Expand Down Expand Up @@ -154,7 +153,7 @@ class OfrepProviderTest {

val provider = createOfrepProvider(mockEngine)
var providerErrorReceived = false
var exceptionReceived: Throwable? = null
var details: EventDetails? = null

launch {
provider
Expand All @@ -163,19 +162,15 @@ class OfrepProviderTest {
.take(1)
.collect {
providerErrorReceived = true
exceptionReceived = it.error
details = it.eventDetails
}
}
runCurrent()
withClient(provider, defaultEvalCtx) { client ->
runCurrent()
assertTrue(providerErrorReceived, "ProviderError event was not received")
assertIs<OpenFeatureError.GeneralError>(exceptionReceived, "The exception is not of type GeneralError")
assertEquals(
"Rate limited",
(exceptionReceived as OpenFeatureError.GeneralError).message,
"The exception's message is not correct",
)
assertEquals(ErrorCode.GENERAL, details?.errorCode)
assertEquals("Rate limited", details?.message)
}
}

Expand All @@ -187,7 +182,7 @@ class OfrepProviderTest {

val provider = createOfrepProvider(mockEngine)
var providerErrorReceived = false
var exceptionReceived: Throwable? = null
var details: EventDetails? = null

launch {
provider
Expand All @@ -196,18 +191,15 @@ class OfrepProviderTest {
.take(1)
.collect {
providerErrorReceived = true
exceptionReceived = it.error
details = it.eventDetails
}
}
runCurrent()
val evalCtx = ImmutableContext(targetingKey = "")
withClient(provider, evalCtx) { client ->
runCurrent()
assertTrue(providerErrorReceived, "ProviderError event was not received")
assertIs<OpenFeatureError.TargetingKeyMissingError>(
exceptionReceived,
"The exception is not of type TargetingKeyMissingError",
)
assertEquals(ErrorCode.TARGETING_KEY_MISSING, details?.errorCode)
}
}

Expand All @@ -219,7 +211,7 @@ class OfrepProviderTest {

val provider = createOfrepProvider(mockEngine)
var providerErrorReceived = false
var exceptionReceived: Throwable? = null
var details: EventDetails? = null

launch {
provider
Expand All @@ -228,18 +220,15 @@ class OfrepProviderTest {
.take(1)
.collect {
providerErrorReceived = true
exceptionReceived = it.error
details = it.eventDetails
}
}
runCurrent()
val evalCtx = ImmutableContext()
withClient(provider, evalCtx) { client ->
runCurrent()
assertTrue(providerErrorReceived, "ProviderError event was not received")
assertIs<OpenFeatureError.TargetingKeyMissingError>(
exceptionReceived,
"The exception is not of type TargetingKeyMissingError",
)
assertEquals(ErrorCode.TARGETING_KEY_MISSING, details?.errorCode)
}
}

Expand All @@ -253,7 +242,7 @@ class OfrepProviderTest {
)
val provider = createOfrepProvider(mockEngine)
var providerErrorReceived = false
var exceptionReceived: Throwable? = null
var details: EventDetails? = null

launch {
provider
Expand All @@ -262,14 +251,14 @@ class OfrepProviderTest {
.take(1)
.collect {
providerErrorReceived = true
exceptionReceived = it.error
details = it.eventDetails
}
}
runCurrent()
withClient(provider, defaultEvalCtx) { client ->
runCurrent()
assertTrue(providerErrorReceived, "ProviderError event was not received")
assertIs<OpenFeatureError.InvalidContextError>(exceptionReceived, "The exception is not of type InvalidContextError")
assertEquals(ErrorCode.INVALID_CONTEXT, details?.errorCode)
}
}

Expand All @@ -284,7 +273,7 @@ class OfrepProviderTest {

val provider = createOfrepProvider(mockEngine)
var providerErrorReceived = false
var exceptionReceived: Throwable? = null
var details: EventDetails? = null

launch {
provider
Expand All @@ -293,14 +282,14 @@ class OfrepProviderTest {
.take(1)
.collect {
providerErrorReceived = true
exceptionReceived = it.error
details = it.eventDetails
}
}
runCurrent()
withClient(provider, defaultEvalCtx) { client ->
runCurrent()
assertTrue(providerErrorReceived, "ProviderError event was not received")
assertIs<OpenFeatureError.ParseError>(exceptionReceived, "The exception is not of type ParseError")
assertEquals(ErrorCode.PARSE_ERROR, details?.errorCode)
}
}

Expand Down
Loading