Skip to content

Commit

Permalink
flag overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
kp-cat committed Mar 31, 2022
1 parent e463f89 commit 7791140
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 32 deletions.
9 changes: 9 additions & 0 deletions lib/configcat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def ConfigCat.create_client_with_auto_poll(sdk_key,
proxy_pass: nil,
open_timeout: 10,
read_timeout: 30,
flag_overrides: nil,
data_governance: DataGovernance::GLOBAL)
#
# Create an instance of ConfigCatClient and setup Auto Poll mode with custom options
Expand All @@ -52,6 +53,7 @@ def ConfigCat.create_client_with_auto_poll(sdk_key,
# :param proxy_pass: password for proxy authentication
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
# :param data_governance:
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
# https://app.configcat.com/organization/data-governance
Expand Down Expand Up @@ -79,6 +81,7 @@ def ConfigCat.create_client_with_auto_poll(sdk_key,
proxy_pass: proxy_pass,
open_timeout: open_timeout,
read_timeout: read_timeout,
flag_overrides: flag_overrides,
data_governance: data_governance)
end

Expand All @@ -92,6 +95,7 @@ def ConfigCat.create_client_with_lazy_load(sdk_key,
proxy_pass: nil,
open_timeout: 10,
read_timeout: 30,
flag_overrides: nil,
data_governance: DataGovernance::GLOBAL)
#
# Create an instance of ConfigCatClient and setup Lazy Load mode with custom options
Expand All @@ -107,6 +111,7 @@ def ConfigCat.create_client_with_lazy_load(sdk_key,
# :param proxy_pass: password for proxy authentication
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
# :param data_governance:
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
# https://app.configcat.com/organization/data-governance
Expand All @@ -131,6 +136,7 @@ def ConfigCat.create_client_with_lazy_load(sdk_key,
proxy_pass: proxy_pass,
open_timeout: open_timeout,
read_timeout: read_timeout,
flag_overrides: flag_overrides,
data_governance: data_governance)
end

Expand All @@ -143,6 +149,7 @@ def ConfigCat.create_client_with_manual_poll(sdk_key,
proxy_pass: nil,
open_timeout: 10,
read_timeout: 30,
flag_overrides: nil,
data_governance: DataGovernance::GLOBAL)
#
# Create an instance of ConfigCatClient and setup Manual Poll mode with custom options
Expand All @@ -157,6 +164,7 @@ def ConfigCat.create_client_with_manual_poll(sdk_key,
# :param proxy_pass: password for proxy authentication
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
# :param data_governance:
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
# https://app.configcat.com/organization/data-governance
Expand All @@ -178,6 +186,7 @@ def ConfigCat.create_client_with_manual_poll(sdk_key,
proxy_pass: proxy_pass,
open_timeout: open_timeout,
read_timeout: read_timeout,
flag_overrides: flag_overrides,
data_governance: data_governance)
end

Expand Down
104 changes: 72 additions & 32 deletions lib/configcat/configcatclient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@
require 'configcat/rolloutevaluator'
require 'configcat/datagovernance'


module ConfigCat
KeyValue = Struct.new(:key, :value)
class ConfigCatClient
@@sdk_keys = []

def initialize(sdk_key,
poll_interval_seconds:60,
max_init_wait_time_seconds:5,
on_configuration_changed_callback:nil,
cache_time_to_live_seconds:60,
config_cache_class:nil,
base_url:nil,
proxy_address:nil,
proxy_port:nil,
proxy_user:nil,
proxy_pass:nil,
open_timeout:10,
read_timeout:30,
poll_interval_seconds: 60,
max_init_wait_time_seconds: 5,
on_configuration_changed_callback: nil,
cache_time_to_live_seconds: 60,
config_cache_class: nil,
base_url: nil,
proxy_address: nil,
proxy_port: nil,
proxy_user: nil,
proxy_pass: nil,
open_timeout: 10,
read_timeout: 30,
flag_overrides: nil,
data_governance: DataGovernance::GLOBAL)
if sdk_key === nil
raise ConfigCatClientException, "SDK Key is required."
Expand All @@ -39,47 +41,55 @@ def initialize(sdk_key,
end

@_sdk_key = sdk_key
@_override_data_source = flag_overrides

if config_cache_class
@_config_cache = config_cache_class.new()
else
@_config_cache = InMemoryConfigCache.new()
end

if poll_interval_seconds > 0
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "p", base_url: base_url,
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
open_timeout: open_timeout, read_timeout: read_timeout,
data_governance: data_governance)
@_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback)
if !@_override_data_source.equal?(nil) && @_override_data_source.get_behaviour() == OverrideBehaviour::LOCAL_ONLY
@_config_fetcher = nil
@_cache_policy = nil
else
if cache_time_to_live_seconds > 0
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "l", base_url: base_url,
if poll_interval_seconds > 0
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "p", base_url: base_url,
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
open_timeout: open_timeout, read_timeout: read_timeout,
data_governance: data_governance)
@_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), cache_time_to_live_seconds)
@_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback)
else
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "m", base_url: base_url,
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
open_timeout: open_timeout, read_timeout: read_timeout,
data_governance: data_governance)
@_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key())
if cache_time_to_live_seconds > 0
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "l", base_url: base_url,
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
open_timeout: open_timeout, read_timeout: read_timeout,
data_governance: data_governance)
@_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), cache_time_to_live_seconds)
else
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "m", base_url: base_url,
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
open_timeout: open_timeout, read_timeout: read_timeout,
data_governance: data_governance)
@_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key())
end
end
end
end

