Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
af65a59
Multi provider impl draft
PenguinDan Aug 20, 2025
79f59ba
Ktlint
PenguinDan Aug 20, 2025
6127d68
Try emit initial state for Multi provider
PenguinDan Aug 20, 2025
2142879
Shared flow should always have content
PenguinDan Aug 20, 2025
c6f3a8f
Add tests
PenguinDan Aug 20, 2025
f2f415a
Update multi provider strategy to better align with Open Feature specs
PenguinDan Aug 20, 2025
199236c
Add original metadata and allow all providers to shutdown
PenguinDan Aug 20, 2025
38fac8c
Add default reason to default value in First Match Strategy
PenguinDan Aug 21, 2025
48033c5
Remove json dependency and update ProviderMetadata
PenguinDan Aug 21, 2025
4fa33eb
Align to Event spec
PenguinDan Aug 22, 2025
d5f5546
Ktlint
PenguinDan Aug 22, 2025
8d8cbec
Update API dumps for multiprovider and ProviderMetadata changes
PenguinDan Aug 22, 2025
3818a11
Use Lazy and ktlint
PenguinDan Aug 23, 2025
f834a43
Add TODO once EventDetails have been added
PenguinDan Aug 26, 2025
c299717
PR comments; remove redundant comments, fix test definitions, move st…
PenguinDan Aug 26, 2025
2305056
Return an error result for FirstSuccessfulStrategy rather than throwing
PenguinDan Aug 26, 2025
70a892d
Revert sample app changes
PenguinDan Aug 26, 2025
6fe18eb
Add README documentation for Multiprovider
PenguinDan Aug 26, 2025
870ae07
Lets favor not throwing in the FirstMatchStrategy also
PenguinDan Aug 26, 2025
5472c08
Update tests to represent to non-throwing pattern and api dump
PenguinDan Aug 26, 2025
7df6772
Kotlin Format
PenguinDan Aug 30, 2025
2cc75c5
Update kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/mu…
PenguinDan Aug 30, 2025
e71d448
Update multi provider readme
PenguinDan Aug 30, 2025
c4c49db
Merge branch 'main' into MultiProvider-Impl
PenguinDan Sep 1, 2025
f540783
API dump
PenguinDan Sep 1, 2025
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
1 change: 1 addition & 0 deletions kotlin-sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ kotlin {
commonMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
}
commonTest.dependencies {
implementation("org.jetbrains.kotlin:kotlin-test:2.1.21")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dev.openfeature.kotlin.sdk.multiprovider

import dev.openfeature.kotlin.sdk.EvaluationContext
import dev.openfeature.kotlin.sdk.FeatureProvider
import dev.openfeature.kotlin.sdk.ProviderEvaluation
import dev.openfeature.kotlin.sdk.exceptions.ErrorCode
import dev.openfeature.kotlin.sdk.exceptions.OpenFeatureError

/**
* Return the first result returned by a provider. Skip providers that indicate they had no value due to FLAG_NOT_FOUND.
* In all other cases, use the value returned by the provider. If any provider returns an error result other than
* FLAG_NOT_FOUND, the whole evaluation should error and "bubble up" the individual provider's error in the result.
*
* As soon as a value is returned by a provider, the rest of the operation should short-circuit and not call the
* rest of the providers.
*/
class FirstMatchStrategy : Strategy {
/**
* Evaluates providers in sequence until finding one that has knowledge of the flag.
*
* @param providers List of providers to evaluate in order
* @param key The feature flag key to look up
* @param defaultValue Value to return if no provider knows about the flag
* @param evaluationContext Optional context for evaluation
* @param flagEval The specific evaluation method to call on each provider
* @return ProviderEvaluation with the first match or default value
*/
override fun <T> evaluate(
providers: List<FeatureProvider>,
key: String,
defaultValue: T,
evaluationContext: EvaluationContext?,
flagEval: FlagEval<T>
): ProviderEvaluation<T> {
// Iterate through each provider in the provided order
for (provider in providers) {
try {
// Call the flag evaluation method on the current provider
val eval = provider.flagEval(key, defaultValue, evaluationContext)

// If the provider knows about this flag (any result except FLAG_NOT_FOUND),
// return this result immediately, even if it's an error
if (eval.errorCode != ErrorCode.FLAG_NOT_FOUND) {
return eval
}
// Continue to next provider if error is FLAG_NOT_FOUND
} catch (_: OpenFeatureError.FlagNotFoundError) {
// Handle FLAG_NOT_FOUND exception - continue to next provider
continue
}
// We don't catch any other exception, but rather, bubble up the exceptions
}

// No provider knew about the flag, return default value with DEFAULT reason
return ProviderEvaluation(defaultValue, errorCode = ErrorCode.FLAG_NOT_FOUND)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package dev.openfeature.kotlin.sdk.multiprovider

import dev.openfeature.kotlin.sdk.EvaluationContext
import dev.openfeature.kotlin.sdk.FeatureProvider
import dev.openfeature.kotlin.sdk.ProviderEvaluation
import dev.openfeature.kotlin.sdk.exceptions.OpenFeatureError

/**
* Similar to "First Match", except that errors from evaluated providers do not halt execution.
* Instead, it will return the first successful result from a provider.
*
* If no provider successfully responds, it will throw an error result.
*/
class FirstSuccessfulStrategy : Strategy {
/**
* Evaluates providers in sequence until finding one that returns a successful result.
*
* @param providers List of providers to evaluate in order
* @param key The feature flag key to evaluate
* @param defaultValue Value to use in provider evaluations
* @param evaluationContext Optional context for evaluation
* @param flagEval The specific evaluation method to call on each provider
* @return ProviderEvaluation with the first successful result
* @throws OpenFeatureError.GeneralError if no provider returns a successful evaluation
*/
override fun <T> evaluate(
providers: List<FeatureProvider>,
key: String,
defaultValue: T,
evaluationContext: EvaluationContext?,
flagEval: FlagEval<T>
): ProviderEvaluation<T> {
// Iterate through each provider in the provided order
for (provider in providers) {
try {
// Call the flag evaluation method on the current provider
val eval = provider.flagEval(key, defaultValue, evaluationContext)

// If the provider returned a successful result (no error),
// return this result immediately
if (eval.errorCode == null) {
return eval
}
// Continue to next provider if this one had an error
} catch (_: OpenFeatureError) {
// Handle any OpenFeature exceptions - continue to next provider
// FirstSuccessful strategy skips errors and continues
continue
}
}

// No provider returned a successful result, throw an error
// This indicates that all providers either failed or had errors
throw OpenFeatureError.GeneralError("No provider returned a successful evaluation for the requested flag.")
}
}
Loading