configcat 2.0.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 216277c9c25debbe5d07c33f6c73c850f774c5115b0dd324ee4973514f83d6b9
4
- data.tar.gz: 5e94aca770e2888ccc887befd5681950bf4dc6d6dc1819e57e731cf8e02e5793
3
+ metadata.gz: 118a3db79c8d7d9a0795897380a4dfaf14fecda8a636024abfd9d2433b3af924
4
+ data.tar.gz: 4c85c82b81fe08fcf9b6eed0a40bea06369939301aa09bc12e82b33a3b2176eb
5
5
  SHA512:
6
- metadata.gz: 308f9b0d6fcebebded88f6945aaf4ae025329e9ab21711f587f8fee366024788da514c2e30d1b875b025de12b692783a26ab163732a6c333e6513789a04ca496
7
- data.tar.gz: 8f73ab03c23e10d961564663078574839a91758108b02c8511c449e1d3fd12347186857871e3cd048d7d526fbffd204adbe0d950f55a589cd2d9f501854edd04
6
+ metadata.gz: afe03eda0affc92755ae0eb91196a15bfe61f68a12244270bb5d19b3870bc023fe9ccf3231d8f11f414c4439b49fd2f9481e9a64295c3ab87697511ebcf57b25
7
+ data.tar.gz: 5ac8e3beef89cbc84ef89f2eb5b071fcf5cb348db1c302755ba4fc619c759f9919f80785b2b08a4a583725d35ea6b9ef038d869706956485eec3b4a9d4cadbaa
@@ -10,29 +10,34 @@ module ConfigCat
10
10
  attr_accessor :logger
11
11
  end
12
12
 
13
- def ConfigCat.create_client(api_key)
13
+ def ConfigCat.create_client(sdk_key, data_governance: DataGovernance::GLOBAL)
14
14
  #
15
15
  # Create an instance of ConfigCatClient and setup Auto Poll mode with default options
16
16
  #
17
- # :param api_key: ConfigCat ApiKey to access your configuration.
17
+ # :param sdk_key: ConfigCat SDK Key to access your configuration.
18
+ # :param data_governance:
19
+ # Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
20
+ # https://app.configcat.com/organization/data-governance
21
+ # (Only Organization Admins have access)
18
22
  #
19
- return create_client_with_auto_poll(api_key)
23
+ return create_client_with_auto_poll(sdk_key, data_governance: data_governance)
20
24
  end
21
25
 
22
- def ConfigCat.create_client_with_auto_poll(api_key,
26
+ def ConfigCat.create_client_with_auto_poll(sdk_key,
23
27
  poll_interval_seconds: 60,
24
28
  max_init_wait_time_seconds: 5,
25
29
  on_configuration_changed_callback: nil,
26
30
  config_cache_class: nil,
27
31
  base_url: nil,
28
- proxy_address:nil,
29
- proxy_port:nil,
30
- proxy_user:nil,
31
- proxy_pass:nil)
32
+ proxy_address: nil,
33
+ proxy_port: nil,
34
+ proxy_user: nil,
35
+ proxy_pass: nil,
36
+ data_governance: DataGovernance::GLOBAL)
32
37
  #
33
38
  # Create an instance of ConfigCatClient and setup Auto Poll mode with custom options
34
39
  #
35
- # :param api_key: ConfigCat ApiKey to access your configuration.
40
+ # :param sdk_key: ConfigCat SDK Key to access your configuration.
36
41
  # :param poll_interval_seconds: The client's poll interval in seconds. Default: 60 seconds.
37
42
  # :param on_configuration_changed_callback: You can subscribe to configuration changes with this callback
38
43
  # :param max_init_wait_time_seconds: maximum waiting time for first configuration fetch in polling mode.
@@ -43,9 +48,13 @@ module ConfigCat
43
48
  # :param proxy_port: Proxy port
44
49
  # :param proxy_user: username for proxy authentication
45
50
  # :param proxy_pass: password for proxy authentication
51
+ # :param data_governance:
52
+ # Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
53
+ # https://app.configcat.com/organization/data-governance
54
+ # (Only Organization Admins have access)
46
55
  #
47
- if api_key === nil
48
- raise ConfigCatClientException, "API Key is required."
56
+ if sdk_key === nil
57
+ raise ConfigCatClientException, "SDK Key is required."
49
58
  end
50
59
  if poll_interval_seconds < 1
51
60
  poll_interval_seconds = 1
@@ -53,7 +62,7 @@ module ConfigCat
53
62
  if max_init_wait_time_seconds < 0
54
63
  max_init_wait_time_seconds = 0
55
64
  end
56
- return ConfigCatClient.new(api_key,
65
+ return ConfigCatClient.new(sdk_key,
57
66
  poll_interval_seconds: poll_interval_seconds,
58
67
  max_init_wait_time_seconds: max_init_wait_time_seconds,
59
68
  on_configuration_changed_callback: on_configuration_changed_callback,
@@ -63,21 +72,23 @@ module ConfigCat
63
72
  proxy_address: proxy_address,
64
73
  proxy_port: proxy_port,
65
74
  proxy_user: proxy_user,
66
- proxy_pass: proxy_pass)
75
+ proxy_pass: proxy_pass,
76
+ data_governance: data_governance)
67
77
  end
68
78
 
