Skip to content

Commit 8aca6b6

Browse files
feat: implement hook data sharing across stages, add tests
1 parent 01410f9 commit 8aca6b6

File tree

3 files changed

+146
-48
lines changed

3 files changed

+146
-48
lines changed

src/main/java/dev/openfeature/sdk/HookSupport.java

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,62 +6,75 @@
66
import java.util.List;
77
import java.util.Map;
88
import java.util.Optional;
9+
import java.util.function.BiConsumer;
910
import java.util.function.Consumer;
1011
import lombok.RequiredArgsConstructor;
1112
import lombok.extern.slf4j.Slf4j;
13+
import org.apache.commons.lang3.tuple.Pair;
1214

1315
@Slf4j
1416
@RequiredArgsConstructor
1517
@SuppressWarnings({"unchecked", "rawtypes"})
1618
class HookSupport {
1719

1820
public EvaluationContext beforeHooks(
19-
FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
20-
return callBeforeHooks(flagValueType, hookCtx, hooks, hints);
21+
FlagValueType flagValueType, HookContext hookCtx, List<Pair<Hook, HookData>> hookDataPairs, Map<String, Object> hints) {
22+
return callBeforeHooks(flagValueType, hookCtx, hookDataPairs, hints);
2123
}
2224

2325
public void afterHooks(
2426
FlagValueType flagValueType,
2527
HookContext hookContext,
2628
FlagEvaluationDetails details,
27-
List<Hook> hooks,
29+
List<Pair<Hook, HookData>> hookDataPairs,
2830
Map<String, Object> hints) {
29-
executeHooksUnchecked(flagValueType, hooks, hook -> hook.after(hookContext, details, hints));
31+
executeHooksUnchecked(flagValueType, hookDataPairs, hookContext, (hook, ctx) -> hook.after(ctx, details, hints));
3032
}
3133

3234
public void afterAllHooks(
3335
FlagValueType flagValueType,
3436
HookContext hookCtx,
3537
FlagEvaluationDetails details,
36-
List<Hook> hooks,
38+
List<Pair<Hook, HookData>> hookDataPairs,
3739
Map<String, Object> hints) {
38-
executeHooks(flagValueType, hooks, "finally", hook -> hook.finallyAfter(hookCtx, details, hints));
40+
executeHooks(flagValueType, hookDataPairs, hookCtx, "finally", (hook, ctx) -> hook.finallyAfter(ctx, details, hints));
3941
}
4042

4143
public void errorHooks(
4244
FlagValueType flagValueType,
4345
HookContext hookCtx,
4446
Exception e,
45-
List<Hook> hooks,
47+
List<Pair<Hook, HookData>> hookDataPairs,
4648
Map<String, Object> hints) {
47-
executeHooks(flagValueType, hooks, "error", hook -> hook.error(hookCtx, e, hints));
49+
executeHooks(flagValueType, hookDataPairs, hookCtx, "error", (hook, ctx) -> hook.error(ctx, e, hints));
50+
}
51+
52+
public List<Pair<Hook, HookData>> getHookDataPairs(List<Hook> hooks) {
53+
var pairs = new ArrayList<Pair<Hook, HookData>>();
54+
for (Hook hook : hooks) {
55+
pairs.add(Pair.of(hook, HookData.create()));
56+
}
57+
return pairs;
4858
}
4959

5060
private <T> void executeHooks(
51-
FlagValueType flagValueType, List<Hook> hooks, String hookMethod, Consumer<Hook<T>> hookCode) {
52-
if (hooks != null) {
53-
for (Hook hook : hooks) {
61+
FlagValueType flagValueType, List<Pair<Hook, HookData>> hookDataPairs, HookContext hookContext, String hookMethod, BiConsumer<Hook<T>, HookContext> hookCode) {
62+
if (hookDataPairs != null) {
63+
for (Pair<Hook, HookData> hookDataPair : hookDataPairs) {
64+
Hook hook = hookDataPair.getLeft();
65+
HookData hookData = hookDataPair.getRight();
5466
if (hook.supportsFlagValueType(flagValueType)) {
55-
executeChecked(hook, hookCode, hookMethod);
67+
executeChecked(hook, hookData, hookContext, hookCode, hookMethod);
5668
}
5769
}
5870
}
5971
}
6072

6173
// before, error, and finally hooks shouldn't throw
62-
private <T> void executeChecked(Hook<T> hook, Consumer<Hook<T>> hookCode, String hookMethod) {
74+
private <T> void executeChecked(Hook<T> hook, HookData hookData, HookContext hookContext, BiConsumer<Hook<T>, HookContext> hookCode, String hookMethod) {
6375
try {
64-
hookCode.accept(hook);
76+
var hookCtxWithData = hookContext.withHookData(hookData);
77+
hookCode.accept(hook, hookCtxWithData);
6578
} catch (Exception exception) {
6679
log.error(
6780
"Unhandled exception when running {} hook {} (only 'after' hooks should throw)",
@@ -72,35 +85,42 @@ private <T> void executeChecked(Hook<T> hook, Consumer<Hook<T>> hookCode, String
7285
}
7386

7487
// after hooks can throw in order to do validation
75-
private <T> void executeHooksUnchecked(FlagValueType flagValueType, List<Hook> hooks, Consumer<Hook<T>> hookCode) {
76-
if (hooks != null) {
77-
for (Hook hook : hooks) {
88+
private <T> void executeHooksUnchecked(FlagValueType flagValueType, List<Pair<Hook, HookData>> hookDataPairs, HookContext hookContext, BiConsumer<Hook<T>, HookContext> hookCode) {
89+
if (hookDataPairs != null) {
90+
for (Pair<Hook, HookData> hookDataPair : hookDataPairs) {
91+
Hook hook = hookDataPair.getLeft();
92+
HookData hookData = hookDataPair.getRight();
7893
if (hook.supportsFlagValueType(flagValueType)) {
79-
hookCode.accept(hook);
94+
var hookCtxWithData = hookContext.withHookData(hookData);
95+
hookCode.accept(hook, hookCtxWithData);
8096
}
8197
}
8298
}
8399
}
84100

85101
private EvaluationContext callBeforeHooks(
86-
FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
102+
FlagValueType flagValueType, HookContext hookCtx, List<Pair<Hook, HookData>> hookDataPairs, Map<String, Object> hints) {
87103
// These traverse backwards from normal.
88-
List<Hook> reversedHooks = new ArrayList<>(hooks);
104+
List<Pair<Hook, HookData>> reversedHooks = new ArrayList<>(hookDataPairs);
89105
Collections.reverse(reversedHooks);
90106
EvaluationContext context = hookCtx.getCtx();
91-
107+
/*
92108
// Create hook data for each hook instance
93109
Map<Hook, HookData> hookDataMap = new HashMap<>();
94110
for (Hook hook : reversedHooks) {
95111
if (hook.supportsFlagValueType(flagValueType)) {
96112
hookDataMap.put(hook, HookData.create());
97113
}
98114
}
115+
*/
116+
117+
for (Pair<Hook, HookData> hookDataPair : reversedHooks) {
118+
Hook hook = hookDataPair.getLeft();
119+
HookData hookData = hookDataPair.getRight();
99120

100-
for (Hook hook : reversedHooks) {
101121
if (hook.supportsFlagValueType(flagValueType)) {
102122
// Create a new context with this hook's data
103-
HookContext contextWithHookData = hookCtx.withHookData(hookDataMap.get(hook));
123+
HookContext contextWithHookData = hookCtx.withHookData(hookData);
104124
Optional<EvaluationContext> optional = Optional.ofNullable(hook.before(contextWithHookData, hints))
105125
.orElse(Optional.empty());
106126
if (optional.isPresent()) {

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.function.Consumer;
2020
import lombok.Getter;
2121
import lombok.extern.slf4j.Slf4j;
22+
import org.apache.commons.lang3.tuple.Pair;
2223

2324
/**
2425
* OpenFeature Client implementation.
@@ -164,7 +165,8 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(
164165
var hints = Collections.unmodifiableMap(flagOptions.getHookHints());
165166

166167
FlagEvaluationDetails<T> details = null;
167-
List<Hook> mergedHooks = null;
168+
List<Hook> mergedHooks;
169+
List<Pair<Hook, HookData>> hookDataPairs = null;
168170
HookContext<T> afterHookContext = null;
169171

170172
try {
@@ -175,7 +177,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(
175177

176178
mergedHooks = ObjectUtils.merge(
177179
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getMutableHooks());
178-
180+
hookDataPairs = hookSupport.getHookDataPairs(mergedHooks);
179181
var mergedCtx = hookSupport.beforeHooks(
180182
type,
181183
HookContext.from(
@@ -185,7 +187,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(
185187
provider.getMetadata(),
186188
mergeEvaluationContext(ctx),
187189
defaultValue),
188-
mergedHooks,
190+
hookDataPairs,
189191
hints);
190192

191193
afterHookContext =
@@ -207,9 +209,9 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(
207209
var error =
208210
ExceptionUtils.instantiateErrorByErrorCode(details.getErrorCode(), details.getErrorMessage());
209211
enrichDetailsWithErrorDefaults(defaultValue, details);
210-
hookSupport.errorHooks(type, afterHookContext, error, mergedHooks, hints);
212+
hookSupport.errorHooks(type, afterHookContext, error, hookDataPairs, hints);
211213
} else {
212-
hookSupport.afterHooks(type, afterHookContext, details, mergedHooks, hints);
214+
hookSupport.afterHooks(type, afterHookContext, details, hookDataPairs, hints);
213215
}
214216
} catch (Exception e) {
215217
if (details == null) {
@@ -222,9 +224,9 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(
222224
}
223225
details.setErrorMessage(e.getMessage());
224226
enrichDetailsWithErrorDefaults(defaultValue, details);
225-
hookSupport.errorHooks(type, afterHookContext, e, mergedHooks, hints);
227+
hookSupport.errorHooks(type, afterHookContext, e, hookDataPairs, hints);
226228
} finally {
227-
hookSupport.afterAllHooks(type, afterHookContext, details, mergedHooks, hints);
229+
hookSupport.afterAllHooks(type, afterHookContext, details, hookDataPairs, hints);
228230
}
229231

230232
return details;

0 commit comments

Comments
 (0)