Skip to content

Commit 6f94d6b

Browse files
author
Matias Melograno
committed
added input validation logic
1 parent b659858 commit 6f94d6b

File tree

6 files changed

+811
-201
lines changed

6 files changed

+811
-201
lines changed

splitio/clients.py

Lines changed: 28 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,14 @@
44

55
import logging
66
import time
7-
import re
8-
from numbers import Number
9-
from six import string_types
107
from splitio.treatments import CONTROL
118
from splitio.splitters import Splitter
129
from splitio.impressions import Impression, Label
1310
from splitio.metrics import SDK_GET_TREATMENT
1411
from splitio.splits import ConditionType
1512
from splitio.events import Event
16-
17-
class Key(object):
18-
"""Key class includes a matching key and bucketing key."""
19-
20-
def __init__(self, matching_key, bucketing_key):
21-
"""Construct a key object."""
22-
self._matching_key = matching_key
23-
self._bucketing_key = bucketing_key
24-
25-
@property
26-
def matching_key(self):
27-
"""Return matching key."""
28-
return self._matching_key
29-
30-
@property
31-
def bucketing_key(self):
32-
"""Return bucketing key."""
33-
return self._bucketing_key
13+
from splitio.input_validator import InputValidator
14+
from splitio.key import Key
3415

3516

3617
class Client(object):
@@ -54,31 +35,6 @@ def __init__(self, broker, labels_enabled=True):
5435
self._labels_enabled = labels_enabled
5536
self._destroyed = False
5637

57-
def _get_keys(self, key):
58-
"""
59-
Parse received key.
60-
61-
:param key: user submitted key
62-
:type key: mixed
63-
64-
:rtype: tuple(string,string)
65-
"""
66-
if isinstance(key, Key):
67-
matching_key = key.matching_key
68-
bucketing_key = key.bucketing_key
69-
else:
70-
if isinstance(key, string_types):
71-
matching_key = key
72-
elif isinstance(key, Number):
73-
self._logger.warning("Key received as Number. Converting to string")
74-
matching_key = str(key)
75-
else:
76-
# If the key is not a string, int or Key,
77-
# set keys to None in order to return CONTROL
78-
return None, None
79-
bucketing_key = None
80-
return matching_key, bucketing_key
81-
8238
def destroy(self):
8339
"""
8440
Disable the split-client and free all allocated resources.
@@ -88,49 +44,6 @@ def destroy(self):
8844
self._destroyed = True
8945
self._broker.destroy()
9046

91-
def _validate_input(self, key, feature, start, attributes=None):
92-
"""
93-
Validate the user-supplied arguments. Return True if valid, False otherwhise.
94-
95-
:param key: user key
96-
:type key: mixed
97-
98-
:param feature: feature name
99-
:type feature: str
100-
101-
:param attributes: custom user data
102-
:type attributes: dict
103-
104-
:rtype: tuple
105-
"""
106-
if feature is None:
107-
self._logger.error("Neither Key or FeatureName can be None")
108-
return None, None
109-
110-
if not isinstance(feature, string_types):
111-
self._logger.error("feature name must be a string")
112-
return None, None
113-
114-
if key is None:
115-
self._logger.error("Neither Key or FeatureName can be None")
116-
impression = self._build_impression("", feature, CONTROL, Label.EXCEPTION,
117-
0, None, start)
118-
self._record_stats(impression, start, SDK_GET_TREATMENT)
119-
return None, None
120-
121-
122-
matching_key, bucketing_key = self._get_keys(key)
123-
if matching_key is None and bucketing_key is None:
124-
self._logger.error(
125-
"getTreatment: Key should be an object with bucketingKey and matchingKey"
126-
"or a string."
127-
)
128-
return None, None
129-
130-
return matching_key, bucketing_key
131-
132-
133-
13447
def get_treatment(self, key, feature, attributes=None):
13548
"""
13649
Get the treatment for a feature and key, with an optional dictionary of attributes.
@@ -153,8 +66,14 @@ def get_treatment(self, key, feature, attributes=None):
15366

15467
start = int(round(time.time() * 1000))
15568

156-
matching_key, bucketing_key = self._validate_input(key, feature, start, attributes)
157-
if matching_key is None and bucketing_key is None:
69+
input_validator = InputValidator()
70+
matching_key, bucketing_key = input_validator.validate_key(key)
71+
feature = input_validator.validate_feature_name(feature)
72+
73+
if ((matching_key is None and bucketing_key is None) or (feature is None)):
74+
impression = self._build_impression(matching_key, feature, CONTROL, Label.EXCEPTION,
75+
0, None, start)
76+
self._record_stats(impression, start, SDK_GET_TREATMENT)
15877
return CONTROL
15978

