Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/splitclient-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,11 @@
require 'splitclient-rb/engine/models/treatment'
require 'splitclient-rb/engine/models/split_http_response'
require 'splitclient-rb/engine/models/evaluation_options'
require 'splitclient-rb/engine/models/fallback_treatment.rb'
require 'splitclient-rb/engine/models/fallback_treatments_configuration.rb'
require 'splitclient-rb/engine/auth_api_client'
require 'splitclient-rb/engine/back_off'
require 'splitclient-rb/engine/fallback_treatment_calculator.rb'
require 'splitclient-rb/engine/push_manager'
require 'splitclient-rb/engine/status_manager'
require 'splitclient-rb/engine/sync_manager'
Expand Down
48 changes: 48 additions & 0 deletions lib/splitclient-rb/engine/fallback_treatment_calculator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

module SplitIoClient
module Engine
class FallbackTreatmentCalculator
attr_accessor :fallback_treatments_configuration, :label_prefix

def initialize(fallback_treatment_configuration)
@label_prefix = 'fallback - '
@fallback_treatments_configuration = fallback_treatment_configuration
end

def resolve(flag_name, label)
default_fallback_treatment = Engine::Models::FallbackTreatment.new(
Engine::Models::Treatment::CONTROL,
nil,
label
)
return default_fallback_treatment if @fallback_treatments_configuration.nil?

if !@fallback_treatments_configuration.by_flag_fallback_treatment.nil? \
&& !@fallback_treatments_configuration.by_flag_fallback_treatment.fetch(flag_name, nil).nil?
return copy_with_label(
@fallback_treatments_configuration.by_flag_fallback_treatment[flag_name],
resolve_label(label)
)
end

return copy_with_label(@fallback_treatments_configuration.global_fallback_treatment, resolve_label(label)) \
unless @fallback_treatments_configuration.global_fallback_treatment.nil?

default_fallback_treatment
end

private

def resolve_label(label)
return nil if label.nil?

@label_prefix + label
end

def copy_with_label(fallback_treatment, label)
Engine::Models::FallbackTreatment.new(fallback_treatment.treatment, fallback_treatment.config, label)
end
end
end
end
11 changes: 11 additions & 0 deletions lib/splitclient-rb/engine/models/fallback_treatment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module SplitIoClient::Engine::Models
class FallbackTreatment
attr_accessor :treatment, :config, :label

def initialize(treatment, config=nil, label=nil)
@treatment = treatment
@config = config
@label = label
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module SplitIoClient::Engine::Models
class FallbackTreatmentsConfiguration
attr_accessor :global_fallback_treatment, :by_flag_fallback_treatment

def initialize(global_fallback_treatment=nil, by_flag_fallback_treatment=nil)
@global_fallback_treatment = build_global_fallback_treatment(global_fallback_treatment)
@by_flag_fallback_treatment = build_by_flag_fallback_treatment(by_flag_fallback_treatment)
end

private

def build_global_fallback_treatment(global_fallback_treatment)
if global_fallback_treatment.is_a? String
return FallbackTreatment.new(global_fallback_treatment)
end

global_fallback_treatment
end

def build_by_flag_fallback_treatment(by_flag_fallback_treatment)
return nil unless by_flag_fallback_treatment.is_a? Hash
processed_by_flag_fallback_treatment = Hash.new

by_flag_fallback_treatment.each do |key, value|
if value.is_a? String
processed_by_flag_fallback_treatment[key] = FallbackTreatment.new(value)
next
end

processed_by_flag_fallback_treatment[key] = value
end

processed_by_flag_fallback_treatment
end
end
end
36 changes: 36 additions & 0 deletions lib/splitclient-rb/split_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ def initialize(opts = {})
@on_demand_fetch_max_retries = SplitConfig.default_on_demand_fetch_max_retries

@flag_sets_filter = SplitConfig.sanitize_flag_set_filter(opts[:flag_sets_filter], @split_validator, opts[:cache_adapter], @logger)

@fallback_treatments_configuration = SplitConfig.sanitize_fallback_config(opts[:fallback_treatments], @split_validator, @logger)
startup_log
end

Expand Down Expand Up @@ -303,6 +305,8 @@ def initialize(opts = {})
# @return [Array]
attr_accessor :flag_sets_filter

attr_accessor :fallback_treatments_configuration

def self.default_counter_refresh_rate(adapter)
return 300 if adapter == :redis # Send bulk impressions count - Refresh rate: 5 min.

Expand Down Expand Up @@ -697,5 +701,37 @@ def self.machine_ip(ip_addresses_enabled, ip, adapter)

return ''.freeze
end

def self.sanitize_fallback_config(fallback_config, validator, logger)
return fallback_config if fallback_config.nil?

