@@ -21,6 +21,17 @@ import kotlinx.coroutines.flow.launchIn
2121import kotlinx.coroutines.flow.onEach
2222import kotlinx.coroutines.flow.update
2323
24+ /* *
25+ * Type alias for a function that evaluates a feature flag using a FeatureProvider.
26+ * This represents an extension function on FeatureProvider that takes:
27+ * - key: The feature flag key to evaluate
28+ * - defaultValue: The default value to return if evaluation fails
29+ * - evaluationContext: Optional context for the evaluation
30+ * Returns a ProviderEvaluation containing the result
31+ */
32+ typealias FlagEval <T > =
33+ FeatureProvider .(key: String , defaultValue: T , evaluationContext: EvaluationContext ? ) -> ProviderEvaluation <T >
34+
2435/* *
2536 * MultiProvider is a FeatureProvider implementation that delegates flag evaluations
2637 * to multiple underlying providers using a configurable strategy.
@@ -51,6 +62,41 @@ class MultiProvider(
5162 val name : String // Maybe there's a better variable name for this?
5263 ) : FeatureProvider by implementation
5364
65+ /* *
66+ * Strategy interface defines how multiple feature providers should be evaluated
67+ * to determine the final result for a feature flag evaluation.
68+ * Different strategies can implement different logic for combining or selecting
69+ * results from multiple providers.
70+ */
71+ interface Strategy {
72+ /* *
73+ * Evaluates a feature flag across multiple providers using the strategy's logic.
74+ * @param providers List of FeatureProvider instances to evaluate against
75+ * @param key The feature flag key to evaluate
76+ * @param defaultValue The default value to use if evaluation fails or no providers match
77+ * @param evaluationContext Optional context containing additional data for evaluation
78+ * @param flagEval Function reference to the specific evaluation method to call on each provider
79+ * @return ProviderEvaluation<T> containing the final evaluation result
80+ */
81+ fun <T > evaluate (
82+ providers : List <FeatureProvider >,
83+ key : String ,
84+ defaultValue : T ,
85+ evaluationContext : EvaluationContext ? ,
86+ flagEval : FlagEval <T >
87+ ): ProviderEvaluation <T >
88+ }
89+
90+ private val OpenFeatureStatus .precedence: Int
91+ get() = when (this ) {
92+ is OpenFeatureStatus .Fatal -> 5
93+ is OpenFeatureStatus .NotReady -> 4
94+ is OpenFeatureStatus .Error -> 3
95+ is OpenFeatureStatus .Reconciling -> 2 // Not specified in precedence; treat similar to Stale
96+ is OpenFeatureStatus .Stale -> 2
97+ is OpenFeatureStatus .Ready -> 1
98+ }
99+
54100 // TODO: Support hooks
55101 override val hooks: List <Hook <* >> = emptyList()
56102 private val childFeatureProviders: List <ChildFeatureProvider > by lazy {
@@ -61,11 +107,7 @@ class MultiProvider(
61107 override val metadata: ProviderMetadata = object : ProviderMetadata {
62108 override val name: String? = MULTIPROVIDER_NAME
63109 override val originalMetadata: Map <String , ProviderMetadata > by lazy {
64- constructOriginalMetadata()
65- }
66-
67- private fun constructOriginalMetadata (): Map <String , ProviderMetadata > {
68- return childFeatureProviders.associate { it.name to it.metadata }
110+ childFeatureProviders.associate { it.name to it.metadata }
69111 }
70112
71113 override fun toString (): String {
@@ -114,7 +156,7 @@ class MultiProvider(
114156 /* *
115157 * @return Number of unique providers
116158 */
117- fun getProviderCount (): Int = childFeatureProviders.size
159+ internal fun getProviderCount (): Int = childFeatureProviders.size
118160
119161 // TODO Add distinctUntilChanged operator once EventDetails have been added
120162 override fun observe (): Flow <OpenFeatureProviderEvents > = eventFlow.asSharedFlow()
@@ -145,12 +187,13 @@ class MultiProvider(
145187 }
146188
147189 private suspend fun handleProviderEvent (provider : ChildFeatureProvider , event : OpenFeatureProviderEvents ) {
148- if (event is OpenFeatureProviderEvents .ProviderConfigurationChanged ) {
149- eventFlow.emit(event)
150- return
151- }
152-
153190 val newChildStatus = when (event) {
191+ // ProviderConfigurationChanged events should always re-emit
192+ is OpenFeatureProviderEvents .ProviderConfigurationChanged -> {
193+ eventFlow.emit(event)
194+ return
195+ }
196+
154197 is OpenFeatureProviderEvents .ProviderReady -> OpenFeatureStatus .Ready
155198 is OpenFeatureProviderEvents .ProviderNotReady -> OpenFeatureStatus .NotReady
156199 is OpenFeatureProviderEvents .ProviderStale -> OpenFeatureStatus .Stale
@@ -160,8 +203,6 @@ class MultiProvider(
160203 } else {
161204 OpenFeatureStatus .Error (event.error)
162205 }
163-
164- else -> error(" Unexpected event $event " )
165206 }
166207
167208 val previousStatus = _statusFlow .value
@@ -176,21 +217,10 @@ class MultiProvider(
176217 }
177218
178219 private fun calculateAggregateStatus (): OpenFeatureStatus {
179- val highestPrecedenceStatus = childProviderStatuses.values.maxBy(:: precedence)
220+ val highestPrecedenceStatus = childProviderStatuses.values.maxBy { it. precedence }
180221 return highestPrecedenceStatus
181222 }
182223
183- private fun precedence (status : OpenFeatureStatus ): Int {
184- return when (status) {
185- is OpenFeatureStatus .Fatal -> 5
186- is OpenFeatureStatus .NotReady -> 4
187- is OpenFeatureStatus .Error -> 3
188- is OpenFeatureStatus .Reconciling -> 2 // Not specified in precedence; treat similar to Stale
189- is OpenFeatureStatus .Stale -> 2
190- is OpenFeatureStatus .Ready -> 1
191- }
192- }
193-
194224 /* *
195225 * Shuts down all underlying providers.
196226 * This allows providers to clean up resources and complete any pending operations.
0 commit comments