configcat 7.0.0 → 8.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.
@@ -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