Skip to content

Commit a68dee1

Browse files
authored
Merge pull request #178 from splitio/input-validation-refactor
Input validation refactor
2 parents 76669cd + 897eb5d commit a68dee1

16 files changed

+577
-285
lines changed

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

Lines changed: 32 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@
99
import io.split.engine.evaluator.Evaluator;
1010
import io.split.engine.SDKReadinessGates;
1111
import io.split.engine.evaluator.EvaluatorImp;
12+
import io.split.engine.evaluator.Labels;
13+
1214
import io.split.engine.metrics.Metrics;
1315
import io.split.grammar.Treatments;
16+
import io.split.inputValidation.EventsValidator;
17+
import io.split.inputValidation.KeyValidator;
18+
import io.split.inputValidation.SplitNameValidator;
19+
import io.split.inputValidation.TrafficTypeValidator;
1420
import org.slf4j.Logger;
1521
import org.slf4j.LoggerFactory;
1622

1723
import java.util.Collections;
1824
import java.util.HashMap;
1925
import java.util.Map;
26+
import java.util.Optional;
2027
import java.util.concurrent.TimeoutException;
21-
import java.util.regex.Pattern;
2228

2329
import static com.google.common.base.Preconditions.checkNotNull;
2430

@@ -28,11 +34,10 @@
2834
* @author adil
2935
*/
3036
public final class SplitClientImpl implements SplitClient {
31-
public static final Pattern EVENT_TYPE_MATCHER = Pattern.compile("^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$");
3237
public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null);
3338

34-
private static final String GET_TREATMENT_LABEL = "sdk.getTreatment";
35-
private static final String DEFINITION_NOT_FOUND = "definition not found";
39+
private static final String GET_TREATMENT = "getTreatment";
40+
private static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig";
3641

3742
private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class);
3843

@@ -70,59 +75,27 @@ public String getTreatment(String key, String split) {
7075

7176
@Override
7277
public String getTreatment(String key, String split, Map<String, Object> attributes) {
73-
return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes).treatment();
78+
return getTreatmentWithConfigInternal(GET_TREATMENT, key, null, split, attributes).treatment();
7479
}
7580

7681
@Override
7782
public String getTreatment(Key key, String split, Map<String, Object> attributes) {
78-
if (key == null) {
79-
_log.error("getTreatment: you passed a null key, the key must be a non-empty string");
80-
return Treatments.CONTROL;
81-
}
82-
83-
if (key.matchingKey() == null) {
84-
_log.error("getTreatment: you passed a null matchingKey, the matchingKey must be a non-empty string");
85-
return Treatments.CONTROL;
86-
}
87-
88-
89-
if (key.bucketingKey() == null) {
90-
_log.error("getTreatment: you passed a null bucketingKey, the bucketingKey must be a non-empty string");
91-
return Treatments.CONTROL;
92-
}
93-
94-
return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes).treatment();
83+
return getTreatmentWithConfigInternal(GET_TREATMENT, key.matchingKey(), key.bucketingKey(), split, attributes).treatment();
9584
}
9685

9786
@Override
9887
public SplitResult getTreatmentWithConfig(String key, String split) {
99-
return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, Collections.<String, Object>emptyMap());
88+
return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG, key, null, split, Collections.<String, Object>emptyMap());
10089
}
10190

10291
@Override
10392
public SplitResult getTreatmentWithConfig(String key, String split, Map<String, Object> attributes) {
104-
return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes);
93+
return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG, key, null, split, attributes);
10594
}
10695

10796
@Override
10897
public SplitResult getTreatmentWithConfig(Key key, String split, Map<String, Object> attributes) {
109-
if (key == null) {
110-
_log.error("getTreatment: you passed a null key, the key must be a non-empty string");
111-
return SPLIT_RESULT_CONTROL;
112-
}
113-
114-
if (key.matchingKey() == null) {
115-
_log.error("getTreatment: you passed a null matchingKey, the matchingKey must be a non-empty string");
116-
return SPLIT_RESULT_CONTROL;
117-
}
118-
119-
120-
if (key.bucketingKey() == null) {
121-
_log.error("getTreatment: you passed a null bucketingKey, the bucketingKey must be a non-empty string");
122-
return SPLIT_RESULT_CONTROL;
123-
}
124-
125-
return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes);
98+
return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG, key.matchingKey(), key.bucketingKey(), split, attributes);
12699
}
127100

128101
@Override
@@ -178,144 +151,59 @@ private boolean track(Event event) {
178151
}
179152

