configcat 7.0.0 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -30,13 +30,12 @@ module ConfigCat
30
30
  end
31
31
  end
32
32
 
33
- def get_settings
33
+ def get_config
34
34
  if @polling_mode.is_a?(LazyLoadingMode)
35
35
  entry, _ = fetch_if_older(Utils.get_utc_now_seconds_since_epoch - @polling_mode.cache_refresh_interval_seconds)
36
36
  return !entry.empty? ?
37
- [entry.config.fetch(FEATURE_FLAGS, {}), entry.fetch_time] :
37
+ [entry.config, entry.fetch_time] :
38
38
  [nil, Utils::DISTANT_PAST]
39
-
40
39
  elsif @polling_mode.is_a?(AutoPollingMode) && !@initialized.set?
41
40
  elapsed_time = Utils.get_utc_now_seconds_since_epoch - @start_time # Elapsed time in seconds
42
41
  if elapsed_time < @polling_mode.max_init_wait_time_seconds
@@ -46,20 +45,27 @@ module ConfigCat
46
45
  if !@initialized.set?
47
46
  set_initialized
48
47
  return !@cached_entry.empty? ?
49
- [@cached_entry.config.fetch(FEATURE_FLAGS, {}), @cached_entry.fetch_time] :
48
+ [@cached_entry.config, @cached_entry.fetch_time] :
50
49
  [nil, Utils::DISTANT_PAST]
51
50
  end
52
51
  end
53
52
  end
54
53
 
55
- entry, _ = fetch_if_older(Utils::DISTANT_PAST, prefer_cache: true)
54
+ # If we are initialized, we prefer the cached results
55
+ entry, _ = fetch_if_older(Utils::DISTANT_PAST, prefer_cache: @initialized.set?)
56
56
  return !entry.empty? ?
57
- [entry.config.fetch(FEATURE_FLAGS, {}), entry.fetch_time] :
57
+ [entry.config, entry.fetch_time] :
58
58
  [nil, Utils::DISTANT_PAST]
59
59
  end
60
60
 
61
61
  # :return [RefreshResult]
62
62
  def refresh
63
+ if offline?
64
+ offline_warning = "Client is in offline mode, it cannot initiate HTTP calls."
65
+ @log.warn(3200, offline_warning)
66
+ return RefreshResult.new(success = false, error = offline_warning)
67
+ end
68
+
63
69
  _, error = fetch_if_older(Utils::DISTANT_FUTURE)
64
70
  return RefreshResult.new(success = error.nil?, error = error)
65
71
  end
@@ -74,7 +80,7 @@ module ConfigCat
74
80
  if @polling_mode.is_a?(AutoPollingMode)
75
81
  start_poll
76
82
  end
77
- @log.info(5200, 'Switched to ONLINE mode.')
83
+ @log.info(5200, "Switched to ONLINE mode.")
78
84
  end
79
85
  end
80
86
 
@@ -90,7 +96,7 @@ module ConfigCat
90
96
  @thread.join
91
97
  end
92
98
 
93
- @log.info(5200, 'Switched to OFFLINE mode.')
99
+ @log.info(5200, "Switched to OFFLINE mode.")
94
100
  end
95
101
  end
96
102
 
@@ -111,35 +117,25 @@ module ConfigCat
111
117
  end
112
118
 
113
119
  # :return [ConfigEntry, String] Returns the ConfigEntry object and error message in case of any error.
114
- def fetch_if_older(time, prefer_cache: false)
120
+ def fetch_if_older(threshold, prefer_cache: false)
115
121
  # Sync up with the cache and use it when it's not expired.
116
122
  @lock.synchronize do
117
- if @cached_entry.empty? || @cached_entry.fetch_time > time
118
- entry = read_cache
119
- if !entry.empty? && entry.etag != @cached_entry.etag
120
- @cached_entry = entry
121
- @hooks.invoke_on_config_changed(entry.config[FEATURE_FLAGS])
122
- end
123
-
124
- # Cache isn't expired
125
- if @cached_entry.fetch_time > time
126
- set_initialized
127
- return @cached_entry, nil
128
- end
123
+ # Sync up with the cache and use it when it's not expired.
124
+ from_cache = read_cache
125
+ if !from_cache.empty? && from_cache.etag != @cached_entry.etag
126
+ @cached_entry = from_cache
127
+ @hooks.invoke_on_config_changed(from_cache.config[FEATURE_FLAGS])
129
128
  end
130
129
 
131
- # Use cache anyway (get calls on auto & manual poll must not initiate fetch).
132
- # The initialized check ensures that we subscribe for the ongoing fetch during the
133
- # max init wait time window in case of auto poll.
134
- if prefer_cache && @initialized.set?
130
+ # Cache isn't expired
131
+ if @cached_entry.fetch_time > threshold
132
+ set_initialized
135
133
  return @cached_entry, nil
136
134
  end
137
135
 