processed = Engine::Models::FallbackTreatmentsConfiguration.new
if !fallback_config.is_a?(Engine::Models::FallbackTreatmentsConfiguration)
logger.warn('Config: fallbackTreatments parameter should be of `FallbackTreatmentsConfiguration` class.')
return processed
end

sanitized_global_fallback_treatment = fallback_config.global_fallback_treatment
if !fallback_config.global_fallback_treatment.nil? && !validator.validate_fallback_treatment('Config', fallback_config.global_fallback_treatment)
logger.warn('Config: global fallbacktreatment parameter is discarded.')
sanitized_global_fallback_treatment = nil
end

sanitized_flag_fallback_treatments = nil
if !fallback_config.by_flag_fallback_treatment.nil? && fallback_config.by_flag_fallback_treatment.is_a?(Hash)
sanitized_flag_fallback_treatments = Hash.new
for feature_name in fallback_config.by_flag_fallback_treatment.keys()
if !validator.valid_split_name?('Config', feature_name) || !validator.validate_fallback_treatment('Config', fallback_config.by_flag_fallback_treatment[feature_name])
logger.warn("Config: fallback treatment parameter for feature flag #{feature_name} is discarded.")
next
end

sanitized_flag_fallback_treatments[feature_name] = fallback_config.by_flag_fallback_treatment[feature_name]
end
end
processed = Engine::Models::FallbackTreatmentsConfiguration.new(sanitized_global_fallback_treatment, sanitized_flag_fallback_treatments)

processed
end
end
end
73 changes: 50 additions & 23 deletions lib/splitclient-rb/validators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module SplitIoClient
class Validators

Flagset_regex = /^[a-z0-9][_a-z0-9]{0,49}$/
Fallback_treatment_regex = /^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/
Fallback_treatment_size = 100

def initialize(config)
@config = config
Expand Down Expand Up @@ -68,7 +70,7 @@ def valid_flag_sets(method, flag_sets)
log_invalid_flag_set_type(method)
elsif flag_set.is_a?(String) && flag_set.empty?
log_invalid_flag_set_type(method)
elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method)
elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method, Flagset_regex, :log_invalid_match)
valid_flag_sets.add(flag_set.strip.downcase)
else
log_invalid_flag_set_type(method)
Expand All @@ -77,6 +79,46 @@ def valid_flag_sets(method, flag_sets)
!valid_flag_sets.empty? ? valid_flag_sets.to_a.sort : []
end

def validate_fallback_treatment(method, fallback_treatment)
if !fallback_treatment.is_a? Engine::Models::FallbackTreatment
@config.logger.warn("#{method}: Fallback treatment instance should be FallbackTreatment, input is discarded")
return false
end

if !fallback_treatment.treatment.is_a? String
@config.logger.warn("#{method}: Fallback treatment value should be str type, input is discarded")
return false
end

return false unless string_match?(fallback_treatment.treatment, method, Fallback_treatment_regex, :log_invalid_fallback_treatment)

if fallback_treatment.treatment.size > Fallback_treatment_size
@config.logger.warn("#{method}: Fallback treatment size should not exceed #{Fallback_treatment_size} characters")
return false
end

true
end

def valid_split_name?(method, split_name)
if split_name.nil?
log_nil(:split_name, method)
return false
end

unless string?(split_name)
log_invalid_type(:split_name, method)
return false
end

if empty_string?(split_name)
log_empty_string(:split_name, method)
return false
end

true
end

private

def string?(value)
Expand All @@ -91,9 +133,9 @@ def number_or_string?(value)
(value.is_a?(Numeric) && !value.to_f.nan?) || string?(value)
end

def string_match?(value, method)
if Flagset_regex.match(value) == nil
log_invalid_match(value, method)
def string_match?(value, method, regex_exp, log_if_invalid)
if regex_exp.match(value) == nil
method(log_if_invalid).call(value, method)
false
else
true
Expand Down Expand Up @@ -132,25 +174,6 @@ def log_key_too_long(key, method)
@config.logger.error("#{method}: #{key} is too long - must be #{@config.max_key_size} characters or less")
end

def valid_split_name?(method, split_name)
if split_name.nil?
log_nil(:split_name, method)
return false
end

unless string?(split_name)
log_invalid_type(:split_name, method)
return false
end

if empty_string?(split_name)
log_empty_string(:split_name, method)
return false
end

true
end

def valid_key?(method, key)
if key.nil?
log_nil(:key, method)
Expand Down Expand Up @@ -326,5 +349,9 @@ def valid_properties?(properties)

true
end

def log_invalid_fallback_treatment(key, method)
@config.logger.warn("#{method}: Invalid treatment #{key}, Fallback treatment should match regex #{Fallback_treatment_regex}")
end
end
end
30 changes: 30 additions & 0 deletions spec/engine/fallback_treatment_calculator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require 'spec_helper'

