configcat 5.0.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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(1300, "Cannot find the local config file '#{file_path}'. This is a path that your application provided to the ConfigCat SDK by passing it to the `LocalFileFlagOverrides.new()` method. Read more: https://configcat.com/docs/sdk-reference/ruby/#json-file")
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(2302, "Failed to decode JSON from the local config file '#{@_file_path}'. #{e}")
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(1302, "Failed to read the local config file '#{@_file_path}'. #{e}")
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,39 @@ 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 = "Failed to evaluate setting '#{key}' (the key was not found in config JSON). " \
19
+ "Returning the `default_value` parameter that you specified in your application: '#{default_value}'. " \
20
+ "Available keys: [#{settings.keys.map { |s| "'#{s}'" }.join(", ")}]."
21
+ @log.error(1001, error)
22
+ return default_value, default_variation_id, nil, nil, error
23
23
  end
24
24
 
25
25
  rollout_rules = setting_descriptor.fetch(ROLLOUT_RULES, [])
26
26
  rollout_percentage_items = setting_descriptor.fetch(ROLLOUT_PERCENTAGE_ITEMS, [])
27
27
 
28
- 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)
28
+ user_has_invalid_type = !user.equal?(nil) && !user.class.equal?(User)
29
+ if user_has_invalid_type
30
+ @log.warn(4001, "Cannot evaluate targeting rules and % options for setting '#{key}' (User Object is not an instance of User type).")
30
31
  user = nil
31
32
  end
32
33
  if user === nil
33
- 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)
34
+ if !user_has_invalid_type && (rollout_rules.size > 0 || rollout_percentage_items.size > 0)
35
+ @log.warn(3001, "Cannot evaluate targeting rules and % options for setting '#{key}' (User Object is missing). " \
36
+ "You should pass a User Object to the evaluation methods like `get_value()` in order to make targeting work properly. " \
37
+ "Read more: https://configcat.com/docs/advanced/user-object/")
35
38
  end
36
39
  return_value = setting_descriptor.fetch(VALUE, default_value)
37
40
  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
41
+ @log.info(5000, "Returning [#{return_value}]")
42
+ return return_value, return_variation_id, nil, nil, nil
40
43
  end
41
44
 
42
45
  log_entries = ["Evaluating get_value('%s')." % key, "User object:\n%s" % user.to_s]
@@ -61,25 +64,25 @@ module ConfigCat
61
64
  if comparator == 0
62
65
  if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
63
66
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
64
- return value, variation_id
67
+ return value, variation_id, rollout_rule, nil, nil
65
68
  end
66
69
  # IS NOT ONE OF
67
70
  elsif comparator == 1
68
71
  if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
69
72
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
70
- return value, variation_id
73
+ return value, variation_id, rollout_rule, nil, nil
71
74
  end
72
75
  # CONTAINS
73
76
  elsif comparator == 2
74
77
  if user_value.to_s.include?(comparison_value.to_s)
75
78
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
76
- return value, variation_id
79
+ return value, variation_id, rollout_rule, nil, nil
77
80
  end
78
81
  # DOES NOT CONTAIN
79
82
  elsif comparator == 3
80
83
  if !user_value.to_s.include?(comparison_value.to_s)
81
84
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
82
- return value, variation_id
85
+ return value, variation_id, rollout_rule, nil, nil
83
86
  end
84
87
  # IS ONE OF, IS NOT ONE OF (Semantic version)
85
88
  elsif (4 <= comparator) && (comparator <= 5)
@@ -92,11 +95,11 @@ module ConfigCat
92
95
  }
93
96
  if match && comparator == 4 || !match && comparator == 5
94
97
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
95
- return value, variation_id
98
+ return value, variation_id, rollout_rule, nil, nil
96
99
  end
97
100
  rescue ArgumentError => e
98
101
  message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
99
- ConfigCat.logger.warn(message)
102
+ @log.warn(0, message)
100
103
  log_entries.push(message)
101
104
  next
102
105
  end