69
- def ConfigCat.create_client_with_lazy_load(api_key,
79
+ def ConfigCat.create_client_with_lazy_load(sdk_key,
70
80
  cache_time_to_live_seconds: 60,
71
81
  config_cache_class: nil,
72
82
  base_url: nil,
73
- proxy_address:nil,
74
- proxy_port:nil,
75
- proxy_user:nil,
76
- proxy_pass:nil)
83
+ proxy_address: nil,
84
+ proxy_port: nil,
85
+ proxy_user: nil,
86
+ proxy_pass: nil,
87
+ data_governance: DataGovernance::GLOBAL)
77
88
  #
78
89
  # Create an instance of ConfigCatClient and setup Lazy Load mode with custom options
79
90
  #
80
- # :param api_key: ConfigCat ApiKey to access your configuration.
91
+ # :param sdk_key: ConfigCat SDK Key to access your configuration.
81
92
  # :param cache_time_to_live_seconds: The cache TTL.
82
93
  # :param config_cache_class: If you want to use custom caching instead of the client's default InMemoryConfigCache,
83
94
  # You can provide an implementation of ConfigCache.
@@ -86,14 +97,18 @@ module ConfigCat
86
97
  # :param proxy_port: Proxy port
87
98
  # :param proxy_user: username for proxy authentication
88
99
  # :param proxy_pass: password for proxy authentication
100
+ # :param data_governance:
101
+ # Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
102
+ # https://app.configcat.com/organization/data-governance
103
+ # (Only Organization Admins have access)
89
104
  #
90
- if api_key === nil
91
- raise ConfigCatClientException, "API Key is required."
105
+ if sdk_key === nil
106
+ raise ConfigCatClientException, "SDK Key is required."
92
107
  end
93
108
  if cache_time_to_live_seconds < 1
94
109
  cache_time_to_live_seconds = 1
95
110
  end
96
- return ConfigCatClient.new(api_key,
111
+ return ConfigCatClient.new(sdk_key,
97
112
  poll_interval_seconds: 0,
98
113
  max_init_wait_time_seconds: 0,
99
114
  on_configuration_changed_callback: nil,
@@ -103,20 +118,22 @@ module ConfigCat
103
118
  proxy_address: proxy_address,
104
119
  proxy_port: proxy_port,
105
120
  proxy_user: proxy_user,
106
- proxy_pass: proxy_pass)
121
+ proxy_pass: proxy_pass,
122
+ data_governance: data_governance)
107
123
  end
108
124
 
109
- def ConfigCat.create_client_with_manual_poll(api_key,
125
+ def ConfigCat.create_client_with_manual_poll(sdk_key,
110
126
  config_cache_class: nil,
111
127
  base_url: nil,
112
128
  proxy_address:nil,
113
129
  proxy_port:nil,
114
130
  proxy_user:nil,
115
- proxy_pass:nil)
131
+ proxy_pass:nil,
132
+ data_governance: DataGovernance::GLOBAL)
116
133
  #
117
134
  # Create an instance of ConfigCatClient and setup Manual Poll mode with custom options
118
135
  #
119
- # :param api_key: ConfigCat ApiKey to access your configuration.
136
+ # :param sdk_key: ConfigCat SDK Key to access your configuration.
120
137
  # :param config_cache_class: If you want to use custom caching instead of the client's default InMemoryConfigCache,
121
138
  # You can provide an implementation of ConfigCache.
122
139
  # :param base_url: You can set a base_url if you want to use a proxy server between your application and ConfigCat
@@ -124,11 +141,15 @@ module ConfigCat
124
141
  # :param proxy_port: Proxy port
125
142
  # :param proxy_user: username for proxy authentication
126
143
  # :param proxy_pass: password for proxy authentication
144
+ # :param data_governance:
145
+ # Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
146
+ # https://app.configcat.com/organization/data-governance
147
+ # (Only Organization Admins have access)
127
148
  #
128
- if api_key === nil
129
- raise ConfigCatClientException, "API Key is required."
149
+ if sdk_key === nil
150
+ raise ConfigCatClientException, "SDK Key is required."
130
151
  end
131
- return ConfigCatClient.new(api_key,
152
+ return ConfigCatClient.new(sdk_key,
132
153
  poll_interval_seconds: 0,
133
154
  max_init_wait_time_seconds: 0,
134
155
  on_configuration_changed_callback: nil,
@@ -138,7 +159,8 @@ module ConfigCat
138
159
  proxy_address: proxy_address,
139
160
  proxy_port: proxy_port,
140
161
  proxy_user: proxy_user,
141
- proxy_pass: proxy_pass)
162
+ proxy_pass: proxy_pass,
163
+ data_governance: data_governance)
142
164
  end
143
165
 
144
166
  end
@@ -1,9 +1,10 @@
1
1
  require 'configcat/interfaces'
2
+ require 'configcat/constants'
2
3
  require 'concurrent'
3
4
 
4
5
  module ConfigCat
5
6
  class AutoPollingCachePolicy < CachePolicy
6
- def initialize(config_fetcher, config_cache, poll_interval_seconds=60, max_init_wait_time_seconds=5, on_configuration_changed_callback=nil)
7
+ def initialize(config_fetcher, config_cache, cache_key, poll_interval_seconds=60, max_init_wait_time_seconds=5, on_configuration_changed_callback=nil)
7
8
  if poll_interval_seconds < 1
8
9
  poll_interval_seconds = 1
9
10
  end
@@ -12,6 +13,7 @@ module ConfigCat
12
13
  end
13
14
  @_config_fetcher = config_fetcher
