configcat 5.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,71 +1,16 @@
1
1
  module ConfigCat
2
-
3
- class ConfigFetcher
4
- #
5
- # Config fetcher interface
6
- #
7
-
8
- def get_configuration_json()
9
- #
10
- # :return: Returns the configuration json Dictionary
11
- #
12
- end
13
-
14
- def close()
15
- #
16
- # Closes the ConfigFetcher's resources
17
- #
18
- end
19
- end
20
-
2
+ # Config cache interface
21
3
  class ConfigCache
22
- #
23
- # Config cache interface
24
- #
25
-
4
+ # :returns the config json object from the cache
26
5
  def get(key)
27
- #
28
- # :returns the config json object from the cache
29
- #
30
6
  end
31
7
 
8
+ # Sets the config json cache.
32
9
  def set(key, value)
33
- #
34
- # Sets the config json cache.
35
- #
36
- end
37
- end
38
-
39
- class CachePolicy
40
- #
41
- # Config cache interface
42
- #
43
-
44
- def get()
45
- #
46
- # :returns the config json object from the cache
47
- #
48
- end
49
-
50
- def force_refresh()
51
- #
52
- #
53
- # :return:
54
- #
55
- end
56
-
57
- def stop()
58
- #
59
- #
60
- # :return:
61
- #
62
10
  end
63
11
  end
64
12
 
13
+ # Generic ConfigCatClientException
65
14
  class ConfigCatClientException < Exception
66
- #
67
- # Generic ConfigCatClientException
68
- #
69
15
  end
70
-
71
16
  end
@@ -3,17 +3,27 @@ require 'configcat/constants'
3
3
 
4
4
 
5
5
  module ConfigCat
6
+ class LocalDictionaryFlagOverrides < FlagOverrides
7
+ def initialize(source, override_behaviour)
8
+ @source = source
9
+ @override_behaviour = override_behaviour
10
+ end
11
+
12
+ def create_data_source(log)
13
+ return LocalDictionaryDataSource.new(@source, @override_behaviour)
14
+ end
15
+ end
16
+
6
17
  class LocalDictionaryDataSource < OverrideDataSource
7
18
  def initialize(source, override_behaviour)
8
19
  super(override_behaviour)
9
- dictionary = {}
20
+ @_settings = {}
10
21
  source.each do |key, value|
11
- dictionary[key] = {VALUE => value}
22
+ @_settings[key] = { VALUE => value }
12
23
  end
13
- @_settings = {FEATURE_FLAGS => dictionary}
14
24
  end
15
25
 
16
- def get_overrides()
26
+ def get_overrides
17
27
  return @_settings
18
28
  end
19
29
  end
@@ -3,25 +3,37 @@ require 'configcat/constants'
3
3
 
4
4
 
5
5
  module ConfigCat
6
- class LocalFileDataSource < OverrideDataSource
6
+ class LocalFileFlagOverrides < FlagOverrides
7
7
  def initialize(file_path, override_behaviour)
8
+ @file_path = file_path
9
+ @override_behaviour = override_behaviour
10
+ end
11
+
12
+ def create_data_source(log)
13
+ return LocalFileDataSource.new(@file_path, @override_behaviour, log)
14
+ end
15
+ end
16
+
17
+ class LocalFileDataSource < OverrideDataSource
18
+ def initialize(file_path, override_behaviour, log)
8
19
  super(override_behaviour)
9
- if File.exists?(file_path)
10
- ConfigCat.logger.error("The file '%s' does not exists." % file_path)
20
+ @log = log
21
+ if !File.exists?(file_path)
22
+ @log.error("The file '%s' does not exists." % file_path)
11
23
  end
12
24
  @_file_path = file_path
13
25
  @_settings = nil
14
26
  @_cached_file_stamp = 0
15
27
  end
16
28
 
17
- def get_overrides()
29
+ def get_overrides
18
30
  reload_file_content()
19
31
  return @_settings
20
32
  end
21
33
 
22
34
  private
23
35
 
24
- def reload_file_content()
36
+ def reload_file_content
25
37
  begin
26
38
  stamp = File.mtime(@_file_path)
27
39
  if stamp != @_cached_file_stamp
