Skip to content

Commit 835f672

Browse files
liran2000toddbaert
andauthored
feat: add support for getBooleanEvaluation with default value (#722)
Signed-off-by: liran2000 <[email protected]> Co-authored-by: Todd Baert <[email protected]>
1 parent f101e2f commit 835f672

File tree

5 files changed

+60
-23
lines changed

5 files changed

+60
-23
lines changed

providers/configcat/src/test/java/dev/openfeature/contrib/providers/configcat/ConfigCatProviderTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ void contextTransformTest() {
227227

228228
HashMap<String, Object > customMap = new HashMap<>();
229229
customMap.put(customPropertyKey, customPropertyValue);
230+
customMap.put("targetingKey", userId);
230231
User expectedUser = User.newBuilder().email(email).country(country).custom(customMap).build(userId);
231232
User transformedUser = ContextTransformer.transform(evaluationContext);
232233

providers/statsig/README.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder().sd
3838
statsigProvider = new StatsigProvider(statsigProviderConfig);
3939
OpenFeatureAPI.getInstance().setProviderAndWait(statsigProvider);
4040
41+
MutableContext evaluationContext = new MutableContext();
42+
evaluationContext.setTargetingKey(TARGETING_KEY);
4143
boolean featureEnabled = client.getBooleanValue(FLAG_NAME, false);
4244
43-
MutableContext evaluationContext = new MutableContext();
4445
MutableContext featureConfig = new MutableContext();
4546
featureConfig.add("type", "CONFIG");
4647
featureConfig.add("name", "product");
@@ -73,7 +74,4 @@ As it is limited, evaluation context based tests are limited.
7374
See [statsigProviderTest](./src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java)
7475
for more information.
7576

76-
## Known issues
77-
- Gate BooleanEvaluation with default value true cannot fallback to true.
78-
https://github.com/statsig-io/java-server-sdk/issues/22
7977

providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import java.util.HashMap;
1010
import java.util.Map;
11+
import java.util.Objects;
1112

1213
/**
1314
* Transformer from OpenFeature context to statsig User.
@@ -29,27 +30,30 @@ static StatsigUser transform(EvaluationContext ctx) {
2930
Map<String, String> customMap = new HashMap<>();
3031
ctx.asObjectMap().forEach((k, v) -> {
3132
switch (k) {
33+
case "targetingKey":
34+
user.setUserID(Objects.toString(v, null));
35+
break;
3236
case CONTEXT_APP_VERSION:
33-
user.setAppVersion(String.valueOf(v));
37+
user.setAppVersion(Objects.toString(v, null));
3438
break;
3539
case CONTEXT_COUNTRY:
36-
user.setCountry(String.valueOf(v));
40+
user.setCountry(Objects.toString(v, null));
3741
break;
3842
case CONTEXT_EMAIL:
39-
user.setEmail(String.valueOf(v));
43+
user.setEmail(Objects.toString(v, null));
4044
break;
4145
case CONTEXT_IP:
42-
user.setIp(String.valueOf(v));
46+
user.setIp(Objects.toString(v, null));
4347
break;
4448
case CONTEXT_USER_AGENT:
45-
user.setUserAgent(String.valueOf(v));
49+
user.setUserAgent(Objects.toString(v, null));
4650
break;
4751
case CONTEXT_LOCALE:
48-
user.setLocale(String.valueOf(v));
52+
user.setLocale(Objects.toString(v, null));
4953
break;
5054
default:
5155
if (!CONTEXT_PRIVATE_ATTRIBUTES.equals(k)) {
52-
customMap.put(k, String.valueOf(v));
56+
customMap.put(k, Objects.toString(v, null));
5357
}
5458
break;
5559
}

providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java

+28-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package dev.openfeature.contrib.providers.statsig;
22

3+
import com.statsig.sdk.APIFeatureGate;
34
import com.statsig.sdk.DynamicConfig;
5+
import com.statsig.sdk.EvaluationReason;
46
import com.statsig.sdk.Layer;
57
import com.statsig.sdk.Statsig;
68
import com.statsig.sdk.StatsigUser;
@@ -14,6 +16,7 @@
1416
import dev.openfeature.sdk.Value;
1517
import dev.openfeature.sdk.exceptions.GeneralError;
1618
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
19+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1720
import lombok.AllArgsConstructor;
1821
import lombok.Getter;
1922
import lombok.SneakyThrows;
@@ -82,11 +85,22 @@ public Metadata getMetadata() {
8285

8386
@SneakyThrows
8487
@Override
88+
@SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "reason can be null")
8589
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
8690
verifyEvaluation();
8791
StatsigUser user = ContextTransformer.transform(ctx);
8892
Boolean evaluatedValue = defaultValue;
89-
try {
93+
Value featureConfigValue = ctx.getValue(FEATURE_CONFIG_KEY);
94+
String reason = null;
95+
if (featureConfigValue == null) {
96+
APIFeatureGate featureGate = Statsig.getFeatureGate(user, key);
97+
reason = featureGate.getReason().getReason();
98+
99+
// in case of evaluation failure, remain with default value.
100+
if (!assumeFailure(featureGate)) {
101+
evaluatedValue = featureGate.getValue();
102+
}
103+
} else {
90104
FeatureConfig featureConfig = parseFeatureConfig(ctx);
91105
switch (featureConfig.getType()) {
92106
case CONFIG:
@@ -100,17 +114,26 @@ public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defa
100114
default:
101115
break;
102116
}
103-
} catch (Exception e) {
104-
log.debug("could not fetch feature config. checking gate {}.", key);
105-
Future<Boolean> featureOn = Statsig.checkGateAsync(user, key);
106-
evaluatedValue = featureOn.get();
107117
}
108118

109119
return ProviderEvaluation.<Boolean>builder()
110120
.value(evaluatedValue)
121+
.reason(reason)
111122
.build();
112123
}
113124

125+
/*
126+
https://github.com/statsig-io/java-server-sdk/issues/22#issuecomment-2002346349
127+
failure is assumed by reason, since success status is not returned.
128+
*/
129+
private boolean assumeFailure(APIFeatureGate featureGate) {
130+
EvaluationReason reason = featureGate.getReason();
131+
return EvaluationReason.DEFAULT.equals(reason)
132+
|| EvaluationReason.UNINITIALIZED.equals(reason)
133+
|| EvaluationReason.UNRECOGNIZED.equals(reason)
134+
|| EvaluationReason.UNSUPPORTED.equals(reason);
135+
}
136+
114137
@Override
115138
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
116139
verifyEvaluation();

providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java

+18-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.statsig.sdk.StatsigOptions;
77
import com.statsig.sdk.StatsigUser;
88
import dev.openfeature.sdk.Client;
9+
import dev.openfeature.sdk.FlagEvaluationDetails;
910
import dev.openfeature.sdk.ImmutableContext;
1011
import dev.openfeature.sdk.MutableContext;
1112
import dev.openfeature.sdk.OpenFeatureAPI;
@@ -54,6 +55,7 @@ class StatsigProviderTest {
5455
public static final Double DOUBLE_FLAG_VALUE = 3.14;
5556
public static final String USERS_FLAG_NAME = "userIdMatching";
5657
public static final String PROPERTIES_FLAG_NAME = "emailMatching";
58+
public static final String TARGETING_KEY = "user1";
5759
private static StatsigProvider statsigProvider;
5860
private static Client client;
5961

@@ -115,15 +117,18 @@ static void shutdown() {
115117

116118
@Test
117119
void getBooleanEvaluation() {
118-
assertEquals(true, statsigProvider.getBooleanEvaluation(FLAG_NAME, false, new ImmutableContext()).getValue());
119-
assertEquals(true, client.getBooleanValue(FLAG_NAME, false));
120-
assertEquals(false, statsigProvider.getBooleanEvaluation("non-existing", false, new ImmutableContext()).getValue());
121-
assertEquals(false, client.getBooleanValue("non-existing", false));
122-
123-
// expected to succeed when https://github.com/statsig-io/java-server-sdk/issues/22 is resolved and adopted
124-
// assertEquals(true, client.getBooleanValue("non-existing", true));
120+
FlagEvaluationDetails<Boolean> flagEvaluationDetails = client.getBooleanDetails(FLAG_NAME, false, new ImmutableContext());
121+
assertEquals(false, flagEvaluationDetails.getValue());
122+
assertEquals("ERROR", flagEvaluationDetails.getReason());
125123

126124
MutableContext evaluationContext = new MutableContext();
125+
evaluationContext.setTargetingKey(TARGETING_KEY);
126+
assertEquals(true, statsigProvider.getBooleanEvaluation(FLAG_NAME, false, evaluationContext).getValue());
127+
assertEquals(true, client.getBooleanValue(FLAG_NAME, false, evaluationContext));
128+
assertEquals(false, statsigProvider.getBooleanEvaluation("non-existing", false, evaluationContext).getValue());
129+
assertEquals(false, client.getBooleanValue("non-existing", false, evaluationContext));
130+
assertEquals(true, client.getBooleanValue("non-existing", true));
131+
127132
MutableContext featureConfig = new MutableContext();
128133
featureConfig.add("type", "CONFIG");
129134
featureConfig.add("name", "product");
@@ -135,6 +140,7 @@ void getBooleanEvaluation() {
135140
@Test
136141
void getStringEvaluation() {
137142
MutableContext evaluationContext = new MutableContext();
143+
evaluationContext.setTargetingKey(TARGETING_KEY);
138144
MutableContext featureConfig = new MutableContext();
139145
featureConfig.add("type", "CONFIG");
140146
featureConfig.add("name", "product");
@@ -149,6 +155,7 @@ void getStringEvaluation() {
149155
@Test
150156
void getObjectConfigEvaluation() {
151157
MutableContext evaluationContext = new MutableContext();
158+
evaluationContext.setTargetingKey(TARGETING_KEY);
152159
MutableContext featureConfig = new MutableContext();
153160
featureConfig.add("type", "CONFIG");
154161
featureConfig.add("name", "object-config-name");
@@ -164,6 +171,7 @@ void getObjectConfigEvaluation() {
164171
@Test
165172
void getObjectLayerEvaluation() {
166173
MutableContext evaluationContext = new MutableContext();
174+
evaluationContext.setTargetingKey(TARGETING_KEY);
167175
MutableContext featureConfig = new MutableContext();
168176
featureConfig.add("type", "LAYER");
169177
featureConfig.add("name", "layer-name");
@@ -180,6 +188,7 @@ void getObjectLayerEvaluation() {
180188
@Test
181189
void getIntegerEvaluation() {
182190
MutableContext evaluationContext = new MutableContext();
191+
evaluationContext.setTargetingKey(TARGETING_KEY);
183192
MutableContext featureConfig = new MutableContext();
184193
featureConfig.add("type", "CONFIG");
185194
featureConfig.add("name", "product");
@@ -197,6 +206,7 @@ void getIntegerEvaluation() {
197206
@Test
198207
void getDoubleEvaluation() {
199208
MutableContext evaluationContext = new MutableContext();
209+
evaluationContext.setTargetingKey(TARGETING_KEY);
200210
MutableContext featureConfig = new MutableContext();
201211
featureConfig.add("type", "CONFIG");
202212
featureConfig.add("name", "product");
@@ -214,6 +224,7 @@ void getDoubleEvaluation() {
214224
@Test
215225
void getBooleanEvaluationByUser() {
216226
MutableContext evaluationContext = new MutableContext();
227+
evaluationContext.setTargetingKey(TARGETING_KEY);
217228
MutableContext featureConfig = new MutableContext();
218229
featureConfig.add("type", "CONFIG");
219230
featureConfig.add("name", "product");

0 commit comments

Comments
 (0)