def get_value(key, default_value, user=nil)
config = @_cache_policy.get()
config = _get_settings()
if config === nil
ConfigCat.logger.warn("Evaluating get_value('%s') failed. Cache is empty. "\
"Returning default_value in your get_value call: [%s]." % [key, default_value.to_s])
return default_value
end
value, variation_id = RolloutEvaluator.evaluate(key, user, default_value, nil, config)
return value
end

def get_all_keys()
config = @_cache_policy.get()
config = _get_settings()
if config === nil
return []
end
Expand All @@ -91,7 +101,7 @@ def get_all_keys()
end

def get_variation_id(key, default_variation_id, user=nil)
config = @_cache_policy.get()
config = _get_settings()
if config === nil
ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. "\
"Returning default_variation_id in your get_variation_id call: [%s]." %
Expand All @@ -115,7 +125,7 @@ def get_all_variation_ids(user: nil)
end

def get_key_and_value(variation_id)
config = @_cache_policy.get()
config = _get_settings()
if config === nil
ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. Returning nil." % variation_id)
return nil
Expand Down Expand Up @@ -153,13 +163,43 @@ def force_refresh()
end

def stop()
@_cache_policy.stop()
@_config_fetcher.close()
@_cache_policy.stop() if @_cache_policy
@_config_fetcher.close() if @_config_fetcher
@@sdk_keys.delete(@_sdk_key)
end

private

def _get_settings()
if !@_override_data_source.nil?
behaviour = @_override_data_source.get_behaviour()
if behaviour == OverrideBehaviour::LOCAL_ONLY
return @_override_data_source.get_overrides()
else
if behaviour == OverrideBehaviour::REMOTE_OVER_LOCAL
remote_settings = @_cache_policy.get()
local_settings = @_override_data_source.get_overrides()
result = local_settings.clone()
if remote_settings.key?(FEATURE_FLAGS) && local_settings.key?(FEATURE_FLAGS)
result[FEATURE_FLAGS] = result[FEATURE_FLAGS].merge(remote_settings[FEATURE_FLAGS])
end
return result
else
if behaviour == OverrideBehaviour::LOCAL_OVER_REMOTE
remote_settings = @_cache_policy.get()
local_settings = @_override_data_source.get_overrides()
result = remote_settings.clone()
if remote_settings.key?(FEATURE_FLAGS) && local_settings.key?(FEATURE_FLAGS)
result[FEATURE_FLAGS] = result[FEATURE_FLAGS].merge(local_settings[FEATURE_FLAGS])
end
return result
end
end
end
end
return @_cache_policy.get()
end