14
15
  @_config_cache = config_cache
16
+ @_cache_key = cache_key
15
17
  @_poll_interval_seconds = poll_interval_seconds
16
18
  @_max_init_wait_time_seconds = max_init_wait_time_seconds
17
19
  @_on_configuration_changed_callback = on_configuration_changed_callback
@@ -40,7 +42,7 @@ module ConfigCat
40
42
  end
41
43
  begin
42
44
  @_lock.acquire_read_lock()
43
- return @_config_cache.get()
45
+ return @_config_cache.get(@_cache_key)
44
46
  ensure
45
47
  @_lock.release_read_lock()
46
48
  end
@@ -48,29 +50,32 @@ module ConfigCat
48
50
 
49
51
  def force_refresh()
50
52
  begin
51
- configuration = @_config_fetcher.get_configuration_json()
53
+ configuration_response = @_config_fetcher.get_configuration_json()
52
54
 
53
55
  begin
54
56
  @_lock.acquire_read_lock()
55
- old_configuration = @_config_cache.get()
57
+ old_configuration = @_config_cache.get(@_cache_key)
56
58
  ensure
57
59
  @_lock.release_read_lock()
58
60
  end
59
61
 
60
- if configuration != old_configuration
61
- begin
62
- @_lock.acquire_write_lock()
63
- @_config_cache.set(configuration)
64
- @_initialized = true
65
- ensure
66
- @_lock.release_write_lock()
67
- end
68
- begin
69
- if !@_on_configuration_changed_callback.equal?(nil)
70
- @_on_configuration_changed_callback.()
62
+ if configuration_response.is_fetched()
63
+ configuration = configuration_response.json()
64
+ if configuration != old_configuration
65
+ begin
66
+ @_lock.acquire_write_lock()
67
+ @_config_cache.set(@_cache_key, configuration)
68
+ @_initialized = true
69
+ ensure
70
+ @_lock.release_write_lock()
71
+ end
72
+ begin
73
+ if !@_on_configuration_changed_callback.equal?(nil)
74
+ @_on_configuration_changed_callback.()
75
+ end
76
+ rescue Exception => e
77
+ ConfigCat.logger.error("Exception in on_configuration_changed_callback: #{e.class}:'#{e}'")
71
78
  end
72
- rescue Exception => e
73
- ConfigCat.logger.error("Exception in on_configuration_changed_callback: #{e.class}:'#{e}'")
74
79
  end
75
80
  end
76
81
 
@@ -78,7 +83,7 @@ module ConfigCat
78
83
  @_initialized = true
79
84
  end
80
85
  rescue Exception => e
81
- ConfigCat.logger.error("Double-check your API KEY at https://app.configcat.com/apikey.")
86
+ ConfigCat.logger.error("Double-check your SDK Key at https://app.configcat.com/sdkkey.")
82
87
  ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
83
88
  ConfigCat.logger.error "stacktrace: #{e.backtrace}"
84
89
  end
@@ -3,15 +3,15 @@ require 'configcat/interfaces'
3
3
  module ConfigCat
4
4
  class InMemoryConfigCache < ConfigCache
5
5
  def initialize()
6
- @_value = nil
6
+ @_value = {}
7
7
  end
8
8
 
9
- def get()
10
- return @_value
9
+ def get(key)
10
+ return @_value.fetch(key, nil)
11
11
  end
12
12
 
13
- def set(value)
14
- @_value = value
13
+ def set(key, value)
14
+ @_value[key] = value
15
15
  end
16
16
  end
17
17
  end
@@ -5,10 +5,12 @@ require 'configcat/autopollingcachepolicy'
5
5
  require 'configcat/manualpollingcachepolicy'
6
6
  require 'configcat/lazyloadingcachepolicy'
7
7
  require 'configcat/rolloutevaluator'
8
+ require 'configcat/datagovernance'
8
9
 
9
10
  module ConfigCat
11
+ KeyValue = Struct.new(:key, :value)
10
12
  class ConfigCatClient
11
- def initialize(api_key,
13
+ def initialize(sdk_key,
12
14
  poll_interval_seconds:60,
13
15
  max_init_wait_time_seconds:5,
14
16
  on_configuration_changed_callback:nil,
@@ -18,11 +20,12 @@ module ConfigCat
18
20
  proxy_address:nil,
19
21
  proxy_port:nil,
20
22
  proxy_user:nil,
21
- proxy_pass:nil)
22
- if api_key === nil
23
- raise ConfigCatClientException, "API Key is required."
23
+ proxy_pass:nil,
24
+ data_governance: DataGovernance::GLOBAL)
25
+ if sdk_key === nil
26
+ raise ConfigCatClientException, "SDK Key is required."
24
27
  end
25
- @_api_key = api_key
28
+ @_sdk_key = sdk_key
26
29
 
27
30
  if config_cache_class
28
31
  @_config_cache = config_cache_class.new()
@@ -31,15 +34,15 @@ module ConfigCat
31
34
  end
32
35
 
33
36
  if poll_interval_seconds > 0
34
- @_config_fetcher = CacheControlConfigFetcher.new(api_key, "p", base_url, proxy_address, proxy_port, proxy_user, proxy_pass)
35
- @_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback)
37
+ @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "p", base_url, proxy_address, proxy_port, proxy_user, proxy_pass, data_governance)
38
+ @_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback)
36
39
  else
37
40
  if cache_time_to_live_seconds > 0