138
- # If we are in offline mode we are not allowed to initiate fetch.
139
- if @is_offline
140
- offline_warning = "Client is in offline mode, it cannot initiate HTTP calls."
141
- @log.warn(3200, offline_warning)
142
- return @cached_entry, offline_warning
136
+ # If we are in offline mode or the caller prefers cached values, do not initiate fetch.
137
+ if @is_offline || prefer_cache
138
+ return @cached_entry, nil
143
139
  end
144
140
  end
145
141
 
@@ -0,0 +1,14 @@
1
+ module ConfigCat
2
+ class EvaluationContext
3
+ attr_accessor :key, :setting_type, :user, :visited_keys, :is_missing_user_object_logged, :is_missing_user_object_attribute_logged
4
+
5
+ def initialize(key, setting_type, user, visited_keys = nil, is_missing_user_object_logged = false, is_missing_user_object_attribute_logged = false)
6
+ @key = key
7
+ @setting_type = setting_type
8
+ @user = user
9
+ @visited_keys = visited_keys || []
10
+ @is_missing_user_object_logged = is_missing_user_object_logged
11
+ @is_missing_user_object_attribute_logged = is_missing_user_object_attribute_logged
12
+ end
13
+ end
14
+ end
@@ -1,19 +1,37 @@
1
1
  module ConfigCat
2
2
  class EvaluationDetails
3
3
  attr_reader :key, :value, :variation_id, :fetch_time, :user, :is_default_value, :error,
4
- :matched_evaluation_rule, :matched_evaluation_percentage_rule
4
+ :matched_targeting_rule, :matched_percentage_option
5
5
 
6
6
  def initialize(key:, value:, variation_id: nil, fetch_time: nil, user: nil, is_default_value: false, error: nil,
7
- matched_evaluation_rule: nil, matched_evaluation_percentage_rule: nil)
7
+ matched_targeting_rule: nil, matched_percentage_option: nil)
8
+ # Key of the feature flag or setting.
8
9
  @key = key
10
+
11
+ # Evaluated value of the feature flag or setting.
9
12
  @value = value
13
+
14
+ # Variation ID of the feature flag or setting (if available).
10
15
  @variation_id = variation_id
16
+
17
+ # Time of last successful config download.
11
18
  @fetch_time = fetch_time
19
+
20
+ # The User Object used for the evaluation (if available).
12
21
  @user = user
22
+
23
+ # Indicates whether the default value passed to the setting evaluation methods like ConfigCatClient.get_value,
24
+ # ConfigCatClient.get_value_details, etc. is used as the result of the evaluation.
13
25
  @is_default_value = is_default_value
26
+
27
+ # Error message in case evaluation failed.
14
28
  @error = error
15
- @matched_evaluation_rule = matched_evaluation_rule
16
- @matched_evaluation_percentage_rule = matched_evaluation_percentage_rule
29
+
30
+ # The targeting rule (if any) that matched during the evaluation and was used to return the evaluated value.
31
+ @matched_targeting_rule = matched_targeting_rule
32
+
33
+ # The percentage option (if any) that was used to select the evaluated value.
34
+ @matched_percentage_option = matched_percentage_option
17
35
  end
18
36
 
19
37
  def self.from_error(key, value, error:, variation_id: nil)
@@ -0,0 +1,81 @@
1
+ module ConfigCat
2
+ class EvaluationLogBuilder
3
+ def initialize
4
+ @indent_level = 0
5
+ @text = ''
6
+ end
7
+
8
+ def self.trunc_comparison_value_if_needed(comparator, comparison_value)
9
+ if [
10
+ Comparator::IS_ONE_OF_HASHED,
11
+ Comparator::IS_NOT_ONE_OF_HASHED,
12
+ Comparator::EQUALS_HASHED,
13
+ Comparator::NOT_EQUALS_HASHED,
14
+ Comparator::STARTS_WITH_ANY_OF_HASHED,
15
+ Comparator::NOT_STARTS_WITH_ANY_OF_HASHED,
16
+ Comparator::ENDS_WITH_ANY_OF_HASHED,
17
+ Comparator::NOT_ENDS_WITH_ANY_OF_HASHED,
18
+ Comparator::ARRAY_CONTAINS_ANY_OF_HASHED,
19
+ Comparator::ARRAY_NOT_CONTAINS_ANY_OF_HASHED
20
+ ].include?(comparator)
21
+ if comparison_value.is_a?(Array)
22
+ length = comparison_value.length
23
+ if length > 1
24
+ return "[<#{length} hashed values>]"
25
+ end
26
+ return "[<#{length} hashed value>]"
27
+ end
28
+
29
+ return "'<hashed value>'"
30
+ end
31
+
32
+ if comparison_value.is_a?(Array)
33
+ length_limit = 10
34
+ length = comparison_value.length
35
+ if length > length_limit
36
+ remaining = length - length_limit
37
+ more_text = remaining == 1 ? "<1 more value>" : "<#{remaining} more values>"
38
+
39
+ formatted_strings = comparison_value.first(length_limit).map { |str| "'#{str}'" }.join(", ")
40
+ return "[#{formatted_strings}, ... #{more_text}]"
41
+ end
42
+
43
+ # replace '"' with "'" in the string representation of the array
44
+ formatted_strings = comparison_value.map { |str| "'#{str}'" }.join(", ")
45
+ return "[#{formatted_strings}]"
46
+ end
47
+
48
+ if [Comparator::BEFORE_DATETIME, Comparator::AFTER_DATETIME].include?(comparator)
49
+ time = Utils.get_date_time(comparison_value)
50
+ return "'#{comparison_value}' (#{time.strftime('%Y-%m-%dT%H:%M:%S.%L')}Z UTC)"
51
+ end
52
+
53
+ "'#{comparison_value.to_s}'"
54
+ end
55
+
56
+ def increase_indent
57
+ @indent_level += 1
58
+ self
59
+ end
60
+
61
+ def decrease_indent
62
+ @indent_level = [@indent_level - 1, 0].max
63
+ self
64
+ end
65
+
66
+ def append(text)
67
+ @text += text
68
+ self
69
+ end
70
+
71
+ def new_line(text = nil)
72
+ @text += "\n" + ' ' * @indent_level
73
+ @text += text if text
74
+ self
75
+ end
76
+
77
+ def to_s
78
+ @text
79
+ end
80
+ end
81
+ end
@@ -1,5 +1,5 @@
1
1
  require 'configcat/overridedatasource'
