@@ -276,27 +276,6 @@ final private[openfeature] class FeatureFlagsLive(
276276 _ <- txState.record(eval)
277277 } yield resolution
278278
279- // Evaluate a flag using a ClientEvaluator typeclass instance for type-safe SDK dispatch
280- private def evaluateViaTypeclass [A ](
281- key : String ,
282- default : A ,
283- ofContext : dev.openfeature.sdk.EvaluationContext ,
284- timeout : Option [Duration ] = None
285- )(implicit ev : ClientEvaluator [A ]): IO [FeatureFlagError , FlagResolution [A ]] = {
286- val rawEval = ev.evaluate(client, key, default, ofContext)
287- val timedEval = timeout match {
288- case Some (d) =>
289- rawEval.disconnect
290- .timeoutFail(new java.util.concurrent.TimeoutException (s " Evaluation of ' $key' timed out after $d" ))(d)
291- case None => rawEval
292- }
293- timedEval
294- .mapError(e => FeatureFlagError .ProviderError (e))
295- .flatMap { details =>
296- toFlagResolution(key, details).map(_.copy(value = ev.extractValue(details)))
297- }
298- }
299-
300279 private def evaluateFromClient [A : FlagType ](
301280 key : String ,
302281 default : A ,
@@ -314,88 +293,78 @@ final private[openfeature] class FeatureFlagsLive(
314293 case None => effect
315294 }
316295
317- val evaluation : IO [FeatureFlagError , FlagResolution [A ]] = flagType.typeName match {
318- case " Boolean" =>
319- evaluateViaTypeclass(key, default.asInstanceOf [Boolean ], ofContext, timeout)
320- .map(_.asInstanceOf [FlagResolution [A ]])
321-
322- case " String" =>
323- evaluateViaTypeclass(key, default.asInstanceOf [String ], ofContext, timeout)
324- .map(_.asInstanceOf [FlagResolution [A ]])
325-
326- case " Int" =>
327- evaluateViaTypeclass(key, default.asInstanceOf [Int ], ofContext, timeout)
328- .map(_.asInstanceOf [FlagResolution [A ]])
329-
330- case " Long" =>
331- evaluateViaTypeclass(key, default.asInstanceOf [Long ], ofContext, timeout)
332- .map(_.asInstanceOf [FlagResolution [A ]])
333-
334- case " Float" =>
335- evaluateViaTypeclass(key, default.asInstanceOf [Float ], ofContext, timeout)
336- .map(_.asInstanceOf [FlagResolution [A ]])
337-
338- case " Double" =>
339- evaluateViaTypeclass(key, default.asInstanceOf [Double ], ofContext, timeout)
340- .map(_.asInstanceOf [FlagResolution [A ]])
341-
342- case " Object" =>
343- withTimeout(
344- ZIO .attemptBlocking {
345- val defaultValue = new dev.openfeature.sdk.Value (
346- dev.openfeature.sdk.Structure .mapToStructure(
347- default.asInstanceOf [Map [String , Any ]].map { case (k, v) => k -> anyToObject(v) }.asJava
348- )
349- )
350- client.getObjectDetails(key, defaultValue, ofContext)
296+ val evaluation : IO [FeatureFlagError , FlagResolution [A ]] =
297+ ClientEvaluator .evaluateStandard[A ](flagType.typeName, client, key, default, ofContext) match {
298+ case Some (erased) =>
299+ val timedEval = timeout match {
300+ case Some (d) =>
301+ erased.task.disconnect
302+ .timeoutFail(new java.util.concurrent.TimeoutException (s " Evaluation of ' $key' timed out after $d" ))(d)
303+ case None => erased.task
351304 }
352- ).mapError(e => FeatureFlagError .ProviderError (e))
353- .flatMap { details =>
354- val value = valueToMap(details.getValue)
355- toFlagMetadata(details.getFlagMetadata).map { metadata =>
356- FlagResolution (
357- value = value.asInstanceOf [A ],
358- variant = Option (details.getVariant),
359- reason = toResolutionReason(details.getReason),
360- metadata = metadata,
361- flagKey = key,
362- errorCode = Option (details.getErrorCode).map(ErrorCodeConverter .fromJava),
363- errorMessage = Option (details.getErrorMessage)
305+ timedEval
306+ .mapError(e => FeatureFlagError .ProviderError (e))
307+ .flatMap { details =>
308+ toFlagResolution(key, details).map(r => r.copy(value = erased.extract(details)))
309+ }
310+
311+ case None if flagType.typeName == " Object" =>
312+ withTimeout(
313+ ZIO .attemptBlocking {
314+ val defaultValue = new dev.openfeature.sdk.Value (
315+ dev.openfeature.sdk.Structure .mapToStructure(
316+ default.asInstanceOf [Map [String , Any ]].map { case (k, v) => k -> anyToObject(v) }.asJava
317+ )
364318 )
319+ client.getObjectDetails(key, defaultValue, ofContext)
320+ }
321+ ).mapError(e => FeatureFlagError .ProviderError (e))
322+ .flatMap { details =>
323+ val value = valueToMap(details.getValue)
324+ toFlagMetadata(details.getFlagMetadata).map { metadata =>
325+ FlagResolution (
326+ value = value.asInstanceOf [A ],
327+ variant = Option (details.getVariant),
328+ reason = toResolutionReason(details.getReason),
329+ metadata = metadata,
330+ flagKey = key,
331+ errorCode = Option (details.getErrorCode).map(ErrorCodeConverter .fromJava),
332+ errorMessage = Option (details.getErrorMessage)
333+ )
334+ }
365335 }
366- }
367336
368- case _ =>
369- // Custom type - try to decode from object
370- withTimeout(
371- ZIO .attemptBlocking {
372- client.getObjectDetails(key, new dev.openfeature.sdk.Value (), ofContext)
373- }
374- ).mapError(e => FeatureFlagError .ProviderError (e))
375- .flatMap { details =>
376- valueToAny(details.getValue) match {
377- case Some (rawValue) =>
378- flagType.decode(rawValue) match {
379- case Right (decoded) =>
380- toFlagMetadata(details.getFlagMetadata).map { metadata =>
381- FlagResolution (
382- value = decoded,
383- variant = Option (details.getVariant),
384- reason = toResolutionReason(details.getReason),
385- metadata = metadata,
386- flagKey = key,
387- errorCode = Option (details.getErrorCode).map(ErrorCodeConverter .fromJava),
388- errorMessage = Option (details.getErrorMessage)
389- )
390- }
391- case Left (_) =>
392- ZIO .fail(FeatureFlagError .TypeMismatch (key, flagType.typeName, " Object" ))
393- }
394- case None =>
395- ZIO .fail(FeatureFlagError .TypeMismatch (key, flagType.typeName, " null" ))
337+ case None =>
338+ // Custom type - try to decode from object
339+ withTimeout(
340+ ZIO .attemptBlocking {
341+ client.getObjectDetails(key, new dev.openfeature.sdk.Value (), ofContext)
396342 }
397- }
398- }
343+ ).mapError(e => FeatureFlagError .ProviderError (e))
344+ .flatMap { details =>
345+ valueToAny(details.getValue) match {
346+ case Some (rawValue) =>
347+ flagType.decode(rawValue) match {
348+ case Right (decoded) =>
349+ toFlagMetadata(details.getFlagMetadata).map { metadata =>
350+ FlagResolution (
351+ value = decoded,
352+ variant = Option (details.getVariant),
353+ reason = toResolutionReason(details.getReason),
354+ metadata = metadata,
355+ flagKey = key,
356+ errorCode = Option (details.getErrorCode).map(ErrorCodeConverter .fromJava),
357+ errorMessage = Option (details.getErrorMessage)
358+ )
359+ }
360+ case Left (_) =>
361+ ZIO .fail(FeatureFlagError .TypeMismatch (key, flagType.typeName, " Object" ))
362+ }
363+ case None =>
364+ ZIO .fail(FeatureFlagError .TypeMismatch (key, flagType.typeName, " null" ))
365+ }
366+ }
367+ }
399368
400369 // Check resolution error codes for provider-level failures (handles TOCTOU race
401370 // where checkProviderStatus passes but the Java SDK's internal state is stale)
@@ -716,6 +685,12 @@ final private[openfeature] class FeatureFlagsLive(
716685 override def hooks : UIO [List [FeatureHook ]] =
717686 state.hooksRef.get
718687
688+ override def addApiHook (hook : dev.openfeature.sdk.Hook [_]): UIO [Unit ] =
689+ ZIO .succeed(api.addHooks(hook))
690+
691+ override def clearApiHooks : UIO [Unit ] =
692+ ZIO .succeed(api.clearHooks())
693+
719694 // Provider hooks (spec: provider hooks included in hook pipeline)
720695
721696 private def getProviderHooks : UIO [List [FeatureHook ]] =
0 commit comments