@@ -29,20 +41,19 @@ module ConfigCat
29
41
  file = File.read(@_file_path)
30
42
  data = JSON.parse(file)
31
43
  if data.key?("flags")
32
- dictionary = {}
44
+ @_settings = {}
33
45
  source = data["flags"]
34
46
  source.each do |key, value|
35
- dictionary[key] = {VALUE => value}
47
+ @_settings[key] = { VALUE => value }
36
48
  end
37
- @_settings = {FEATURE_FLAGS => dictionary}
38
49
  else
39
- @_settings = data
50
+ @_settings = data[FEATURE_FLAGS]
40
51
  end
41
52
  end
42
53
  rescue JSON::ParserError => e
43
- ConfigCat.logger.error("Could not decode json from file %s. %s" % [@_file_path, e.to_s])
54
+ @log.error("Could not decode json from file %s. %s" % [@_file_path, e.to_s])
44
55
  rescue Exception => e
45
- ConfigCat.logger.error("Could not read the content of the file %s. %s" % [@_file_path, e.to_s])
56
+ @log.error("Could not read the content of the file %s. %s" % [@_file_path, e.to_s])
46
57
  end
47
58
  end
48
59
  end
@@ -15,16 +15,22 @@ module ConfigCat
15
15
  REMOTE_OVER_LOCAL = 2
16
16
  end
17
17
 
18
+ class FlagOverrides
19
+ # :returns [OverrideDataSource] the created OverrideDataSource
20
+ def create_data_source(log)
21
+ end
22
+ end
23
+
18
24
  class OverrideDataSource
19
25
  def initialize(override_behaviour)
20
26
  @_override_behaviour = override_behaviour
21
27
  end
22
28
 
23
- def get_behaviour()
29
+ def get_behaviour
24
30
  return @_override_behaviour
25
31
  end
26
32
 
27
- def get_overrides()
33
+ def get_overrides
28
34
  # :returns the override dictionary
29
35
  return {}
30
36
  end
@@ -0,0 +1,62 @@
1
+ module ConfigCat
2
+ class PollingMode
3
+ # Creates a configured auto polling configuration.
4
+ #
5
+ # :param poll_interval_seconds: sets at least how often this policy should fetch the latest configuration and refresh the cache.
6
+ # :param max_init_wait_time_seconds: sets the maximum waiting time between initialization and the first config acquisition in seconds.
7
+ # :return [AutoPollingMode]
8
+ def self.auto_poll(poll_interval_seconds: 60, max_init_wait_time_seconds: 5)
9
+ poll_interval_seconds = 1 if poll_interval_seconds < 1
10
+ max_init_wait_time_seconds = 0 if max_init_wait_time_seconds < 0
11
+
12
+ AutoPollingMode.new(poll_interval_seconds, max_init_wait_time_seconds)
13
+ end
14
+
15
+ # Creates a configured lazy loading polling configuration.
16
+ #
17
+ # :param cache_refresh_interval_seconds: sets how long the cache will store its value before fetching the latest from the network again.
18
+ # :return [LazyLoadingMode]
19
+ def self.lazy_load(cache_refresh_interval_seconds: 60)
20
+ cache_refresh_interval_seconds = 1 if cache_refresh_interval_seconds < 1
21
+
22
+ LazyLoadingMode.new(cache_refresh_interval_seconds)
23
+ end
24
+
25
+ # Creates a configured manual polling configuration.
26
+ # :return [ManualPollingMode]
27
+ def self.manual_poll
28
+ ManualPollingMode.new
29
+ end
30
+ end
31
+
32
+ class AutoPollingMode < PollingMode
33
+ attr_reader :poll_interval_seconds, :max_init_wait_time_seconds
34
+
35
+ def initialize(poll_interval_seconds, max_init_wait_time_seconds)
36
+ @poll_interval_seconds = poll_interval_seconds
37
+ @max_init_wait_time_seconds = max_init_wait_time_seconds
38
+ end
39
+
40
+ def identifier
41
+ return "a"
42
+ end
43
+ end
44
+
45
+ class LazyLoadingMode < PollingMode
46
+ attr_reader :cache_refresh_interval_seconds
47
+
48
+ def initialize(cache_refresh_interval_seconds)
49
+ @cache_refresh_interval_seconds = cache_refresh_interval_seconds
50
+ end
51
+
52
+ def identifier
53
+ return "l"
54
+ end
55
+ end
56
+
57
+ class ManualPollingMode < PollingMode
58
+ def identifier
59
+ return "m"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ module ConfigCat
2
+ RefreshResult = Struct.new(:success, :error)
3
+ end
@@ -7,36 +7,34 @@ module ConfigCat
7
7
  class RolloutEvaluator