38
- @_config_fetcher = CacheControlConfigFetcher.new(api_key, "l", base_url, proxy_address, proxy_port, proxy_user, proxy_pass)
39
- @_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, cache_time_to_live_seconds)
41
+ @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "l", base_url, proxy_address, proxy_port, proxy_user, proxy_pass, data_governance)
42
+ @_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), cache_time_to_live_seconds)
40
43
  else
41
- @_config_fetcher = CacheControlConfigFetcher.new(api_key, "m", base_url, proxy_address, proxy_port, proxy_user, proxy_pass)
42
- @_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache)
44
+ @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "m", base_url, proxy_address, proxy_port, proxy_user, proxy_pass, data_governance)
45
+ @_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key())
43
46
  end
44
47
  end
45
48
  end
@@ -49,7 +52,8 @@ module ConfigCat
49
52
  if config === nil
50
53
  return default_value
51
54
  end
52
- return RolloutEvaluator.evaluate(key, user, default_value, config)
55
+ value, variation_id = RolloutEvaluator.evaluate(key, user, default_value, nil, config)
56
+ return value
53
57
  end
54
58
 
55
59
  def get_all_keys()
@@ -57,7 +61,69 @@ module ConfigCat
57
61
  if config === nil
58
62
  return []
59
63
  end
60
- return config.keys
64
+ feature_flags = config.fetch(FEATURE_FLAGS, nil)
65
+ if feature_flags === nil
66
+ return []
67
+ end
68
+ return feature_flags.keys
69
+ end
70
+
71
+ def get_variation_id(key, default_variation_id, user=nil)
72
+ config = @_cache_policy.get()
73
+ if config === nil
74
+ ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. "\
75
+ "Returning default_variation_id in your get_variation_id call: [%s]." %
76
+ [key, default_variation_id.to_s])
77
+ return default_variation_id
78
+ end
79
+ value, variation_id = RolloutEvaluator.evaluate(key, user, nil, default_variation_id, config)
80
+ return variation_id
81
+ end
82
+
83
+ def get_all_variation_ids(user: nil)
84
+ keys = get_all_keys()
85
+ variation_ids = []
86
+ for key in keys
87
+ variation_id = get_variation_id(key, nil, user)
88
+ if !variation_id.equal?(nil)
89
+ variation_ids.push(variation_id)
90
+ end
91
+ end
92
+ return variation_ids
93
+ end
94
+
95
+ def get_key_and_value(variation_id)
96
+ config = @_cache_policy.get()
97
+ if config === nil
98
+ ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. Returning nil." % variation_id)
99
+ return nil
100
+ end
101
+
102
+ feature_flags = config.fetch(FEATURE_FLAGS, nil)
103
+ if feature_flags === nil
104
+ ConfigCat.logger.warn("Evaluating get_key_and_value('%s') failed. Cache is empty. Returning None." % variation_id)
105
+ return nil
106
+ end
107
+
108
+ for key, value in feature_flags
109
+ if variation_id == value.fetch(VARIATION_ID, nil)
110
+ return KeyValue.new(key, value[VALUE])
111
+ end
112
+
113
+ rollout_rules = value.fetch(ROLLOUT_RULES, [])
114
+ for rollout_rule in rollout_rules
115
+ if variation_id == rollout_rule.fetch(VARIATION_ID, nil)
116
+ return KeyValue.new(key, rollout_rule[VALUE])
117
+ end
118
+ end
119
+
120
+ rollout_percentage_items = value.fetch(ROLLOUT_PERCENTAGE_ITEMS, [])
121
+ for rollout_percentage_item in rollout_percentage_items
122
+ if variation_id == rollout_percentage_item.fetch(VARIATION_ID, nil)
123
+ return KeyValue.new(key, rollout_percentage_item[VALUE])
124
+ end
125
+ end
126
+ end
61
127
  end
62
128
 
63
129
  def force_refresh()
@@ -69,5 +135,11 @@ module ConfigCat
69
135
  @_config_fetcher.close()
70
136
  end
71
137
 
138
+ private
139
+
140
+ def _get_cache_key()
141
+ return Digest::SHA1.hexdigest("ruby_" + CONFIG_FILE_NAME + "_" + @_sdk_key)
142
+ end
143
+
72
144
  end
73
145
  end
@@ -1,41 +1,129 @@
1
1
  require 'configcat/interfaces'
2
2
  require 'configcat/version'
3
+ require 'configcat/datagovernance'
4
+ require 'configcat/constants'
3
5
  require 'net/http'
4
6
  require 'uri'
5
7
  require 'json'
6
8
 
7
9
  module ConfigCat
8
- BASE_URL = "https://cdn.configcat.com"
10
+ BASE_URL_GLOBAL = "https://cdn-global.configcat.com"
11
+ BASE_URL_EU_ONLY = "https://cdn-eu.configcat.com"
9
12
  BASE_PATH = "configuration-files/"
10
- BASE_EXTENSION = "/config_v3.json"
13
+ BASE_EXTENSION = "/" + CONFIG_FILE_NAME + ".json"
14
+
15
+ class RedirectMode
16
+ NO_REDIRECT = 0
17
+ SHOULD_REDIRECT = 1
18
+ FORCE_REDIRECT = 2
19
+ end
20
+
21
+ class FetchResponse
22
+ def initialize(response)
23
+ @_response = response
24
+ end
25
+
26
+ # Returns the json-encoded content of a response, if any
27
+ def json()
28
+ return JSON.parse(@_response.body)
29
+ end
30
+
31
+ # Gets whether a new configuration value was fetched or not
32
+ def is_fetched()
33
+ code = @_response.code.to_i
34
+ return 200 <= code && code < 300
35
+ end
36
+
37
+ # Gets whether the fetch resulted a '304 Not Modified' or not
38
+ def is_not_modified()
39
+ return @_response.code == "304"
40
+ end
41
+ end
11
42
 