@@ -110,11 +113,11 @@ module ConfigCat
110
113
  (comparator == 8 && user_value_version > comparison_value_version) ||
111
114
  (comparator == 9 && user_value_version >= comparison_value_version)
112
115
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
113
- return value, variation_id
116
+ return value, variation_id, rollout_rule, nil, nil
114
117
  end
115
118
  rescue ArgumentError => e
116
119
  message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
117
- ConfigCat.logger.warn(message)
120
+ @log.warn(0, message)
118
121
  log_entries.push(message)
119
122
  next
120
123
  end
@@ -129,11 +132,11 @@ module ConfigCat
129
132
  (comparator == 14 && user_value_float > comparison_value_float) ||
130
133
  (comparator == 15 && user_value_float >= comparison_value_float)
131
134
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
132
- return value, variation_id
135
+ return value, variation_id, rollout_rule, nil, nil
133
136
  end
134
137
  rescue Exception => e
135
138
  message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
136
- ConfigCat.logger.warn(message)
139
+ @log.warn(0, message)
137
140
  log_entries.push(message)
138
141
  next
139
142
  end
@@ -141,13 +144,13 @@ module ConfigCat
141
144
  elsif comparator == 16
142
145
  if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
143
146
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
144
- return value, variation_id
147
+ return value, variation_id, rollout_rule, nil, nil
145
148
  end
146
149
  # IS NOT ONE OF (Sensitive)
147
150
  elsif comparator == 17
148
151
  if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
149
152
  log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
150
- return value, variation_id
153
+ return value, variation_id, rollout_rule, nil, nil
151
154
  end
152
155
  end
153
156
  log_entries.push(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
@@ -156,7 +159,7 @@ module ConfigCat
156
159
  if rollout_percentage_items.size > 0
157
160
  user_key = user.get_identifier()
158
161
  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
162
+ hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base = 16) % 100
160
163
  bucket = 0
161
164
  for rollout_percentage_item in rollout_percentage_items || []
162
165
  bucket += rollout_percentage_item.fetch(PERCENTAGE, 0)
@@ -164,34 +167,31 @@ module ConfigCat
164
167
  percentage_value = rollout_percentage_item.fetch(VALUE, nil)
165
168
  variation_id = rollout_percentage_item.fetch(VARIATION_ID, default_variation_id)
166
169
  log_entries.push("Evaluating %% options. Returning %s" % percentage_value)
167
- return percentage_value, variation_id
170
+ return percentage_value, variation_id, nil, rollout_percentage_item, nil
168
171
  end
169
172
  end
170
173
  end
171
174
  return_value = setting_descriptor.fetch(VALUE, default_value)
172
175
  return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
173
176
  log_entries.push("Returning %s" % return_value)
174
- return return_value, return_variation_id
177
+ return return_value, return_variation_id, nil, nil, nil
175
178
  ensure
176
- ConfigCat.logger.info(log_entries.join("\n"))
179
+ @log.info(5000, log_entries.join("\n"))
177
180
  end
178
181
  end
179
182
 
180
183
  private
181
184
 
182
- def self.format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)
185
+ def format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)
183
186
  return "Evaluating rule: [%s:%s] [%s] [%s] => match, returning: %s" % [comparison_attribute, user_value, COMPARATOR_TEXTS[comparator], comparison_value, value]
184
187
  end
185
188
 
186
- def self.format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value)
189
+ def format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value)
187
190
  return "Evaluating rule: [%s:%s] [%s] [%s] => no match" % [comparison_attribute, user_value, COMPARATOR_TEXTS[comparator], comparison_value]
188
191
  end
189
192
 
190
- def self.format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, error)
193
+ def format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, error)
191
194
  return "Evaluating rule: [%s:%s] [%s] [%s] => SKIP rule. Validation error: %s" % [comparison_attribute, user_value, COMPARATOR_TEXTS[comparator], comparison_value, error]
192
195
  end
193
-
194
196
  end
195
-
196
197
  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.2"
2
+ VERSION = "6.1.0"
3
3
  end