Skip to content

Commit 2520f7c

Browse files
committed
Support for hook hints.
1 parent a0172d5 commit 2520f7c

File tree

6 files changed

+50
-43
lines changed

6 files changed

+50
-43
lines changed
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package javasdk;
22

3+
import com.google.common.collect.ImmutableMap;
34
import lombok.Builder;
45
import lombok.Data;
56
import lombok.Singular;
67

78
import java.util.List;
8-
import java.util.Map;
99

1010
@Data @Builder
1111
public class FlagEvaluationOptions {
1212
@Singular private List<Hook> hooks;
13-
private Map<String, Object> hookHints;
13+
@Builder.Default
14+
private ImmutableMap<String, Object> hookHints = ImmutableMap.of();
1415
}

lib/src/main/java/javasdk/Hook.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package javasdk;
22

3+
import com.google.common.collect.ImmutableMap;
4+
35
// TODO: interface? or abstract class?
46
public abstract class Hook<T> {
5-
void before(HookContext<T> ctx) {}
6-
void after(HookContext<T> ctx, FlagEvaluationDetails<T> details) {}
7-
void error(HookContext<T> ctx, Exception error) {}
8-
void finallyAfter(HookContext<T> ctx) {}
7+
void before(HookContext<T> ctx, ImmutableMap<String, Object> hints) {}
8+
void after(HookContext<T> ctx, FlagEvaluationDetails<T> details, ImmutableMap<String, Object> hints) {}
9+
void error(HookContext<T> ctx, Exception error, ImmutableMap<String, Object> hints) {}
10+
void finallyAfter(HookContext<T> ctx, ImmutableMap<String, Object> hints) {}
911
}

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package javasdk;
22

33
import com.google.common.collect.ImmutableList;
4+
import com.google.common.collect.ImmutableMap;
45
import com.google.common.collect.Lists;
56
import javasdk.exceptions.GeneralError;
67
import javasdk.exceptions.OpenFeatureError;
@@ -37,6 +38,9 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
3738
if (ctx == null) {
3839
ctx = new EvaluationContext();
3940
}
41+
42+
ImmutableMap<String, Object> hints = options.getHookHints();
43+
4044
// TODO: Context transformation?
4145
HookContext hookCtx = HookContext.from(key, type, this, ctx, defaultValue);
4246

@@ -53,7 +57,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
5357

5458
FlagEvaluationDetails<T> details = null;
5559
try {
56-
this.beforeHooks(hookCtx, mergedHooks);
60+
this.beforeHooks(hookCtx, mergedHooks, hints);
5761

5862
ProviderEvaluation<T> providerEval;
5963
if (type == FlagValueType.BOOLEAN) {
@@ -64,7 +68,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
6468
}
6569

6670
details = FlagEvaluationDetails.from(providerEval, key, null);
67-
this.afterHooks(hookCtx, details, mergedHooks);
71+
this.afterHooks(hookCtx, details, mergedHooks, hints);
6872
} catch (Exception e) {
6973
log.error("Unable to correctly evaluate flag with key {} due to exception {}", key, e.getMessage());
7074
if (details == null) {
@@ -77,36 +81,36 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
7781
} else {
7882
details.errorCode = ErrorCode.GENERAL;
7983
}
80-
this.errorHooks(hookCtx, e, mergedHooks);
84+
this.errorHooks(hookCtx, e, mergedHooks, hints);
8185
} finally {
82-
this.afterAllHooks(hookCtx, mergedHooks);
86+
this.afterAllHooks(hookCtx, mergedHooks, hints);
8387
}
8488

8589
return details;
8690
}
8791

