Skip to content

Commit 23e25e6

Browse files
authored
Merge pull request #597 from splitio/FME-9875-fallback-config
Added models and updated config and validator
2 parents 5d33eb3 + 092207b commit 23e25e6

File tree

12 files changed

+319
-49
lines changed

12 files changed

+319
-49
lines changed

client/src/main/java/io/split/client/SplitClientConfig.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.split.client;
22

3+
import io.split.client.dtos.FallbackTreatment;
4+
import io.split.client.dtos.FallbackTreatmentsConfiguration;
35
import io.split.client.dtos.ProxyConfiguration;
46
import io.split.client.impressions.ImpressionListener;
57
import io.split.client.impressions.ImpressionsManager;
@@ -16,10 +18,13 @@
1618
import java.util.HashSet;
1719
import java.util.LinkedHashSet;
1820
import java.util.List;
21+
import java.util.Map;
1922
import java.util.Properties;
2023
import java.util.concurrent.ThreadFactory;
2124
import java.io.InputStream;
2225

26+
import static io.split.inputValidation.FallbackTreatmentValidator.isValidByFlagTreatment;
27+
import static io.split.inputValidation.FallbackTreatmentValidator.isValidTreatment;
2328
import static io.split.inputValidation.FlagSetsValidator.cleanup;
2429