8
8
  COMPARATOR_TEXTS = ["IS ONE OF", "IS NOT ONE OF", "CONTAINS", "DOES NOT CONTAIN", "IS ONE OF (SemVer)", "IS NOT ONE OF (SemVer)", "< (SemVer)", "<= (SemVer)", "> (SemVer)", ">= (SemVer)", "= (Number)", "<> (Number)", "< (Number)", "<= (Number)", "> (Number)", ">= (Number)"]
9
9
 
10
- def self.evaluate(key, user, default_value, default_variation_id, config)
11
- ConfigCat.logger.info("Evaluating get_value('%s')." % key)
12
-
13
- feature_flags = config.fetch(FEATURE_FLAGS, nil)
14
- if feature_flags === nil
15
- ConfigCat.logger.error("Evaluating get_value('%s') failed. Value not found for key '%s' Returning default_value: [%s]." % [key, key, default_value.to_s])
16
- return default_value, default_variation_id
17
- end
10
+ def initialize(log)
11
+ @log = log
12
+ end
18
13
 
19
- setting_descriptor = feature_flags.fetch(key, nil)
14
+ # :returns value, variation_id. matched_evaluation_rule, matched_evaluation_percentage_rule, error
15
+ def evaluate(key:, user:, default_value:, default_variation_id:, settings:)
16
+ setting_descriptor = settings[key]
20
17
  if setting_descriptor === nil
21
- ConfigCat.logger.error("Evaluating get_value('%s') failed. Value not found for key '%s'. Returning default_value: [%s]. Here are the available keys: %s" % [key, key, default_value.to_s, feature_flags.keys.join(", ")])
22
- return default_value, default_variation_id
18
+ error = "Evaluating get_value('%s') failed. Value not found for key '%s'. Returning default_value: [%s]. Here are the available keys: %s" % [key, key, default_value.to_s, settings.keys.join(", ")]
19
+ @log.error(error)
20
+ return default_value, default_variation_id, nil, nil, error
23
21
  end
24
22
 
25
23
  rollout_rules = setting_descriptor.fetch(ROLLOUT_RULES, [])
26
24
  rollout_percentage_items = setting_descriptor.fetch(ROLLOUT_PERCENTAGE_ITEMS, [])
27
25
 
28
26
  if !user.equal?(nil) && !user.class.equal?(User)
29
- ConfigCat.logger.warn("Evaluating get_value('%s'). User Object is not an instance of User type." % key)
27
+ @log.warn("Evaluating get_value('%s'). User Object is not an instance of User type." % key)
30
28
  user = nil
31
29
  end
32
30
  if user === nil
33
31
  if rollout_rules.size > 0 || rollout_percentage_items.size > 0
34
- ConfigCat.logger.warn("Evaluating get_value('%s'). UserObject missing! You should pass a UserObject to get_value(), in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/" % key)
32
+ @log.warn("Evaluating get_value('%s'). UserObject missing! You should pass a UserObject to get_value(), in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/" % key)
35
33
  end
36
34
  return_value = setting_descriptor.fetch(VALUE, default_value)
37
35
  return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
38
- ConfigCat.logger.info("Returning [%s]" % return_value.to_s)
39
- return return_value, return_variation_id
36
+ @log.info("Returning [%s]" % return_value.to_s)
37
+ return return_value, return_variation_id, nil, nil, nil
40
38
  end
41
39
 
42
40
  log_entries = ["Evaluating get_value('%s')." % key, "User object:\n%s" % user.to_s]
@@ -61,25 +59,25 @@ module ConfigCat
61
59
  if comparator == 0
62
60
  if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
63
61
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
64
- return value, variation_id
62
+ return value, variation_id, rollout_rule, nil, nil
65
63
  end
66
64
  # IS NOT ONE OF