180153
// Traffic Type validations
181-
if (event.trafficTypeName == null) {
182-
_log.error("track: you passed a null trafficTypeName, trafficTypeName must be a non-empty string");
183-
return false;
184-
}
185-
186-
if (event.trafficTypeName.isEmpty()) {
187-
_log.error("track: you passed an empty trafficTypeName, trafficTypeName must be a non-empty string");
154+
Optional<String> trafficTypeResult = TrafficTypeValidator.isValid(event.trafficTypeName, _splitCache, "track");
155+
if (!trafficTypeResult.isPresent()) {
188156
return false;
189157
}
190-
191-
if (!event.trafficTypeName.equals(event.trafficTypeName.toLowerCase())) {
192-
_log.warn("track: trafficTypeName should be all lowercase - converting string to lowercase");
193-
event.trafficTypeName = event.trafficTypeName.toLowerCase();
194-
}
195-
196-
if (!_splitCache.trafficTypeExists(event.trafficTypeName)) {
197-
_log.warn("track: Traffic Type " + event.trafficTypeName + " does not have any corresponding Splits in this environment, " +
198-
"make sure you’re tracking your events to a valid traffic type defined in the Split console.");
199-
}
158+
event.trafficTypeName = trafficTypeResult.get();
200159

201160
// EventType validations
202-
if (event.eventTypeId == null) {
203-
_log.error("track: you passed a null eventTypeId, eventTypeId must be a non-empty string");
204-
return false;
205-
}
206-
207-
if (event.eventTypeId.isEmpty()) {
208-
_log.error("track:you passed an empty eventTypeId, eventTypeId must be a non-empty string");
209-
return false;
210-
}
211-
212-
if (!EVENT_TYPE_MATCHER.matcher(event.eventTypeId).find()) {
213-
_log.error("track: you passed " + event.eventTypeId + ", eventTypeId must adhere to the regular expression " +
214-
"[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}. This means an eventTypeID must be alphanumeric, " +
215-
"cannot be more than 80 characters long, and can only include a dash, underscore, period, " +
216-
"or colon as separators of alphanumeric characters");
161+
if (!EventsValidator.typeIsValid(event.eventTypeId, "track")) {
217162
return false;
218163
}
219164

220165
// Key Validations
221-
if (event.key == null) {
222-
_log.error("track: you passed a null key, key must be a non-empty string");
166+
if (!KeyValidator.isValid(event.key, "key", _config.maxStringLength(), "track")) {
223167
return false;
224168
}
225169

226-
if (event.key.isEmpty()) {
227-
_log.error("track: you passed an empty key, key must be a non-empty string");
170+
// Properties validations
171+
EventsValidator.EventValidatorResult propertiesResult = EventsValidator.propertiesAreValid(event.properties);
172+
if (!propertiesResult.getSuccess()) {
228173
return false;
229174
}
230175

231-
if (event.key.length() > _config.maxStringLength()) {
232-
_log.error("track: key too long - must be " + _config.maxStringLength() + "characters or less");
233-
return false;
234-
}
235-
236-
int size = 1024; // We assume 1kb events without properties (750 bytes avg measured)
237-
if (null != event.properties) {
238-
if (event.properties.size() > 300) {
239-
_log.warn("Event has more than 300 properties. Some of them will be trimmed when processed");
240-
}
241-
242-
for (Map.Entry<String, Object> entry: event.properties.entrySet()) {
243-
size += entry.getKey().length();
244-
Object value = entry.getValue();
245-
if (null == value) {
246-
continue;
247-
}
248-
249-
if (!(value instanceof Number) && !(value instanceof Boolean) && !(value instanceof String)) {
250-
_log.warn(String.format("Property %s is of invalid type. Setting value to null", entry.getKey()));
251-
entry.setValue(null);
252-
}
253-
254-
if (value instanceof String) {
255-
size += ((String) value).length();
256-
}
257-
258-
if (size > Event.MAX_PROPERTIES_LENGTH_BYTES) {
259-
_log.error(String.format("The maximum size allowed for the properties is 32768 bytes. "
260-
+ "Current one is %s bytes. Event not queued", size));
261-
return false;
262-
}
263-
}
264-
265-
}
176+
event.properties = propertiesResult.getValue();
266177

267-
return _eventClient.track(event, size);
178+
return _eventClient.track(event, propertiesResult.getEventSize());
268179
}
269180

270-
private SplitResult getTreatmentWithConfigInternal(String label, String matchingKey, String bucketingKey, String split, Map<String, Object> attributes) {
181+
private SplitResult getTreatmentWithConfigInternal(String method, String matchingKey, String bucketingKey, String split, Map<String, Object> attributes) {
271182
try {
272183
if (_container.isDestroyed()) {
273184
_log.error("Client has already been destroyed - no calls possible");
274185
return SPLIT_RESULT_CONTROL;
275186
}
276187

277-
if (matchingKey == null) {
278-
_log.error("getTreatmentWithConfig: you passed a null matchingKey, the matchingKey must be a non-empty string");
279-
return SPLIT_RESULT_CONTROL;
280-
}
281-
if (matchingKey.length() > _config.maxStringLength()) {
282-
_log.error("getTreatmentWithConfig: matchingKey too long - must be " + _config.maxStringLength() + " characters or less");
283-
return SPLIT_RESULT_CONTROL;
284-
}
285-
if (matchingKey.isEmpty()) {
286-
_log.error("getTreatmentWithConfig: you passed an empty string, matchingKey must be a non-empty string");
287-
return SPLIT_RESULT_CONTROL;
288-
}
289-
if (bucketingKey != null && bucketingKey.isEmpty()) {
290-
_log.error("getTreatmentWithConfig: you passed an empty string, bucketingKey must be a non-empty string");
291-
return SPLIT_RESULT_CONTROL;
292-
}
293-
if (bucketingKey != null && bucketingKey.length() > _config.maxStringLength()) {
294-
_log.error("getTreatmentWithConfig: bucketingKey too long - must be " + _config.maxStringLength() + " characters or less");
188+
if (!KeyValidator.isValid(matchingKey, "matchingKey", _config.maxStringLength(), method)) {
295189
return SPLIT_RESULT_CONTROL;
296190
}
297191

298-
if (split == null) {
299-
_log.error("getTreatmentWithConfig: you passed a null split name, split name must be a non-empty string");
192+
if (!KeyValidator.bucketingKeyIsValid(bucketingKey, _config.maxStringLength(), method)) {
300193
return SPLIT_RESULT_CONTROL;
301194
}
302195

303-
if (split.isEmpty()) {
304-
_log.error("getTreatmentWithConfig: you passed an empty split name, split name must be a non-empty string");
196+
Optional<String> splitNameResult = SplitNameValidator.isValid(split, method);
197+
if (!splitNameResult.isPresent()) {
305198
return SPLIT_RESULT_CONTROL;
306199
}
307-
308-
String trimmed = split.trim();
309-
if (!trimmed.equals(split)) {
310-
_log.warn("getTreatmentWithConfig: split name \"" + split + "\" has extra whitespace, trimming");
311-
split = trimmed;
312-
}
200+
split = splitNameResult.get();
313201

314202
long start = System.currentTimeMillis();
315203

316204
EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(matchingKey, bucketingKey, split, attributes);
317205

318-
if (result.treatment.equals(Treatments.CONTROL) && result.label.equals(DEFINITION_NOT_FOUND) && _gates.isSDKReadyNow()) {
206+
if (result.treatment.equals(Treatments.CONTROL) && result.label.equals(Labels.DEFINITION_NOT_FOUND) && _gates.isSDKReadyNow()) {
319207
_log.warn(
320208
"getTreatment: you passed \"" + split + "\" that does not exist in this environment, " +
321209
"please double check what Splits exist in the web console.");
@@ -327,7 +215,7 @@ private SplitResult getTreatmentWithConfigInternal(String label, String matching
327215
split,
328216
start,
329217
result.treatment,
330-
label,
218+
String.format("sdk.%s", method),
331219
_config.labelsEnabled() ? result.label : null,
332220
result.changeNumber,
333221
attributes

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

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

3+
import io.split.inputValidation.ApiKeyValidator;
34
import io.split.grammar.Treatments;
45
import org.slf4j.Logger;
56
import org.slf4j.LoggerFactory;
@@ -36,12 +37,7 @@ public static SplitFactory build(String apiToken) throws IOException, URISyntaxE
3637
* there were problems reading the override file from disk.
3738
*/
3839
public static synchronized SplitFactory build(String apiToken, SplitClientConfig config) throws IOException, URISyntaxException {
39-
if (apiToken == null) {
40-
_log.error("factory instantiation: you passed a null apiToken, apiToken must be a non-empty string");
41-
}
42-
if (apiToken.isEmpty()) {
43-
_log.error("factory instantiation: you passed and empty apiToken, apiToken be a non-empty string");
44-
}
40+
ApiKeyValidator.validate(apiToken);
4541

4642
if (LocalhostSplitFactory.LOCALHOST.equals(apiToken)) {
4743
return LocalhostSplitFactory.createLocalhostSplitFactory(config);

0 commit comments

Comments
 (0)