fix: hooks not run in NOT_READY/FATAL (#1392)

* fix: hooks not run in NOT_READY/FATAL

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
This commit is contained in:
Todd Baert 2025-03-27 09:22:19 -04:00 committed by GitHub
parent 753667925a
commit 24ef9dd290
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 72 additions and 6 deletions

View File

@ -178,12 +178,6 @@ public class OpenFeatureClient implements Client {
// provider must be accessed once to maintain a consistent reference
provider = stateManager.getProvider();
ProviderState state = stateManager.getState();
if (ProviderState.NOT_READY.equals(state)) {
throw new ProviderNotReadyError("provider not yet initialized");
}
if (ProviderState.FATAL.equals(state)) {
throw new FatalError("provider is in an irrecoverable error state");
}
mergedHooks = ObjectUtils.merge(
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getHooks());
@ -203,6 +197,14 @@ public class OpenFeatureClient implements Client {
afterHookContext =
HookContext.from(key, type, this.getMetadata(), provider.getMetadata(), mergedCtx, defaultValue);
// "short circuit" if the provider is in NOT_READY or FATAL state
if (ProviderState.NOT_READY.equals(state)) {
throw new ProviderNotReadyError("Provider not yet initialized");
}
if (ProviderState.FATAL.equals(state)) {
throw new FatalError("Provider is in an irrecoverable error state");
}
ProviderEvaluation<T> providerEval =
(ProviderEvaluation<T>) createProviderEvaluation(type, key, defaultValue, provider, mergedCtx);

View File

@ -0,0 +1,45 @@
package dev.openfeature.sdk;
import dev.openfeature.sdk.exceptions.FatalError;
import dev.openfeature.sdk.exceptions.GeneralError;
public class FatalErrorProvider implements FeatureProvider {
private final String name = "fatal";
@Override
public Metadata getMetadata() {
return () -> name;
}
@Override
public void initialize(EvaluationContext evaluationContext) throws Exception {
throw new FatalError(); // throw a fatal error on startup (this will cause the SDK to short circuit evaluations)
}
@Override
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
@Override
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
@Override
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
@Override
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
@Override
public ProviderEvaluation<Value> getObjectEvaluation(
String key, Value defaultValue, EvaluationContext invocationContext) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
}

View File

@ -594,6 +594,25 @@ class HookSpecTest implements HookFixtures {
assertThat(evaluationDetails.getValue()).isTrue();
}
@Test
void shortCircuit_flagResolution_runsHooksWithAllFields() {
String domain = "shortCircuit_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails";
api.setProvider(domain, new FatalErrorProvider());
Hook hook = mockBooleanHook();
String flagKey = "test-flag-key";
Client client = api.getClient(domain);
client.getBooleanValue(
flagKey,
true,
new ImmutableContext(),
FlagEvaluationOptions.builder().hook(hook).build());
verify(hook).before(any(), any());
verify(hook).error(any(HookContext.class), any(Exception.class), any(Map.class));
verify(hook).finallyAfter(any(HookContext.class), any(FlagEvaluationDetails.class), any(Map.class));
}
@Test
void successful_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails() {
Hook hook = mockBooleanHook();