67
65
  elsif comparator == 1
68
66
  if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
69
67
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
70
- return value, variation_id
68
+ return value, variation_id, rollout_rule, nil, nil
71
69
  end
72
70
  # CONTAINS
73
71
  elsif comparator == 2
74
72
  if user_value.to_s.include?(comparison_value.to_s)
75
73
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
76
- return value, variation_id
74
+ return value, variation_id, rollout_rule, nil, nil
77
75
  end
78
76
  # DOES NOT CONTAIN
79
77
  elsif comparator == 3
80
78
  if !user_value.to_s.include?(comparison_value.to_s)
81
79
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
82
- return value, variation_id
80
+ return value, variation_id, rollout_rule, nil, nil
83
81
  end
84
82
  # IS ONE OF, IS NOT ONE OF (Semantic version)
85
83
  elsif (4 <= comparator) && (comparator <= 5)
@@ -92,11 +90,11 @@ module ConfigCat
92
90
  }
93
91
  if match && comparator == 4 || !match && comparator == 5
94
92
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
95
- return value, variation_id
93
+ return value, variation_id, rollout_rule, nil, nil
96
94
  end
97
95
  rescue ArgumentError => e
98
96
  message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
99
- ConfigCat.logger.warn(message)
97
+ @log.warn(message)
100
98
  log_entries.push(message)
101
99
  next
102
100
  end
@@ -110,11 +108,11 @@ module ConfigCat
110
108
  (comparator == 8 && user_value_version > comparison_value_version) ||
111
109
  (comparator == 9 && user_value_version >= comparison_value_version)
112
110
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
113
- return value, variation_id
111
+ return value, variation_id, rollout_rule, nil, nil
114
112
  end
115
113
  rescue ArgumentError => e
116
114
  message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
117
- ConfigCat.logger.warn(message)
115
+ @log.warn(message)
118
116
  log_entries.push(message)
119
117
  next
120
118
  end
@@ -129,11 +127,11 @@ module ConfigCat
129
127
  (comparator == 14 && user_value_float > comparison_value_float) ||
130
128
  (comparator == 15 && user_value_float >= comparison_value_float)
131
129
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
132
- return value, variation_id
130
+ return value, variation_id, rollout_rule, nil, nil
133
131
  end
134
132
  rescue Exception => e
135
133
  message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
136
- ConfigCat.logger.warn(message)
134
+ @log.warn(message)
137
135
  log_entries.push(message)
138
136
  next
139
137
  end
@@ -141,13 +139,13 @@ module ConfigCat
141
139
  elsif comparator == 16
142
140
  if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
143
141
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
144
- return value, variation_id
142
+ return value, variation_id, rollout_rule, nil, nil
145
143
  end
146
144
  # IS NOT ONE OF (Sensitive)
147
145
  elsif comparator == 17
148
146
  if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
149
147
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
150
- return value, variation_id
148
+ return value, variation_id, rollout_rule, nil, nil
151
149
  end
152
150
  end
153
151
  log_entries.push(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
@@ -156,7 +154,7 @@ module ConfigCat
156
154
  if rollout_percentage_items.size > 0
157
155
  user_key = user.get_identifier()
158
156
  hash_candidate = ("%s%s" % [key, user_key]).encode("utf-8")
159
- hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base=16) % 100
157
+ hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base = 16) % 100
160
158
  bucket = 0
161
159
  for rollout_percentage_item in rollout_percentage_items || []
162
160
  bucket += rollout_percentage_item.fetch(PERCENTAGE, 0)
@@ -164,34 +162,31 @@ module ConfigCat
164
162
  percentage_value = rollout_percentage_item.fetch(VALUE, nil)
165
163
  variation_id = rollout_percentage_item.fetch(VARIATION_ID, default_variation_id)
166
164
  log_entries.push("Evaluating %% options. Returning %s" % percentage_value)
167
- return percentage_value, variation_id
165
+ return percentage_value, variation_id, nil, rollout_percentage_item, nil
168
166
  end
169
167
  end
170
168
  end
171
169
  return_value = setting_descriptor.fetch(VALUE, default_value)
172
170
  return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
173
171
  log_entries.push("Returning %s" % return_value)