12
43
  class CacheControlConfigFetcher < ConfigFetcher
13
- def initialize(api_key, mode, base_url=nil, proxy_address=nil, proxy_port=nil, proxy_user=nil, proxy_pass=nil)
14
- @_api_key = api_key
15
- @_etag = nil
44
+ def initialize(sdk_key, mode, base_url=nil, proxy_address=nil, proxy_port=nil, proxy_user=nil, proxy_pass=nil,
45
+ data_governance=DataGovernance::GLOBAL)
46
+ @_sdk_key = sdk_key
47
+ @_proxy_address = proxy_address
48
+ @_proxy_port = proxy_port
49
+ @_proxy_user = proxy_user
50
+ @_proxy_pass = proxy_pass
51
+ @_etag = ""
16
52
  @_headers = {"User-Agent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "X-ConfigCat-UserAgent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "Content-Type" => "application/json"}
17
53
  if !base_url.equal?(nil)
54
+ @_base_url_overridden = true
18
55
  @_base_url = base_url.chomp("/")
19
56
  else
20
- @_base_url = BASE_URL
57
+ @_base_url_overridden = false
58
+ if data_governance == DataGovernance::EU_ONLY
59
+ @_base_url = BASE_URL_EU_ONLY
60
+ else
61
+ @_base_url = BASE_URL_GLOBAL
62
+ end
21
63
  end
22
- uri = URI.parse(@_base_url)
23
- @_http = Net::HTTP.new(uri.host, uri.port, proxy_address, proxy_port, proxy_user, proxy_pass)
24
- @_http.use_ssl = true if uri.scheme == 'https'
25
- @_http.open_timeout = 10 # in seconds
26
- @_http.read_timeout = 30 # in seconds
27
64
  end
28
65
 
29
- def get_configuration_json()
66
+ # Returns the FetchResponse object contains configuration json Dictionary
67
+ def get_configuration_json(retries=0)
30
68
  ConfigCat.logger.debug "Fetching configuration from ConfigCat"
31
- uri = URI.parse((((@_base_url + ("/")) + BASE_PATH) + @_api_key) + BASE_EXTENSION)
32
- @_headers["If-None-Match"] = @_etag unless @_etag.nil?
33
- request = Net::HTTP::Get.new(uri.request_uri, @_headers)
69
+ uri = URI.parse((((@_base_url + ("/")) + BASE_PATH) + @_sdk_key) + BASE_EXTENSION)
70
+ headers = @_headers
71
+ headers["If-None-Match"] = @_etag unless @_etag.empty?
72
+ _create_http()
73
+ request = Net::HTTP::Get.new(uri.request_uri, headers)
34
74
  response = @_http.request(request)
35
- json = JSON.parse(response.body)
36
- @_etag = response["ETag"]
75
+ etag = response["ETag"]
76
+ @_etag = etag unless etag.nil? || etag.empty?
37
77
  ConfigCat.logger.debug "ConfigCat configuration json fetch response code:#{response.code} Cached:#{response['ETag']}"
38
- return json
78
+ fetch_response = FetchResponse.new(response)
79
+
80
+ # If there wasn't a config change, we return the response.
81
+ if !fetch_response.is_fetched()
82
+ return fetch_response
83
+ end
84
+
85
+ preferences = fetch_response.json().fetch(PREFERENCES, nil)
86
+ if preferences === nil
87
+ return fetch_response
88
+ end
89
+
90
+ base_url = preferences.fetch(BASE_URL, nil)
91
+
92
+ # If the base_url is the same as the last called one, just return the response.
93
+ if base_url.equal?(nil) || @_base_url == base_url
94
+ return fetch_response
95
+ end
96
+
97
+ redirect = preferences.fetch(REDIRECT, nil)
98
+ # If the base_url is overridden, and the redirect parameter is not 2 (force),
99
+ # the SDK should not redirect the calls and it just have to return the response.
100
+ if @_base_url_overridden && redirect != RedirectMode::FORCE_REDIRECT
101
+ return fetch_response
102
+ end
103
+
104
+ # The next call should use the base_url provided in the config json
105
+ @_base_url = base_url
106
+
107
+ # If the redirect property == 0 (redirect not needed), return the response
108
+ if redirect == RedirectMode::NO_REDIRECT
109
+ # Return the response
110
+ return fetch_response
111
+ end
112
+
113
+ # Try to download again with the new url
114
+
115
+ if redirect == RedirectMode::SHOULD_REDIRECT
116
+ ConfigCat.logger.warn("Your data_governance parameter at ConfigCatClient initialization is not in sync with your preferences on the ConfigCat Dashboard: https://app.configcat.com/organization/data-governance. Only Organization Admins can set this preference.")
117
+ end
118
+
119
+ # To prevent loops we check if we retried at least 3 times with the new base_url
120
+ if retries >= 2
121
+ ConfigCat.logger.error("Redirect loop during config.json fetch. Please contact support@configcat.com.")
122
+ return fetch_response
123
+ end
124
+
125
+ # Retry the config download with the new base_url
126
+ return get_configuration_json(retries + 1)
39
127
  end
