From def7f570074b333d9276ef007659bdbb4b4c968f Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Fri, 5 Aug 2022 14:19:18 -0700 Subject: [PATCH] Provider hooks --- .../openfeature/javasdk/FeatureProvider.java | 9 ++++ .../javasdk/OpenFeatureClient.java | 2 +- .../dev/openfeature/javasdk/HookSpecTest.java | 41 +++++++++++++++---- .../openfeature/javasdk/ProviderSpecTest.java | 10 ++--- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/main/java/dev/openfeature/javasdk/FeatureProvider.java b/src/main/java/dev/openfeature/javasdk/FeatureProvider.java index 8f22faa1..68fffd84 100644 --- a/src/main/java/dev/openfeature/javasdk/FeatureProvider.java +++ b/src/main/java/dev/openfeature/javasdk/FeatureProvider.java @@ -1,10 +1,19 @@ package dev.openfeature.javasdk; +import com.google.common.collect.Lists; + +import java.util.List; + /** * The interface implemented by upstream flag providers to resolve flags for their service. */ public interface FeatureProvider { Metadata getMetadata(); + + default List getProviderHooks() { + return Lists.newArrayList(); + } + ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); diff --git a/src/main/java/dev/openfeature/javasdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/javasdk/OpenFeatureClient.java index 01f8f2bb..530f30a4 100644 --- a/src/main/java/dev/openfeature/javasdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/javasdk/OpenFeatureClient.java @@ -43,7 +43,7 @@ public class OpenFeatureClient implements Client { // TODO: Context transformation? HookContext hookCtx = HookContext.from(key, type, this.getMetadata(), openfeatureApi.getProvider().getMetadata(), ctx, defaultValue); - List mergedHooks = ObjectUtils.merge(flagOptions.getHooks(), clientHooks, openfeatureApi.getApiHooks()); + List mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getApiHooks()); FlagEvaluationDetails details = null; try { diff --git a/src/test/java/dev/openfeature/javasdk/HookSpecTest.java b/src/test/java/dev/openfeature/javasdk/HookSpecTest.java index 8f0e8fec..95fa533e 100644 --- a/src/test/java/dev/openfeature/javasdk/HookSpecTest.java +++ b/src/test/java/dev/openfeature/javasdk/HookSpecTest.java @@ -3,6 +3,7 @@ package dev.openfeature.javasdk; import java.util.*; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import dev.openfeature.javasdk.fixtures.HookFixtures; import lombok.SneakyThrows; import org.junit.jupiter.api.*; @@ -166,15 +167,41 @@ public class HookSpecTest implements HookFixtures { } - @Specification(number="4.4.1", text="The API, Client and invocation MUST have a method for registering hooks which accepts flag evaluation options") + @Specification(number="4.4.1", text="The API, Client, Provider, and invocation MUST have a method for registering hooks.") @Specification(number="4.3.5", text="The after stage MUST run after flag resolution occurs. It accepts a hook context (required), flag evaluation details (required) and hook hints (optional). It has no return value.") - @Specification(number="4.4.2", text="Hooks MUST be evaluated in the following order: - before: API, Client, Invocation - after: Invocation, Client, API - error (if applicable): Invocation, Client, API - finally: Invocation, Client, API") + @Specification(number="4.4.2", text="Hooks MUST be evaluated in the following order: - before: API, Client, Invocation, Provider - after: Provider, Invocation, Client, API - error (if applicable): Provider, Invocation, Client, API - finally: Provider, Invocation, Client, API") @Specification(number="4.3.6", text="The error hook MUST run when errors are encountered in the before stage, the after stage or during flag resolution. It accepts hook context (required), exception representing what went wrong (required), and hook hints (optional). It has no return value.") @Specification(number="4.3.7", text="The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required) and hook hints (optional). There is no return value.") @Test void hook_eval_order() { List evalOrder = new ArrayList<>(); OpenFeatureAPI api = OpenFeatureAPI.getInstance(); - api.setProvider(new NoOpProvider()); + api.setProvider(new NoOpProvider() { + public List getProviderHooks() { + return Lists.newArrayList(new BooleanHook() { + + @Override + public Optional before(HookContext ctx, Map hints) { + evalOrder.add("provider before"); + return null; + } + + @Override + public void after(HookContext ctx, FlagEvaluationDetails details, Map hints) { + evalOrder.add("provider after"); + } + + @Override + public void error(HookContext ctx, Exception error, Map hints) { + evalOrder.add("provider error"); + } + + @Override + public void finallyAfter(HookContext ctx, Map hints) { + evalOrder.add("provider finally"); + } + }); + } + }); api.addHooks(new BooleanHook() { @Override public Optional before(HookContext ctx, Map hints) { @@ -250,10 +277,10 @@ public class HookSpecTest implements HookFixtures { .build()); List expectedOrder = Arrays.asList( - "api before", "client before", "invocation before", - "invocation after", "client after", "api after", - "invocation error", "client error", "api error", - "invocation finally", "client finally", "api finally"); + "api before", "client before", "invocation before", "provider before", + "provider after", "invocation after", "client after", "api after", + "provider error", "invocation error", "client error", "api error", + "provider finally", "invocation finally", "client finally", "api finally"); assertEquals(expectedOrder, evalOrder); } diff --git a/src/test/java/dev/openfeature/javasdk/ProviderSpecTest.java b/src/test/java/dev/openfeature/javasdk/ProviderSpecTest.java index eb5e494b..5a85aaf0 100644 --- a/src/test/java/dev/openfeature/javasdk/ProviderSpecTest.java +++ b/src/test/java/dev/openfeature/javasdk/ProviderSpecTest.java @@ -80,9 +80,9 @@ public class ProviderSpecTest { assertNotNull(boolean_result.getReason()); } - @Specification(number="2.11.1", text="If the implementation includes a context transformer, the provider SHOULD accept a generic argument (or use an equivalent language feature) indicating the type of the transformed context. If such type information is supplied, more accurate type information can be supplied in the flag resolution methods.") - @Specification(number="2.10", text="The provider interface MAY define a context transformer method " + - "or function, which can be optionally implemented in order to transform the evaluation context prior to " + - "flag value resolution.") - @Test void not_doing() {} + @Specification(number="2.10", text="The provider interface MUST define a provider hook mechanism which can be optionally implemented in order to add hook instances to the evaluation life-cycle.") + @Specification(number="4.4.1", text="The API, Client, Provider, and invocation MUST have a method for registering hooks.") + @Test void provider_hooks() { + assertEquals(0, p.getProviderHooks().size()); + } }