174
- return return_value, return_variation_id
172
+ return return_value, return_variation_id, nil, nil, nil
175
173
  ensure
176
- ConfigCat.logger.info(log_entries.join("\n"))
174
+ @log.info(log_entries.join("\n"))
177
175
  end
178
176
  end
179
177
 
180
178
  private
181
179
 
182
- def self.format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)
180
+ def format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)
183
181
  return "Evaluating rule: [%s:%s] [%s] [%s] => match, returning: %s" % [comparison_attribute, user_value, COMPARATOR_TEXTS[comparator], comparison_value, value]
184
182
  end
185
183
 
186
- def self.format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value)
184
+ def format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value)
187
185
  return "Evaluating rule: [%s:%s] [%s] [%s] => no match" % [comparison_attribute, user_value, COMPARATOR_TEXTS[comparator], comparison_value]
188
186
  end
189
187
 
190
- def self.format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, error)
188
+ def format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, error)
191
189
  return "Evaluating rule: [%s:%s] [%s] [%s] => SKIP rule. Validation error: %s" % [comparison_attribute, user_value, COMPARATOR_TEXTS[comparator], comparison_value, error]
192
190
  end
193
-
194
191
  end
195
-
196
192
  end
197
-
@@ -1,56 +1,35 @@
1
1
  module ConfigCat
2
-
2
+ # The user object for variation evaluation
3
3
  class User
4
- #
5
- # The user object for variation evaluation
6
- #
7
-
8
4
  PREDEFINED = ["Identifier", "Email", "Country"]
9
5
 
6
+ attr_reader :identifier
7
+
10
8
  def initialize(identifier, email: nil, country: nil, custom: nil)
11
- @__identifier = (!identifier.equal?(nil)) ? identifier : ""
12
- @__data = {"Identifier" => identifier, "Email" => email, "Country" => country}
13
- @__custom = custom
9
+ @identifier = (!identifier.equal?(nil)) ? identifier : ""
10
+ @data = { "Identifier" => identifier, "Email" => email, "Country" => country }
11
+ @custom = custom
14
12
  end
15
13
 
16
- def get_identifier()
17
- return @__identifier
14
+ def get_identifier
15
+ return @identifier
18
16
  end
19
17
 
20
18
  def get_attribute(attribute)
21
19
  attribute = attribute.to_s
22
- if PREDEFINED.include?(attribute)
23
- return @__data[attribute]
24
- end
25
-
26
- if !@__custom.equal?(nil)
27
- @__custom.each do |customField, customValue|
28
- if customField.to_s == attribute
29
- return customValue
30
- end
31
- end
32
- end
20
+ return @data[attribute] if PREDEFINED.include?(attribute)
21
+ return @custom[attribute] if @custom
33
22
  return nil
34
23
  end
35
24
 
36
- def to_s()
37
- r = %Q({\n "Identifier": "#{@__identifier}")
38
- if !@__data["Email"].equal?(nil)
39
- r += %Q(,\n "Email": "#{@__data["Email"]}")
40
- end
41
- if !@__data["Country"].equal?(nil)
42
- r += %Q(,\n "Country": "#{@__data["Country"]}")
43
- end
44
- if !@__custom.equal?(nil)
45
- r += %Q(,\n "Custom": {)
46
- for customField in @__custom
47
- r += %Q(\n "#{customField}": "#{@__custom[customField]}",)
48
- end
49
- r += "\n }"
50
- end
51
- r += "\n}"
52
- return r
25
+ def to_s
26
+ dump = {
27
+ 'Identifier': @identifier,
28
+ 'Email': @data['Email'],
29
+ 'Country': @data['Country'],
30
+ 'Custom': @custom,
31
+ }
32
+ return dump.to_json
53
33
  end
54
34
  end
55
-
56
35
  end
@@ -0,0 +1,10 @@
1
+ module ConfigCat
2
+ class Utils
3
+ DISTANT_FUTURE = Float::INFINITY
4
+ DISTANT_PAST = 0
5
+
6
+ def self.get_utc_now_seconds_since_epoch
7
+ return Time.now.utc.to_f
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module ConfigCat
2
- VERSION = "5.0.1"
2
+ VERSION = "6.0.0"
3
3
  end