2
- require 'configcat/constants'
2
+ require 'configcat/config'
3
3
 
4
4
 
5
5
  module ConfigCat
@@ -17,14 +17,30 @@ module ConfigCat
17
17
  class LocalDictionaryDataSource < OverrideDataSource
18
18
  def initialize(source, override_behaviour)
19
19
  super(override_behaviour)
20
- @_settings = {}
20
+ @_config = {}
21
21
  source.each do |key, value|
22
- @_settings[key] = { VALUE => value }
22
+ value_type = case value
23
+ when TrueClass, FalseClass
24
+ BOOL_VALUE
25
+ when String
26
+ STRING_VALUE
27
+ when Integer
28
+ INT_VALUE
29
+ when Float
30
+ DOUBLE_VALUE
31
+ else
32
+ UNSUPPORTED_VALUE
33
+ end
34
+
35
+ @_config[FEATURE_FLAGS] ||= {}
36
+ @_config[FEATURE_FLAGS][key] = { VALUE => { value_type => value } }
37
+ setting_type = SettingType.from_type(value.class)
38
+ @_config[FEATURE_FLAGS][key][SETTING_TYPE] = setting_type.to_i unless setting_type.nil?
23
39
  end
24
40
  end
25
41
 
26
42
  def get_overrides
27
- return @_settings
43
+ return @_config
28
44
  end
29
45
  end
30
46
  end
@@ -1,5 +1,5 @@
1
1
  require 'configcat/overridedatasource'
2
- require 'configcat/constants'
2
+ require 'configcat/config'
3
3
 
4
4
 
5
5
  module ConfigCat
@@ -18,17 +18,17 @@ module ConfigCat
18
18
  def initialize(file_path, override_behaviour, log)
19
19
  super(override_behaviour)
20
20
  @log = log
21
- if !File.exists?(file_path)
21
+ if !File.exist?(file_path)
22
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")
23
23
  end
24
24
  @_file_path = file_path
25
- @_settings = nil
25
+ @_config = nil
26
26
  @_cached_file_stamp = 0
27
27
  end
28
28
 
29
29
  def get_overrides
30
30
  reload_file_content()
31
- return @_settings
31
+ return @_config
32
32
  end
33
33
 
34
34
  private
@@ -41,13 +41,29 @@ module ConfigCat
41
41
  file = File.read(@_file_path)
42
42
  data = JSON.parse(file)
43
43
  if data.key?("flags")
44
- @_settings = {}
44
+ @_config = { FEATURE_FLAGS => {} }
45
45
  source = data["flags"]
46
46
  source.each do |key, value|
47
- @_settings[key] = { VALUE => value }
47
+ value_type = case value
48
+ when true, false
49
+ BOOL_VALUE
50
+ when String
51
+ STRING_VALUE
52
+ when Integer
53
+ INT_VALUE
54
+ when Float
55
+ DOUBLE_VALUE
56
+ else
57
+ UNSUPPORTED_VALUE
58
+ end
59
+
60
+ @_config[FEATURE_FLAGS][key] = { VALUE => { value_type => value } }
61
+ setting_type = SettingType.from_type(value.class)
62
+ @_config[FEATURE_FLAGS][key][SETTING_TYPE] = setting_type.to_i unless setting_type.nil?
48
63
  end
49
64
  else
50
- @_settings = data[FEATURE_FLAGS]
65
+ Config.fixup_config_salt_and_segments(data)
66
+ @_config = data
51
67
  end
52
68
  end
53
69
  rescue JSON::ParserError => e