40
128
 
41
129
  def close()
@@ -43,5 +131,19 @@ module ConfigCat
43
131
  @_http = nil
44
132
  end
45
133
  end
134
+
135
+ private
136
+
137
+ def _create_http()
138
+ uri = URI.parse(@_base_url)
139
+ use_ssl = true if uri.scheme == 'https'
140
+ if @_http.equal?(nil) || @_http.address != uri.host || @_http.port != uri.port || @_http.use_ssl? != use_ssl
141
+ close()
142
+ @_http = Net::HTTP.new(uri.host, uri.port, @_proxy_address, @_proxy_port, @_proxy_user, @_proxy_pass)
143
+ @_http.use_ssl = use_ssl
144
+ @_http.open_timeout = 10 # in seconds
145
+ @_http.read_timeout = 30 # in seconds
146
+ end
147
+ end
46
148
  end
47
149
  end
@@ -0,0 +1,17 @@
1
+ module ConfigCat
2
+ CONFIG_FILE_NAME = "config_v5"
3
+
4
+ PREFERENCES = "p"
5
+ BASE_URL = "u"
6
+ REDIRECT = "r"
7
+
8
+ FEATURE_FLAGS = "f"
9
+ VALUE = "v"
10
+ COMPARATOR = "t"
11
+ COMPARISON_ATTRIBUTE = "a"
12
+ COMPARISON_VALUE = "c"
13
+ ROLLOUT_PERCENTAGE_ITEMS = "p"
14
+ PERCENTAGE = "p"
15
+ ROLLOUT_RULES = "r"
16
+ VARIATION_ID = "i"
17
+ end
@@ -0,0 +1,10 @@
1
+ module ConfigCat
2
+ class DataGovernance
3
+ # Control the location of the config.json files containing your feature flags
4
+ # and settings within the ConfigCat CDN.
5
+ # Global: Select this if your feature flags are published to all global CDN nodes.
6
+ # EuOnly: Select this if your feature flags are published to CDN nodes only in the EU.
7
+ GLOBAL = 0
8
+ EU_ONLY = 1
9
+ end
10
+ end
@@ -23,13 +23,13 @@ module ConfigCat
23
23
  # Config cache interface
24
24
  #
25
25
 
26
- def get()
26
+ def get(key)
27
27
  #
28
28
  # :returns the config json object from the cache
29
29
  #
30
30
  end
31
31
 
32
- def set(value)
32
+ def set(key, value)
33
33
  #
34
34
  # Sets the config json cache.
35
35
  #
@@ -1,15 +1,18 @@
1
1
  require 'configcat/interfaces'
2
+ require 'configcat/constants'
2
3
  require 'concurrent'
3
4
 
5
+
4
6
  module ConfigCat
5
7
  class LazyLoadingCachePolicy < CachePolicy
6
8
 
7
- def initialize(config_fetcher, config_cache, cache_time_to_live_seconds=60)
9
+ def initialize(config_fetcher, config_cache, cache_key, cache_time_to_live_seconds=60)
8
10
  if cache_time_to_live_seconds < 1
9
11
  cache_time_to_live_seconds = 1
10
12
  end
11
13
  @_config_fetcher = config_fetcher
12
14
  @_config_cache = config_cache
15
+ @_cache_key = cache_key
13
16
  @_cache_time_to_live = cache_time_to_live_seconds
14
17
  @_lock = Concurrent::ReadWriteLock.new()
15
18
  @_last_updated = nil
@@ -20,7 +23,7 @@ module ConfigCat
20
23
  @_lock.acquire_read_lock()
21
24
  utc_now = Time.now.utc
22
25
  if !@_last_updated.equal?(nil) && (@_last_updated + @_cache_time_to_live > utc_now)
23
- config = @_config_cache.get()
26
+ config = @_config_cache.get(@_cache_key)
24
27
  if !config.equal?(nil)
25
28
  return config
26
29
  end
@@ -31,7 +34,7 @@ module ConfigCat
31
34
  force_refresh()
32
35
  begin
33
36
  @_lock.acquire_read_lock()
34
- config = @_config_cache.get()
37
+ config = @_config_cache.get(@_cache_key)
35
38
  return config
36
39
  ensure
37
40
  @_lock.release_read_lock()
@@ -40,16 +43,19 @@ module ConfigCat
40
43
 
41
44
  def force_refresh()
42
45
  begin
43
- configuration = @_config_fetcher.get_configuration_json()
44
- begin
45
- @_lock.acquire_write_lock()
46
- @_config_cache.set(configuration)
47
- @_last_updated = Time.now.utc
48
- ensure
49
- @_lock.release_write_lock()
46
+ configuration_response = @_config_fetcher.get_configuration_json()
47
+ if configuration_response.is_fetched()
48
+ configuration = configuration_response.json()
49
+ begin
50
+ @_lock.acquire_write_lock()
51
+ @_config_cache.set(@_cache_key, configuration)
52
+ @_last_updated = Time.now.utc
53
+ ensure
54
+ @_lock.release_write_lock()
55
+ end
50
56
  end
51
57
  rescue StandardError => e
52
- ConfigCat.logger.error("Double-check your API KEY at https://app.configcat.com/apikey.")
58
+ ConfigCat.logger.error("Double-check your SDK Key at https://app.configcat.com/sdkkey.")
53
59
  ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