describe SplitIoClient::Engine::FallbackTreatmentCalculator do
context 'works' do
it 'process fallback treatments' do
fallback_config = SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(SplitIoClient::Engine::Models::FallbackTreatment.new("on" ,"{}"))
fallback_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(fallback_config)
expect(fallback_calculator.fallback_treatments_configuration).to be fallback_config
expect(fallback_calculator.label_prefix).to eq("fallback - ")

fallback_treatment = fallback_calculator.resolve("feature", "not ready")
expect(fallback_treatment.treatment).to eq("on")
expect(fallback_treatment.label).to eq("fallback - not ready")
expect(fallback_treatment.config).to eq("{}")

fallback_calculator.fallback_treatments_configuration = SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(SplitIoClient::Engine::Models::FallbackTreatment.new("on" ,"{}"), {:feature => SplitIoClient::Engine::Models::FallbackTreatment.new("off" , '{"prop": "val"}')})
fallback_treatment = fallback_calculator.resolve(:feature, "not ready")
expect(fallback_treatment.treatment).to eq("off")
expect(fallback_treatment.label).to eq("fallback - not ready")
expect(fallback_treatment.config).to eq('{"prop": "val"}')

fallback_treatment = fallback_calculator.resolve(:feature2, "not ready")
expect(fallback_treatment.treatment).to eq("on")
expect(fallback_treatment.label).to eq("fallback - not ready")
expect(fallback_treatment.config).to eq("{}")
end
end
end
16 changes: 16 additions & 0 deletions spec/engine/fallback_treatments_configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

require 'spec_helper'

describe SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration do
context 'works' do
it 'it converts string to fallback treatment' do
fb_config = described_class.new("global", {:feature => "local"})
expect(fb_config.global_fallback_treatment.is_a?(SplitIoClient::Engine::Models::FallbackTreatment)).to be true
expect(fb_config.global_fallback_treatment.treatment).to be "global"

expect(fb_config.by_flag_fallback_treatment[:feature].is_a?(SplitIoClient::Engine::Models::FallbackTreatment)).to be true
expect(fb_config.by_flag_fallback_treatment[:feature].treatment).to be "local"
end
end
end
28 changes: 28 additions & 0 deletions spec/splitclient/split_config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,33 @@
configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['1set', 12])
expect(configs.flag_sets_filter).to eq ['1set']
end

it 'test fallback treatment validations' do
configs = SplitIoClient::SplitConfig.new(fallback_treatments: 0)
expect(configs.fallback_treatments_configuration.is_a?(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration)).to eq true
expect(configs.fallback_treatments_configuration.global_fallback_treatment).to eq nil
expect(configs.fallback_treatments_configuration.by_flag_fallback_treatment).to eq nil

configs = SplitIoClient::SplitConfig.new(fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new)
expect(configs.fallback_treatments_configuration.is_a?(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration)).to eq true
expect(configs.fallback_treatments_configuration.global_fallback_treatment).to eq nil
expect(configs.fallback_treatments_configuration.by_flag_fallback_treatment).to eq nil

configs = SplitIoClient::SplitConfig.new(fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new('on-global', {'feature' => 'on_45-c'}))
expect(configs.fallback_treatments_configuration.global_fallback_treatment.is_a?(SplitIoClient::Engine::Models::FallbackTreatment)).to eq true
expect(configs.fallback_treatments_configuration.global_fallback_treatment.treatment).to eq 'on-global'
expect(configs.fallback_treatments_configuration.global_fallback_treatment.config).to eq nil
expect(configs.fallback_treatments_configuration.by_flag_fallback_treatment['feature'].is_a?(SplitIoClient::Engine::Models::FallbackTreatment)).to eq true
expect(configs.fallback_treatments_configuration.by_flag_fallback_treatment['feature'].treatment).to eq 'on_45-c'
expect(configs.fallback_treatments_configuration.by_flag_fallback_treatment['feature'].config).to eq nil

configs = SplitIoClient::SplitConfig.new(fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new('on-gl/obal', {'feature' => "0" * 300}))
expect(configs.fallback_treatments_configuration.global_fallback_treatment).to eq nil
expect(configs.fallback_treatments_configuration.by_flag_fallback_treatment).to eq Hash.new

configs = SplitIoClient::SplitConfig.new(fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(SplitIoClient::Engine::Models::FallbackTreatment.new('on-gl$#obal'), {"" => "treat"}))
expect(configs.fallback_treatments_configuration.global_fallback_treatment).to eq nil
expect(configs.fallback_treatments_configuration.by_flag_fallback_treatment).to eq Hash.new
end
end
end