Skip to content

Commit def7f57

Browse files
committed
Provider hooks
1 parent f37bae7 commit def7f57

File tree

4 files changed

+49
-13
lines changed

4 files changed

+49
-13
lines changed

src/main/java/dev/openfeature/javasdk/FeatureProvider.java

+9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
package dev.openfeature.javasdk;
22

3+
import com.google.common.collect.Lists;
4+
5+
import java.util.List;
6+
37
/**
48
* The interface implemented by upstream flag providers to resolve flags for their service.
59
*/
610
public interface FeatureProvider {
711
Metadata getMetadata();
12+
13+
default List<Hook> getProviderHooks() {
14+
return Lists.newArrayList();
15+
}
16+
817
ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options);
918
ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options);
1019
ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options);

src/main/java/dev/openfeature/javasdk/OpenFeatureClient.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
4343
// TODO: Context transformation?
4444
HookContext<T> hookCtx = HookContext.from(key, type, this.getMetadata(), openfeatureApi.getProvider().getMetadata(), ctx, defaultValue);
4545

46-
List<Hook> mergedHooks = ObjectUtils.merge(flagOptions.getHooks(), clientHooks, openfeatureApi.getApiHooks());
46+
List<Hook> mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getApiHooks());
4747

4848
FlagEvaluationDetails<T> details = null;
4949
try {

src/test/java/dev/openfeature/javasdk/HookSpecTest.java

+34-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.*;
44

55
import com.google.common.collect.ImmutableMap;
6+
import com.google.common.collect.Lists;
67
import dev.openfeature.javasdk.fixtures.HookFixtures;
78
import lombok.SneakyThrows;
89
import org.junit.jupiter.api.*;
@@ -166,15 +167,41 @@ void emptyApiHooks() {
166167
}
167168

168169

169-
@Specification(number="4.4.1", text="The API, Client and invocation MUST have a method for registering hooks which accepts flag evaluation options")
170+
@Specification(number="4.4.1", text="The API, Client, Provider, and invocation MUST have a method for registering hooks.")
170171
@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.")
171-
@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")
172+
@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")
172173
@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.")
173174
@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.")
174175
@Test void hook_eval_order() {
175176
List<String> evalOrder = new ArrayList<>();
176177
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
177-
api.setProvider(new NoOpProvider());
178+
api.setProvider(new NoOpProvider() {
179+
public List<Hook> getProviderHooks() {
180+
return Lists.newArrayList(new BooleanHook() {
181+
182+
@Override
183+
public Optional<EvaluationContext> before(HookContext<Boolean> ctx, Map<String, Object> hints) {
184+
evalOrder.add("provider before");
185+
return null;
186+
}
187+
188+
@Override
189+
public void after(HookContext<Boolean> ctx, FlagEvaluationDetails<Boolean> details, Map<String, Object> hints) {
190+
evalOrder.add("provider after");
191+
}
192+
193+
@Override
194+
public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object> hints) {
195+
evalOrder.add("provider error");
196+
}
197+
198+
@Override
199+
public void finallyAfter(HookContext<Boolean> ctx, Map<String, Object> hints) {
200+
evalOrder.add("provider finally");
201+
}
202+
});
203+
}
204+
});
178205
api.addHooks(new BooleanHook() {
179206
@Override
180207
public Optional<EvaluationContext> before(HookContext<Boolean> ctx, Map<String, Object> hints) {
@@ -250,10 +277,10 @@ public void finallyAfter(HookContext<Boolean> ctx, Map<String, Object> hints) {
250277
.build());
251278

252279
List<String> expectedOrder = Arrays.asList(
253-
"api before", "client before", "invocation before",
254-
"invocation after", "client after", "api after",
255-
"invocation error", "client error", "api error",
256-
"invocation finally", "client finally", "api finally");
280+
"api before", "client before", "invocation before", "provider before",
281+
"provider after", "invocation after", "client after", "api after",
282+
"provider error", "invocation error", "client error", "api error",
283+
"provider finally", "invocation finally", "client finally", "api finally");
257284
assertEquals(expectedOrder, evalOrder);
258285
}
259286

src/test/java/dev/openfeature/javasdk/ProviderSpecTest.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ public class ProviderSpecTest {
8080
assertNotNull(boolean_result.getReason());
8181
}
8282

83-
@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.")
84-
@Specification(number="2.10", text="The provider interface MAY define a context transformer method " +
85-
"or function, which can be optionally implemented in order to transform the evaluation context prior to " +
86-
"flag value resolution.")
87-
@Test void not_doing() {}
83+
@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.")
84+
@Specification(number="4.4.1", text="The API, Client, Provider, and invocation MUST have a method for registering hooks.")
85+
@Test void provider_hooks() {
86+
assertEquals(0, p.getProviderHooks().size());
87+
}
8888
}

0 commit comments

Comments
 (0)