54
60
  ConfigCat.logger.error "stacktrace: #{e.backtrace}"
55
61
  end
@@ -1,18 +1,20 @@
1
1
  require 'configcat/interfaces'
2
+ require 'configcat/constants'
2
3
  require 'concurrent'
3
4
 
4
5
  module ConfigCat
5
6
  class ManualPollingCachePolicy < CachePolicy
6
- def initialize(config_fetcher, config_cache)
7
+ def initialize(config_fetcher, config_cache, cache_key)
7
8
  @_config_fetcher = config_fetcher
8
9
  @_config_cache = config_cache
10
+ @_cache_key = cache_key
9
11
  @_lock = Concurrent::ReadWriteLock.new()
10
12
  end
11
13
 
12
14
  def get()
13
15
  begin
14
16
  @_lock.acquire_read_lock()
15
- config = @_config_cache.get()
17
+ config = @_config_cache.get(@_cache_key)
16
18
  return config
17
19
  ensure
18
20
  @_lock.release_read_lock()
@@ -21,15 +23,18 @@ module ConfigCat
21
23
 
22
24
  def force_refresh()
23
25
  begin
24
- configuration = @_config_fetcher.get_configuration_json()
25
- begin
26
- @_lock.acquire_write_lock()
27
- @_config_cache.set(configuration)
28
- ensure
29
- @_lock.release_write_lock()
26
+ configuration_response = @_config_fetcher.get_configuration_json()
27
+ if configuration_response.is_fetched()
28
+ configuration = configuration_response.json()
29
+ begin
30
+ @_lock.acquire_write_lock()
31
+ @_config_cache.set(@_cache_key, configuration)
32
+ ensure
33
+ @_lock.release_write_lock()
34
+ end
30
35
  end
31
36
  rescue StandardError => e
32
- ConfigCat.logger.error("Double-check your API KEY at https://app.configcat.com/apikey.")
37
+ ConfigCat.logger.error("Double-check your SDK Key at https://app.configcat.com/sdkkey.")
33
38
  ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
34
39
  ConfigCat.logger.error "stacktrace: #{e.backtrace}"
35
40
  end
@@ -1,4 +1,5 @@
1
1
  require 'configcat/user'
2
+ require 'configcat/constants'
2
3
  require 'digest'
3
4
  require 'semantic'
4
5
 
@@ -6,21 +7,19 @@ module ConfigCat
6
7
  class RolloutEvaluator
7
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)"]
8
9
 
9
- VALUE = "v"
10
- COMPARATOR = "t"
11
- COMPARISON_ATTRIBUTE = "a"
12
- COMPARISON_VALUE = "c"
13
- ROLLOUT_PERCENTAGE_ITEMS = "p"
14
- PERCENTAGE = "p"
15
- ROLLOUT_RULES = "r"
16
-
17
- def self.evaluate(key, user, default_value, config)
10
+ def self.evaluate(key, user, default_value, default_variation_id, config)
18
11
  ConfigCat.logger.info("Evaluating get_value('%s')." % key)
19
12
 
20
- setting_descriptor = config.fetch(key, nil)
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
18
+
19
+ setting_descriptor = feature_flags.fetch(key, nil)
21
20
  if setting_descriptor === nil
22
- 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, config.keys.join(", ")])
23
- return default_value
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
24
23
  end
25
24
 
26
25
  rollout_rules = setting_descriptor.fetch(ROLLOUT_RULES, [])
@@ -35,8 +34,9 @@ module ConfigCat
35
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)
36
35
  end
37
36
  return_value = setting_descriptor.fetch(VALUE, default_value)
37
+ return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
38
38
  ConfigCat.logger.info("Returning [%s]" % return_value.to_s)
39
- return return_value
39
+ return return_value, return_variation_id
40
40
  end
41
41
 
42
42
  ConfigCat.logger.info("User object:\n%s" % user.to_s)
@@ -54,30 +54,31 @@ module ConfigCat
54
54
  end
55
55
 
56
56
  value = rollout_rule.fetch(VALUE, nil)
57
+ variation_id = rollout_rule.fetch(VARIATION_ID, default_variation_id)
57
58
 
58
59
  # IS ONE OF
59
60
  if comparator == 0
60
61
  if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
61
62
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
62
- return value
63
+ return value, variation_id
63
64
  end
64
65
  # IS NOT ONE OF
65
66
  elsif comparator == 1
66
67
  if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
67
68
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
68
- return value
69
+ return value, variation_id
69
70
  end
70
71
  # CONTAINS
71
72
  elsif comparator == 2
72
73
  if user_value.to_s.include?(comparison_value.to_s)
73
74
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
74
- return value
75
+ return value, variation_id
75
76
  end
76
77
  # DOES NOT CONTAIN
77
78
  elsif comparator == 3
78
79
  if !user_value.to_s.include?(comparison_value.to_s)
79
80
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
80
- return value
81
+ return value, variation_id
81
82
  end
82
83
  # IS ONE OF, IS NOT ONE OF (Semantic version)
83
84
  elsif (4 <= comparator) && (comparator <= 5)
@@ -90,7 +91,7 @@ module ConfigCat
90
91
  }
91
92
  if match && comparator == 4 || !match && comparator == 5
92
93
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
93
- return value
94
+ return value, variation_id
94
95
  end
95
96
  rescue ArgumentError => e
96
97
  ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s))
@@ -106,7 +107,7 @@ module ConfigCat
106
107
  (comparator == 8 && user_value_version > comparison_value_version) ||