88-
private void errorHooks(HookContext hookCtx, Exception e, List<Hook> hooks) {
92+
private void errorHooks(HookContext hookCtx, Exception e, List<Hook> hooks, ImmutableMap<String, Object> hints) {
8993
for (Hook hook : hooks) {
90-
hook.error(hookCtx, e);
94+
hook.error(hookCtx, e, hints);
9195
}
9296
}
9397

94-
private void afterAllHooks(HookContext hookCtx, List<Hook> hooks) {
98+
private void afterAllHooks(HookContext hookCtx, List<Hook> hooks, ImmutableMap<String, Object> hints) {
9599
for (Hook hook : hooks) {
96-
hook.finallyAfter(hookCtx);
100+
hook.finallyAfter(hookCtx, hints);
97101
}
98102
}
99103

100-
private <T> void afterHooks(HookContext hookContext, FlagEvaluationDetails<T> details, List<Hook> hooks) {
104+
private <T> void afterHooks(HookContext hookContext, FlagEvaluationDetails<T> details, List<Hook> hooks, ImmutableMap<String, Object> hints) {
101105
for (Hook hook : hooks) {
102-
hook.after(hookContext, details);
106+
hook.after(hookContext, details, hints);
103107
}
104108
}
105109

106-
private HookContext beforeHooks(HookContext hookCtx, List<Hook> hooks) {
110+
private HookContext beforeHooks(HookContext hookCtx, List<Hook> hooks, ImmutableMap<String, Object> hints) {
107111
// These traverse backwards from normal.
108112
for (Hook hook : Lists.reverse(hooks)) {
109-
hook.before(hookCtx);
113+
hook.before(hookCtx, hints);
110114
// TODO: Merge returned context w/ hook context object
111115
}
112116
return hookCtx;

lib/src/test/java/javasdk/DeveloperExperienceTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class DeveloperExperienceTest {
2929
Client client = api.getClient();
3030
client.registerHooks(exampleHook);
3131
Boolean retval = client.getBooleanValue(flagKey, false);
32-
verify(exampleHook, times(1)).finallyAfter(any());
32+
verify(exampleHook, times(1)).finallyAfter(any(), any());
3333
assertFalse(retval);
3434
}
3535

@@ -43,8 +43,8 @@ class DeveloperExperienceTest {
4343
client.registerHooks(clientHook);
4444
Boolean retval = client.getBooleanValue(flagKey, false, new EvaluationContext(),
4545
FlagEvaluationOptions.builder().hook(evalHook).build());
46-
verify(clientHook, times(1)).finallyAfter(any());
47-
verify(evalHook, times(1)).finallyAfter(any());
46+
verify(clientHook, times(1)).finallyAfter(any(), any());
47+
verify(evalHook, times(1)).finallyAfter(any(), any());
4848
assertFalse(retval);
4949
}
5050

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ Client _client() {
135135
c.getBooleanValue("key", false, null, FlagEvaluationOptions.builder()
136136
.hook(invocationHook)
137137
.build());
138-
verify(clientHook, times(1)).before(any());
139-
verify(invocationHook, times(1)).before(any());
138+
verify(clientHook, times(1)).before(any(), any());
139+
verify(invocationHook, times(1)).before(any(), any());
140140
}
141141

142142
@Specification(spec="flag evaluation", number="1.18", text="Methods, functions, or operations on the client MUST " +

lib/src/test/java/javasdk/HookSpecTests.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ void emptyApiHooks() {
147147
client.getBooleanValue("key", false, new EvaluationContext(),
148148
FlagEvaluationOptions.builder().hook(evalHook).build());
149149

150-
verify(evalHook, times(1)).before(any());
150+
verify(evalHook, times(1)).before(any(), any());
151151
}
152152

153153
@Specification(spec="hooks", number="5.1", text="Flag evalution options MUST contain a list of hooks to evaluate.")
@@ -160,25 +160,25 @@ void emptyApiHooks() {
160160
@Specification(spec="hooks", number="4.3", text="If an error occurs in the finally hook, it MUST NOT trigger the error hook.")
161161
@Test void errors_in_finally() {
162162
Hook<Boolean> h = mock(Hook.class);
163-
doThrow(RuntimeException.class).when(h).finallyAfter(any());
163+
doThrow(RuntimeException.class).when(h).finallyAfter(any(), any());
164164

165165
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
166166
api.setProvider(new NoOpProvider<>());
167167
Client c= api.getClient();
168168

169169
assertThrows(RuntimeException.class, () -> c.getBooleanValue("key", false, null, FlagEvaluationOptions.builder().hook(h).build()));
170170

171-
verify(h, times(1)).finallyAfter(any());
172-
verify(h, times(0)).error(any(), any());
171+
verify(h, times(1)).finallyAfter(any(), any());
172+
verify(h, times(0)).error(any(), any(), any());
173173
}
174174

175175
@Specification(spec="hooks", number="3.4", text="The error hook MUST run when errors are encountered in the before stage, the after stage or during flag evaluation. It accepts hook context (required), exception for what went wrong (required), and HookHints (optional). It has no return value.")
176176
@Test void error_hook_run_during_non_finally_stage() {
177177
final boolean[] error_called = {false};
178178
Hook h = mock(Hook.class);
179-
doThrow(RuntimeException.class).when(h).finallyAfter(any());
179+
doThrow(RuntimeException.class).when(h).finallyAfter(any(), any());
180180

181-
verify(h, times(0)).error(any(), any());
181+
verify(h, times(0)).error(any(), any(), any());
182182
}
183183
@Specification(spec="hooks", number="4.2", text="Hooks MUST be evaluated in the following order:" +
184184
"before: API, Client, Invocation" +
@@ -189,33 +189,33 @@ void emptyApiHooks() {
189189
List<String> evalOrder = new ArrayList<String>();
190190
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
191191
api.setProvider(new NoOpProvider());
192-
api.registerHooks(new Hook() {
192+
api.registerHooks(new Hook<Boolean>() {
193193
@Override
194-
void before(HookContext ctx) {
194+
void before(HookContext<Boolean> ctx, ImmutableMap<String, Object> hints) {
195195
evalOrder.add("api before");
196196
}
197197

198198
@Override
199-
void after(HookContext ctx, FlagEvaluationDetails details) {
199+
void after(HookContext<Boolean> ctx, FlagEvaluationDetails<Boolean> details, ImmutableMap<String, Object> hints) {
200200
evalOrder.add("api after");
201201
throw new RuntimeException(); // trigger error flows.
202202
}
203203

204204
@Override
205-
void error(HookContext ctx, Exception error) {
205+
void error(HookContext<Boolean> ctx, Exception error, ImmutableMap<String, Object> hints) {
206206
evalOrder.add("api error");
207207
}
208208

209209
@Override
210-
void finallyAfter(HookContext ctx) {
210+
void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object> hints) {
211211
evalOrder.add("api finally");
212212
}
213213
});
214214

215215
Client c = api.getClient();
216-
c.registerHooks(new Hook() {
216+
c.registerHooks(new Hook<Boolean>() {
217217
@Override
218-
void before(HookContext ctx) {
218+
void before(HookContext<Boolean> ctx, ImmutableMap<String, Object> hints) {
219219
evalOrder.add("client before");
220220
}
221221

@@ -225,35 +225,35 @@ void after(HookContext ctx, FlagEvaluationDetails details) {
225225
}
226226

227227
@Override
228-
void error(HookContext ctx, Exception error) {
228+
void error(HookContext<Boolean> ctx, Exception error, ImmutableMap<String, Object> hints) {
229229
evalOrder.add("client error");
230230
}
231231

232232
@Override
233-
void finallyAfter(HookContext ctx) {
233+
void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object> hints) {
234234
evalOrder.add("client finally");
235235
}
236236
});
237237

238238
c.getBooleanValue("key", false, null, FlagEvaluationOptions.builder()
239-
.hook(new Hook() {
239+
.hook(new Hook<Boolean>() {
240240
@Override
241-
void before(HookContext ctx) {
241+
void before(HookContext<Boolean> ctx, ImmutableMap<String, Object> hints) {
242242
evalOrder.add("invocation before");
243243
}
244244

245245
@Override
246-
void after(HookContext ctx, FlagEvaluationDetails details) {
246+
void after(HookContext<Boolean> ctx, FlagEvaluationDetails<Boolean> details, ImmutableMap<String, Object> hints) {
247247
evalOrder.add("invocation after");
248248
}
249249

250250
@Override
251-
void error(HookContext ctx, Exception error) {
251+
void error(HookContext<Boolean> ctx, Exception error, ImmutableMap<String, Object> hints) {
252252
evalOrder.add("invocation error");
253253
}
254254

255255
@Override
256-
void finallyAfter(HookContext ctx) {
256+
void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object> hints) {
257257
evalOrder.add("invocation finally");
258258
}
259259
})

0 commit comments

Comments
 (0)