2530
/**
@@ -91,6 +96,7 @@ private HttpScheme() {
9196
private final CustomStorageWrapper _customStorageWrapper;
9297
private final StorageMode _storageMode;
9398
private final ThreadFactory _threadFactory;
99+
private final FallbackTreatmentsConfiguration _fallbackTreatments;
94100

95101
// Proxy configs
96102
private final ProxyConfiguration _proxyConfiguration;
@@ -163,7 +169,8 @@ private SplitClientConfig(String endpoint,
163169
HashSet<String> flagSetsFilter,
164170
int invalidSets,
165171
CustomHeaderDecorator customHeaderDecorator,
166-
CustomHttpModule alternativeHTTPModule) {
172+
CustomHttpModule alternativeHTTPModule,
173+
FallbackTreatmentsConfiguration fallbackTreatments) {
167174
_endpoint = endpoint;
168175
_eventsEndpoint = eventsEndpoint;
169176
_featuresRefreshRate = pollForFeatureChangesEveryNSeconds;
@@ -218,6 +225,7 @@ private SplitClientConfig(String endpoint,
218225
_invalidSets = invalidSets;
219226
_customHeaderDecorator = customHeaderDecorator;
220227
_alternativeHTTPModule = alternativeHTTPModule;
228+
_fallbackTreatments = fallbackTreatments;
221229

222230
Properties props = new Properties();
223231
try {
@@ -436,6 +444,8 @@ public boolean isSdkEndpointOverridden() {
436444

437445
public CustomHttpModule alternativeHTTPModule() { return _alternativeHTTPModule; }
438446

447+
public FallbackTreatmentsConfiguration fallbackTreatments() { return _fallbackTreatments; }
448+
439449
public static final class Builder {
440450
private String _endpoint = SDK_ENDPOINT;
441451
private boolean _endpointSet = false;
@@ -494,6 +504,7 @@ public static final class Builder {
494504
private int _invalidSetsCount = 0;
495505
private CustomHeaderDecorator _customHeaderDecorator = null;
496506
private CustomHttpModule _alternativeHTTPModule = null;
507+
private FallbackTreatmentsConfiguration _fallbackTreatments;
497508

498509
public Builder() {
499510
}
@@ -1022,6 +1033,17 @@ public Builder alternativeHTTPModule(CustomHttpModule alternativeHTTPModule) {
10221033
return this;
10231034
}
10241035

1036+
/**
1037+
* Fallback Treatments
1038+
*
1039+
* @param fallbackTreatments
1040+
* @return this builder
1041+
*/
1042+
public Builder fallbackTreatments(FallbackTreatmentsConfiguration fallbackTreatments) {
1043+
_fallbackTreatments = fallbackTreatments;
1044+
return this;
1045+
}
1046+
10251047
/**
10261048
* Thread Factory
10271049
*
@@ -1158,6 +1180,25 @@ private void verifyProxy() {
11581180
}
11591181
}
11601182

1183+
private void verifyFallbackTreatments() {
1184+
if (_fallbackTreatments == null)
1185+
return;
1186+
1187+
FallbackTreatment processedGlobalFallbackTreatment = _fallbackTreatments.getGlobalFallbackTreatment();
1188+
Map<String, FallbackTreatment> processedByFlagFallbackTreatment = _fallbackTreatments.getByFlagFallbackTreatment();
1189+
1190+
if (_fallbackTreatments.getGlobalFallbackTreatment() != null) {
1191+
processedGlobalFallbackTreatment = new FallbackTreatment(
1192+
isValidTreatment(_fallbackTreatments.getGlobalFallbackTreatment().getTreatment(), "Fallback treatments"),
1193+
_fallbackTreatments.getGlobalFallbackTreatment().getConfig());
1194+
}
1195+
1196+
if (_fallbackTreatments.getByFlagFallbackTreatment() != null) {
1197+
processedByFlagFallbackTreatment = isValidByFlagTreatment(_fallbackTreatments.getByFlagFallbackTreatment(), "config");
1198+
}
1199+
_fallbackTreatments = new FallbackTreatmentsConfiguration(processedGlobalFallbackTreatment, processedByFlagFallbackTreatment);
1200+
}
1201+
11611202
public SplitClientConfig build() {
11621203

11631204
verifyRates();
@@ -1172,6 +1213,8 @@ public SplitClientConfig build() {
11721213

11731214
verifyProxy();
11741215

1216+
verifyFallbackTreatments();
1217+
11751218
if (_numThreadsForSegmentFetch <= 0) {
11761219
throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
11771220
}
@@ -1230,7 +1273,8 @@ public SplitClientConfig build() {
12301273
_flagSetsFilter,
12311274
_invalidSetsCount,
12321275
_customHeaderDecorator,
1233-
_alternativeHTTPModule);
1276+
_alternativeHTTPModule,
1277+
_fallbackTreatments);
12341278
}
12351279
}
12361280
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.split.client.dtos;
2+
3+
import java.util.Map;
4+
5+
public class FallbackTreatment {
6+
private final Map<String, Object> _config;
7+
private final String _treatment;
8+
private final String _label;
9+
10+
public FallbackTreatment(String treatment, Map<String, Object> config) {
11+
_treatment = treatment;
12+
_config = config;
13+
_label = "fallback - ";
14+
}
15+
16+
public FallbackTreatment(String treatment) {
17+
_treatment = treatment;
18+
_config = null;
19+
_label = "fallback - ";
20+
}
21+
22+
public Map<String, Object> getConfig() {
23+
return _config;
24+
}
25+
26+
public String getTreatment() {
27+
return _treatment;
28+
}
29+
30+
public String getLabel() {
31+
return _label;
32+
}
33+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.split.client.dtos;
2+
3+
import java.util.Map;
4+
5+
public class FallbackTreatmentsConfiguration {
6+
private final FallbackTreatment _globalFallbackTreatment;
7+
private final Map<String, FallbackTreatment> _byFlagFallbackTreatment;
8+
9+
public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment, Map<String, FallbackTreatment> byFlagFallbackTreatment) {
10+
_globalFallbackTreatment = globalFallbackTreatment;
11+
_byFlagFallbackTreatment = byFlagFallbackTreatment;
12+
}
13+
14+
public FallbackTreatment getGlobalFallbackTreatment() {
15+
return _globalFallbackTreatment;
16+
}
17+
18+
public Map<String, FallbackTreatment> getByFlagFallbackTreatment() { return _byFlagFallbackTreatment;}
19+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.split.inputValidation;
2+
3+
import io.split.client.dtos.FallbackTreatment;
4+
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.Optional;
11+
import java.util.regex.Pattern;
12+
13+
import static io.split.inputValidation.SplitNameValidator.isValid;
14+
15+
public class FallbackTreatmentValidator {
16+
private static final Logger _log = LoggerFactory.getLogger(FallbackTreatmentValidator.class);
17+
private static final Pattern TREATMENT_MATCHER = Pattern.compile("^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$");
18+
private static final int MAX_LENGTH = 100;
19+
20+
public static String isValidTreatment(String name, String method) {
21+
if (name == null) {
22+
_log.error(String.format("%s: you passed a null treatment, fallback treatment must be a non-empty string", method));
23+
return null;
24+
}
25+
26+
if (name.isEmpty()) {
27+
_log.error(String.format("%s: you passed an empty treatment, fallback treatment must be a non-empty string", method));
28+
return null;
29+
}
30+
31+
String trimmed = name.trim();
32+
if (!trimmed.equals(name)) {
33+
_log.warn(String.format("%s: fallback treatment %s has extra whitespace, trimming", method, name));
34+
name = trimmed;
35+
}
36+
37+
if (name.length() > MAX_LENGTH) {
38+
return null;
39+
}
40+
41+
if (!TREATMENT_MATCHER.matcher(name).find()) {
42+
_log.error(String.format("%s: you passed %s, treatment must adhere to the regular expression " +
43+
"^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$", method, name));
44+
return null;
45+
}
46+
47+
return name;
48+
}
49+
50+
public static Map<String, FallbackTreatment> isValidByFlagTreatment(Map<String, FallbackTreatment> byFlagTreatment, String method) {
51+
Map<String, FallbackTreatment> result = new HashMap<>();
52+
for (Map.Entry<String, FallbackTreatment> entry : byFlagTreatment.entrySet()) {
53+
Optional<String> feature_name = isValid(entry.getKey(), "Validator");
54+
if (feature_name.equals(Optional.empty())) {
55+
continue;
56+
}
57+
58+
FallbackTreatment fallbackTreatment = entry.getValue();
59+
String treatment = isValidTreatment(fallbackTreatment.getTreatment(), "Validator");
60+
if (treatment == null) {
61+
continue;
62+
}
63+
64+
result.put(feature_name.get(), new FallbackTreatment(treatment, fallbackTreatment.getConfig()));
65+
}
66+
67+
return result;
68+
}
69+
}

client/src/main/java/io/split/inputValidation/SplitNameValidator.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
import java.util.List;
77
import java.util.Objects;
88
import java.util.Optional;
9+
import java.util.regex.Pattern;
910
import java.util.stream.Collectors;
1011

1112
public class SplitNameValidator {
1213
private static final Logger _log = LoggerFactory.getLogger(SplitNameValidator.class);
14+
private static final int MAX_LENGTH = 100;
15+
private static final Pattern NAME_MATCHER = Pattern.compile("^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$");
1316

1417
public static Optional<String> isValid(String name, String method) {
1518
if (name == null) {
@@ -28,6 +31,16 @@ public static Optional<String> isValid(String name, String method) {
2831
name = trimmed;
2932
}
3033

34+
if (name.length() > MAX_LENGTH) {
35+
return Optional.empty();
36+
}
37+
38+
if (!NAME_MATCHER.matcher(name).find()) {
39+
_log.error(String.format("%s: you passed %s, feature flag name must adhere to the regular expression " +
40+
"^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$", method, name));
41+
return Optional.empty();
42+
}
43+
3144
return Optional.of(name);
3245
}
3346

client/src/test/java/io/split/client/SplitClientConfigTest.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package io.split.client;
22

33
import com.google.common.util.concurrent.ThreadFactoryBuilder;
4-
import io.split.client.dtos.BasicCredentialsProvider;
5-
import io.split.client.dtos.BearerCredentialsProvider;
4+
import io.split.client.dtos.RequestContext;
5+
import io.split.client.dtos.FallbackTreatmentsConfiguration;
6+
import io.split.client.dtos.FallbackTreatment;
67
import io.split.client.dtos.ProxyConfiguration;
78
import io.split.client.impressions.Impression;
89
import io.split.client.impressions.ImpressionListener;
910
import io.split.client.impressions.ImpressionsManager;
10-
import io.split.client.dtos.RequestContext;
1111
import io.split.integrations.IntegrationsConfig;
1212
import org.junit.Assert;
1313
import org.junit.Test;
@@ -17,6 +17,7 @@
1717
import java.io.FileNotFoundException;
1818
import java.net.MalformedURLException;
1919
import java.net.URL;
20+
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
2223
import java.util.stream.Collectors;
@@ -362,4 +363,25 @@ public void mustUseP12PassKeyWithProxyMtls() throws MalformedURLException, FileN
362363
.build())
363364
.build();
364365
}
366+
367+
@Test
368+
public void fallbackTreatmentCheckRegex() {
369+
SplitClientConfig config = SplitClientConfig.builder()
370+
.fallbackTreatments(new FallbackTreatmentsConfiguration(new FallbackTreatment("12#2"), null))
371+
.build();
372+
Assert.assertEquals(null, config.fallbackTreatments().getGlobalFallbackTreatment().getTreatment());
373+
374+
config = SplitClientConfig.builder()
375+
.fallbackTreatments(new FallbackTreatmentsConfiguration(null, new HashMap<String, FallbackTreatment>() {{ put("flag", new FallbackTreatment("12#2")); }} ))
376+
.build();
377+
Assert.assertEquals(0, config.fallbackTreatments().getByFlagFallbackTreatment().size());
378+
379+
config = SplitClientConfig.builder()
380+
.fallbackTreatments(new FallbackTreatmentsConfiguration(
381+
new FallbackTreatment("on"),
382+
new HashMap<String, FallbackTreatment>() {{ put("flag", new FallbackTreatment("off")); }} ))
383+
.build();
384+
Assert.assertEquals("on", config.fallbackTreatments().getGlobalFallbackTreatment().getTreatment());
385+
Assert.assertEquals("off", config.fallbackTreatments().getByFlagFallbackTreatment().get("flag").getTreatment());
386+
}
365387
}

client/src/test/java/io/split/client/SplitClientIntegrationTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -712,10 +712,10 @@ public void testPluggableMode() throws IOException, URISyntaxException {
712712
Assert.assertTrue(events.stream().anyMatch(e -> "keyProperties".equals(e.getEventDto().key) && e.getEventDto().properties != null));
713713

714714
Assert.assertEquals(3, splits.size());
715-
Assert.assertTrue(splits.stream().anyMatch(sw -> "first.name".equals(sw.name)));
716-
Assert.assertTrue(splits.stream().anyMatch(sw -> "second.name".equals(sw.name)));
717-
Assert.assertEquals("on", client.getTreatment("key", "first.name"));
718-
Assert.assertEquals("off", client.getTreatmentWithConfig("FakeKey", "second.name").treatment());
715+
Assert.assertTrue(splits.stream().anyMatch(sw -> "first-name".equals(sw.name)));
716+
Assert.assertTrue(splits.stream().anyMatch(sw -> "second-name".equals(sw.name)));
717+
Assert.assertEquals("on", client.getTreatment("key", "first-name"));
718+
Assert.assertEquals("off", client.getTreatmentWithConfig("FakeKey", "second-name").treatment());
719719
Assert.assertEquals("control", client.getTreatment("FakeKey", "noSplit"));
720720
Assert.assertEquals("on", client.getTreatment("bilal@@split.io", "rbs_flag", new HashMap<String, Object>() {{
721721
put("email", "bilal@@split.io");
@@ -726,8 +726,8 @@ public void testPluggableMode() throws IOException, URISyntaxException {
726726

727727
List<ImpressionConsumer> impressions = customStorageWrapper.getImps();
728728
Assert.assertEquals(4, impressions.size());
729-
Assert.assertTrue(impressions.stream().anyMatch(imp -> "first.name".equals(imp.getKeyImpression().feature) && "on".equals(imp.getKeyImpression().treatment)));
730-
Assert.assertTrue(impressions.stream().anyMatch(imp -> "second.name".equals(imp.getKeyImpression().feature) && "off".equals(imp.getKeyImpression().treatment)));
729+
Assert.assertTrue(impressions.stream().anyMatch(imp -> "first-name".equals(imp.getKeyImpression().feature) && "on".equals(imp.getKeyImpression().treatment)));
730+
Assert.assertTrue(impressions.stream().anyMatch(imp -> "second-name".equals(imp.getKeyImpression().feature) && "off".equals(imp.getKeyImpression().treatment)));
731731

732732
Map<String, Long> latencies = customStorageWrapper.getLatencies();
733733

0 commit comments

Comments
 (0)