107
108
  (comparator == 9 && user_value_version >= comparison_value_version)
108
109
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
109
- return value
110
+ return value, variation_id
110
111
  end
111
112
  rescue ArgumentError => e
112
113
  ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s))
@@ -123,7 +124,7 @@ module ConfigCat
123
124
  (comparator == 14 && user_value_float > comparison_value_float) ||
124
125
  (comparator == 15 && user_value_float >= comparison_value_float)
125
126
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
126
- return value
127
+ return value, variation_id
127
128
  end
128
129
  rescue Exception => e
129
130
  ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s))
@@ -133,13 +134,13 @@ module ConfigCat
133
134
  elsif comparator == 16
134
135
  if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
135
136
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
136
- return value
137
+ return value, variation_id
137
138
  end
138
139
  # IS NOT ONE OF (Sensitive)
139
140
  elsif comparator == 17
140
141
  if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
141
142
  ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
142
- return value
143
+ return value, variation_id
143
144
  end
144
145
  end
145
146
  ConfigCat.logger.info(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
@@ -154,14 +155,16 @@ module ConfigCat
154
155
  bucket += rollout_percentage_item.fetch(PERCENTAGE, 0)
155
156
  if hash_val < bucket
156
157
  percentage_value = rollout_percentage_item.fetch(VALUE, nil)
158
+ variation_id = rollout_percentage_item.fetch(VARIATION_ID, default_variation_id)
157
159
  ConfigCat.logger.info("Evaluating %% options. Returning %s" % percentage_value)
158
- return percentage_value
160
+ return percentage_value, variation_id
159
161
  end
160
162
  end
161
163
  end
162
- def_value = setting_descriptor.fetch(VALUE, default_value)
163
- ConfigCat.logger.info("Returning %s" % def_value)
164
- return def_value
164
+ return_value = setting_descriptor.fetch(VALUE, default_value)
165
+ return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
166
+ ConfigCat.logger.info("Returning %s" % return_value)
167
+ return return_value, return_variation_id
165
168
  end
166
169
 
167
170
  private
@@ -5,11 +5,11 @@ module ConfigCat
5
5
  # The user object for variation evaluation
6
6
  #
7
7
 
8
- PREDEFINED = ["identifier", "email", "country"]
8
+ PREDEFINED = ["Identifier", "Email", "Country"]
9
9
 
10
10
  def initialize(identifier, email: nil, country: nil, custom: nil)
11
- @__identifier = identifier
12
- @__data = {"identifier" => identifier, "email" => email, "country" => country}
11
+ @__identifier = (!identifier.equal?(nil)) ? identifier : ""
12
+ @__data = {"Identifier" => identifier, "Email" => email, "Country" => country}
13
13
  @__custom = custom
14
14
  end
15
15
 
@@ -18,14 +18,14 @@ module ConfigCat
18
18
  end
19
19
 
20
20
  def get_attribute(attribute)
21
- attribute = attribute.to_s.downcase()
21
+ attribute = attribute.to_s
22
22
  if PREDEFINED.include?(attribute)
23
23
  return @__data[attribute]
24
24
  end
25
25
 
26
26
  if !@__custom.equal?(nil)
27
27
  @__custom.each do |customField, customValue|
28
- if customField.to_s.downcase() == attribute
28
+ if customField.to_s == attribute
29
29
  return customValue
30
30
  end
31
31
  end
@@ -1,3 +1,3 @@
1
1
  module ConfigCat
2
- VERSION = "2.0.2"
2
+ VERSION = "4.0.0"
3
3
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configcat
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ConfigCat
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-24 00:00:00.000000000 Z
11
+ date: 2020-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '1.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '1.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: semantic
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '1.6'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '1.6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -56,30 +56,44 @@ dependencies:
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '12.3'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '12.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: coveralls
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: '0'
89
+ version: '3.0'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - ">="
94
+ - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: '0'
96
+ version: '3.0'
83
97
  description: Feature Flags created by developers for developers with ❤️. ConfigCat
84
98
  lets you manage feature flags across frontend, backend, mobile, and desktop apps
85
99
  without (re)deploying code. % rollouts, user targeting, segmentation. Feature toggle
@@ -96,6 +110,8 @@ files:
96
110
  - lib/configcat/configcache.rb
97
111
  - lib/configcat/configcatclient.rb
98
112
  - lib/configcat/configfetcher.rb
113
+ - lib/configcat/constants.rb
114
+ - lib/configcat/datagovernance.rb
99
115
  - lib/configcat/interfaces.rb
100
116
  - lib/configcat/lazyloadingcachepolicy.rb
101
117
  - lib/configcat/manualpollingcachepolicy.rb
@@ -112,7 +128,7 @@ require_paths:
112
128
  - lib
113
129
  required_ruby_version: !ruby/object:Gem::Requirement
114
130
  requirements:
115
- - - "~>"
131
+ - - ">="
116
132
  - !ruby/object:Gem::Version
117
133
  version: '2.2'
118
134
  required_rubygems_version: !ruby/object:Gem::Requirement
@@ -121,8 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
137
  - !ruby/object:Gem::Version
122
138
  version: '0'
123
139
  requirements: []
124
- rubyforge_project:
125
- rubygems_version: 2.7.7
140
+ rubygems_version: 3.0.8
126
141
  signing_key:
127
142
  specification_version: 4
128
143
  summary: ConfigCat SDK for Ruby.