16079
try:
@@ -191,7 +110,7 @@ def get_treatment(self, key, feature, attributes=None):
191110
_change_number, bucketing_key, start)
192111
self._record_stats(impression, start, SDK_GET_TREATMENT)
193112
return _treatment
194-
except Exception: #pylint: disable=broad-except
113+
except Exception: # pylint: disable=broad-except
195114
self._logger.exception('Exception caught getting treatment for feature')
196115

197116
try:
@@ -203,7 +122,7 @@ def get_treatment(self, key, feature, attributes=None):
203122
self._broker.get_change_number(), bucketing_key, start
204123
)
205124
self._record_stats(impression, start, SDK_GET_TREATMENT)
206-
except Exception: #pylint: disable=broad-except
125+
except Exception: # pylint: disable=broad-except
207126
self._logger.exception(
208127
'Exception reporting impression into get_treatment exception block'
209128
)
@@ -245,7 +164,7 @@ def _record_stats(self, impression, start, operation):
245164
end = int(round(time.time() * 1000))
246165
self._broker.log_impression(impression)
247166
self._broker.log_operation_time(operation, end - start)
248-
except Exception: #pylint: disable=broad-except
167+
except Exception: # pylint: disable=broad-except
249168
self._logger.exception('Exception caught recording impressions and metrics')
250169

251170
def _get_treatment_for_split(self, split, matching_key, bucketing_key, attributes=None):
@@ -319,39 +238,13 @@ def track(self, key, traffic_type, event_type, value=None):
319238
320239
:rtype: bool
321240
"""
322-
if key is None:
323-
self._logger.error("Key cannot be None")
324-
return False
325-
326-
if isinstance(key, Number):
327-
self._logger.warning("Key must be string. Number supplied. Converting")
328-
key = str(key)
329-
330-
if not isinstance(key, string_types):
331-
self._logger.error("Incorrect type of key supplied. Must be string")
332-
return False
333-
334-
if event_type is None:
335-
self._logger.warning("event_type cannot be None")
336-
return False
241+
input_validator = InputValidator()
242+
key = input_validator.validate_track_key(key)
243+
event_type = input_validator.validate_event_type(event_type)
244+
traffic_type = input_validator.validate_traffic_type(traffic_type)
245+
value = input_validator.validate_value(value)
337246

338-
if not isinstance(event_type, string_types):
339-
self._logger.error("event_name must be string")
340-
return False
341-
342-
if not re.match(r'[a-zA-Z0-9][-_\.a-zA-Z0-9]{0,62}', event_type):
343-
self._logger.error(
344-
'event_type must match the regular expression "'
345-
r'[a-zA-Z0-9][-_\.a-zA-Z0-9]{0,62}"'
346-
)
347-
return False
348-
349-
if traffic_type is None or not isinstance(traffic_type, string_types) or traffic_type == '':
350-
self._logger.error("traffic_type must be a non-empty string")
351-
return False
352-
353-
if value is not None and not isinstance(value, Number):
354-
self._logger.error("value must be None or must be a number")
247+
if key is None or event_type is None or traffic_type is None or value is None:
355248
return False
356249

357250
event = Event(
@@ -371,7 +264,7 @@ class MatcherClient(Client):
371264
TODO: Refactor This!
372265
"""
373266

374-
def __init__(self, broker, splitter, logger): #pylint: disable=super-init-not-called
267+
def __init__(self, broker, splitter, logger): # pylint: disable=super-init-not-called
375268
"""
376269
Construct a MatcherClient instance.
377270
@@ -406,7 +299,13 @@ def get_treatment(self, key, feature, attributes=None):
406299
if key is None or feature is None:
407300
return CONTROL
408301

409-
matching_key, bucketing_key = self._get_keys(key)
302+
input_validator = InputValidator()
303+
matching_key, bucketing_key = input_validator.validate_key(key)
304+
feature = input_validator.validate_feature_name(feature)
305+
306+
if ((matching_key is None and bucketing_key is None) or feature is None):
307+
return CONTROL
308+
410309
try:
411310
# Fetching Split definition
412311
split = self._broker.fetch_feature(feature)
@@ -432,7 +331,7 @@ def get_treatment(self, key, feature, attributes=None):
432331
return split.default_treatment
433332

434333
return treatment
435-
except Exception: #pylint: disable=broad-except
334+
except Exception: # pylint: disable=broad-except
436335
self._logger.exception(
437336
'Exception caught retrieving dependent feature. Returning CONTROL'
438337
)

0 commit comments

Comments
 (0)