def _get_cache_key()
return Digest::SHA1.hexdigest("ruby_" + CONFIG_FILE_NAME + "_" + @_sdk_key)
end
Expand Down
20 changes: 20 additions & 0 deletions lib/configcat/localdictionarydatasource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'configcat/overridedatasource'
require 'configcat/constants'


module ConfigCat
class LocalDictionaryDataSource < OverrideDataSource
def initialize(source, override_behaviour)
super(override_behaviour)
dictionary = {}
source.each do |key, value|
dictionary[key] = {VALUE => value}
end
@_settings = {FEATURE_FLAGS => dictionary}
end

def get_overrides()
return @_settings
end
end
end
32 changes: 32 additions & 0 deletions lib/configcat/overridedatasource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module ConfigCat
class OverrideBehaviour
# When evaluating values, the SDK will not use feature flags & settings from the ConfigCat CDN, but it will use
# all feature flags & settings that are loaded from local-override sources.
LOCAL_ONLY = 0

# When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN,
# plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is
# defined both in the fetched and the local-override source then the local-override version will take precedence.
LOCAL_OVER_REMOTE = 1

# When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN,
# plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is
# defined both in the fetched and the local-override source then the fetched version will take precedence.
REMOTE_OVER_LOCAL = 2
end

class OverrideDataSource
def initialize(override_behaviour)
@_override_behaviour = override_behaviour
end

def get_behaviour()
return @_override_behaviour
end

def get_overrides()
# :returns the override dictionary
return {}
end
end
end
73 changes: 73 additions & 0 deletions spec/local_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require 'spec_helper'
require 'configcat/localdictionarydatasource'


RSpec.describe 'Local test', type: :feature do

def stub_request()
uri_template = Addressable::Template.new "https://{base_url}/{base_path}/{api_key}/{base_ext}"
json = '{"f": {"fakeKey": {"v": false} } }'
WebMock.stub_request(:get, uri_template)
.with(
body: "",
headers: {
'Accept' => '*/*',
'Content-Type' => 'application/json',
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3'
}
)
.to_return(status: 200, body: json, headers: {})
end

it "test dictionary" do
dictionary = {
"enabledFeature" => true,
"disabledFeature" => false,
"intSetting" => 5,
"doubleSetting" => 3.14,
"stringSetting" => "test"
}
client = ConfigCat::ConfigCatClient.new("test",
poll_interval_seconds: 0,
max_init_wait_time_seconds: 0,
flag_overrides: ConfigCat::LocalDictionaryDataSource.new(dictionary, ConfigCat::OverrideBehaviour::LOCAL_ONLY))
expect(client.get_value("enabledFeature", false)).to eq true
expect(client.get_value("disabledFeature", true)).to eq false
expect(client.get_value("intSetting", 0)).to eq 5
expect(client.get_value("doubleSetting", 0.0)).to eq 3.14
expect(client.get_value("stringSetting", "")).to eq "test"
client.stop()
end

it "test local over remote" do
stub_request()
dictionary = {
"fakeKey" => true,
"nonexisting" => true
}
client = ConfigCat::ConfigCatClient.new("test",
poll_interval_seconds: 0,
max_init_wait_time_seconds: 0,
flag_overrides: ConfigCat::LocalDictionaryDataSource.new(dictionary, ConfigCat::OverrideBehaviour::LOCAL_OVER_REMOTE))
expect(client.get_value("fakeKey", false)).to eq true
expect(client.get_value("nonexisting", false)).to eq true
client.force_refresh()
client.stop()
end

it "test remote over local" do
stub_request()
dictionary = {
"fakeKey" => true,
"nonexisting" => true
}
client = ConfigCat::ConfigCatClient.new("test",
poll_interval_seconds: 0,
max_init_wait_time_seconds: 0,
flag_overrides: ConfigCat::LocalDictionaryDataSource.new(dictionary, ConfigCat::OverrideBehaviour::REMOTE_OVER_LOCAL))
expect(client.get_value("fakeKey", true)).to eq false
expect(client.get_value("nonexisting", false)).to eq true
client.force_refresh()
client.stop()
end
end

0 comments on commit 7791140

Please sign in to comment.