Skip to content

Commit 158d109

Browse files
committed
fix/classcastexceptions
-introduces specialized hook interfaces for supported types -adds a fixture for hooks for tests -adds type safety measure for before hook call
1 parent d12c7d3 commit 158d109

File tree

10 files changed

+199
-150
lines changed

10 files changed

+199
-150
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.openfeature.javasdk;
2+
3+
public interface BooleanHook extends Hook<Boolean> {
4+
5+
@Override
6+
default FlagValueType supportsFlagValueType() {
7+
return FlagValueType.BOOLEAN;
8+
}
9+
}

lib/src/main/java/dev/openfeature/javasdk/HookSupport.java

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,33 @@
22

33
import java.util.*;
44
import java.util.function.Consumer;
5+
import java.util.stream.Stream;
56

67
import com.google.common.collect.Lists;
7-
import lombok.*;
8+
import lombok.RequiredArgsConstructor;
89
import org.slf4j.Logger;
910

1011
@RequiredArgsConstructor
1112
class HookSupport {
1213

1314
private final Logger log;
1415

16+
@SuppressWarnings({"unchecked", "rawtypes"})
1517
public void errorHooks(FlagValueType flagValueType, HookContext hookCtx, Exception e, List<Hook> hooks, Map<String, Object> hints) {
1618
executeHooks(flagValueType, hooks, "error", hook -> hook.error(hookCtx, e, hints));
1719
}
1820

21+
@SuppressWarnings({"unchecked", "rawtypes"})
1922
public void afterAllHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
2023
executeHooks(flagValueType, hooks, "finally", hook -> hook.finallyAfter(hookCtx, hints));
2124
}
2225

26+
@SuppressWarnings({"unchecked", "rawtypes"})
2327
public void afterHooks(FlagValueType flagValueType, HookContext hookContext, FlagEvaluationDetails details, List<Hook> hooks, Map<String, Object> hints) {
24-
executeHooksUnsafe(flagValueType, hooks, hook -> hook.after(hookContext, details, hints));
28+
executeHooksUnchecked(flagValueType, hooks, hook -> hook.after(hookContext, details, hints));
2529
}
2630

31+
@SuppressWarnings({"unchecked", "rawtypes"})
2732
private <T> void executeHooks(
2833
FlagValueType flagValueType, List<Hook> hooks,
2934
String hookMethod,
@@ -35,7 +40,8 @@ private <T> void executeHooks(
3540
.forEach(hook -> executeChecked(hook, hookCode, hookMethod));
3641
}
3742

38-
private <T> void executeHooksUnsafe(
43+
@SuppressWarnings("rawtypes")
44+
private <T> void executeHooksUnchecked(
3945
FlagValueType flagValueType, List<Hook> hooks,
4046
Consumer<Hook<T>> hookCode
4147
) {
@@ -53,17 +59,28 @@ private <T> void executeChecked(Hook<T> hook, Consumer<Hook<T>> hookCode, String
5359
}
5460
}
5561

56-
public EvaluationContext beforeHooks(HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
62+
@SuppressWarnings("rawtypes")
63+
public EvaluationContext beforeHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
64+
var result = callBeforeHooks(flagValueType, hookCtx, hooks, hints);
65+
return EvaluationContext.merge(hookCtx.getCtx(), collectContexts(result));
66+
}
67+
68+
@SuppressWarnings({"unchecked", "rawtypes"})
69+
private Stream<EvaluationContext> callBeforeHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
5770
// These traverse backwards from normal.
58-
EvaluationContext ctx = hookCtx.getCtx();
59-
for (Hook hook : Lists.reverse(hooks)) {
60-
Optional<EvaluationContext> newCtx = hook.before(hookCtx, hints);
61-
if (newCtx != null && newCtx.isPresent()) {
62-
ctx = EvaluationContext.merge(ctx, newCtx.get());
63-
hookCtx = hookCtx.withCtx(ctx);
64-
}
65-
}
66-
return ctx;
71+
return Lists
72+
.reverse(hooks)
73+
.stream()
74+
.filter(hook -> hook.supportsFlagValueType() == flagValueType)
75+
.map(hook -> hook.before(hookCtx, hints))
76+
.filter(Objects::nonNull)
77+
.flatMap(Optional::stream);
6778
}
6879

80+
//for whatever reason, an error `error: incompatible types: invalid method reference` is thrown on compilation with javac
81+
//when the reduce call is appended directly as stream call chain above. moving it to its own method works however...
82+
private EvaluationContext collectContexts(Stream<EvaluationContext> result) {
83+
return result
84+
.reduce(new EvaluationContext(), EvaluationContext::merge, EvaluationContext::merge);
85+
}
6986
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.openfeature.javasdk;
2+
3+
public interface IntegerHook extends Hook<Integer> {
4+
5+
@Override
6+
default FlagValueType supportsFlagValueType() {
7+
return FlagValueType.INTEGER;
8+
}
9+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
5050

5151
FlagEvaluationDetails<T> details = null;
5252
try {
53-
EvaluationContext ctxFromHook = hookSupport.beforeHooks(hookCtx, mergedHooks, hints);
53+
EvaluationContext ctxFromHook = hookSupport.beforeHooks(type, hookCtx, mergedHooks, hints);
5454
EvaluationContext invocationContext = EvaluationContext.merge(ctxFromHook, ctx);
5555

5656
ProviderEvaluation<T> providerEval = (ProviderEvaluation<T>) createProviderEvaluation(type, key, defaultValue, options, provider, invocationContext);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.openfeature.javasdk;
2+
3+
public interface StringHook extends Hook<String> {
4+
5+
@Override
6+
default FlagValueType supportsFlagValueType() {
7+
return FlagValueType.STRING;
8+
}
9+
}

lib/src/test/java/dev/openfeature/javasdk/FlagEvaluationSpecTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package dev.openfeature.javasdk;
22

3+
import dev.openfeature.javasdk.fixtures.HookFixtures;
34
import org.junit.jupiter.api.*;
4-
import org.slf4j.*;
55
import uk.org.lidalia.slf4jtest.*;
66

77
import java.util.List;
@@ -10,7 +10,7 @@
1010
import static org.junit.jupiter.api.Assertions.*;
1111
import static org.mockito.Mockito.*;
1212

13-
class FlagEvaluationSpecTests {
13+
class FlagEvaluationSpecTests implements HookFixtures {
1414

1515
private static final TestLogger TEST_LOGGER = TestLoggerFactory.getTestLogger(OpenFeatureClient.class);
1616

@@ -143,9 +143,9 @@ private Client _client() {
143143

144144
@Specification(number="1.5.1", text="The evaluation options structure's hooks field denotes an ordered collection of hooks that the client MUST execute for the respective flag evaluation, in addition to those already configured.")
145145
@Test void hooks() {
146-
Client c = _client();
147-
Hook clientHook = mock(Hook.class);
148-
Hook invocationHook = mock(Hook.class);
146+
var c = _client();
147+
var clientHook = mockBooleanHook();
148+
var invocationHook = mockBooleanHook();
149149
c.addHooks(clientHook);
150150
c.getBooleanValue("key", false, null, FlagEvaluationOptions.builder()
151151
.hook(invocationHook)

0 commit comments

Comments
 (0)