splitclient-rb 8.5.0 → 8.6.0.pre.rc1

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.txt +4 -0
  3. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +9 -7
  4. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +122 -0
  5. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +7 -0
  6. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +19 -11
  7. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +2 -1
  8. data/lib/splitclient-rb/clients/split_client.rb +2 -0
  9. data/lib/splitclient-rb/engine/api/client.rb +8 -0
  10. data/lib/splitclient-rb/engine/api/splits.rb +99 -23
  11. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +3 -1
  12. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  13. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +75 -0
  14. data/lib/splitclient-rb/engine/models/label.rb +1 -0
  15. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  16. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  17. data/lib/splitclient-rb/engine/parser/condition.rb +9 -0
  18. data/lib/splitclient-rb/engine/parser/evaluator.rb +24 -30
  19. data/lib/splitclient-rb/engine/synchronizer.rb +33 -13
  20. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  21. data/lib/splitclient-rb/helpers/repository_helper.rb +38 -7
  22. data/lib/splitclient-rb/helpers/util.rb +12 -3
  23. data/lib/splitclient-rb/spec.rb +1 -1
  24. data/lib/splitclient-rb/split_config.rb +4 -0
  25. data/lib/splitclient-rb/split_factory.rb +5 -3
  26. data/lib/splitclient-rb/sse/event_source/event_types.rb +1 -0
  27. data/lib/splitclient-rb/sse/notification_processor.rb +3 -1
  28. data/lib/splitclient-rb/sse/workers/splits_worker.rb +58 -19
  29. data/lib/splitclient-rb/version.rb +1 -1
  30. data/lib/splitclient-rb.rb +6 -0
  31. data/splitclient-rb.gemspec +2 -2
  32. metadata +15 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49270eca968d09db0947f123ef98071ebf93abe96fd70cca06ae9775fc79c334
4
- data.tar.gz: 7205c421419bd3ab6da82eeb00361ca008d7d936eb2cc3bf83b404967f7615eb
3
+ metadata.gz: a4e482ad28374447239354e09829df29adcf7b58cb2d6a9d42120463cb34b655
4
+ data.tar.gz: 074bb6358936a908054cea7817a0e5e441421f905346b5c458f406785d1e4297
5
5
  SHA512:
6
- metadata.gz: 9e79b5ea033863981678658a28a6444caef7564d38aad5d4a3b22d60ac569d5574b6cfba0e46a71e590a89b22eabea3ab8103f23320ce2101d96b106712e6eeb
7
- data.tar.gz: 2f1d582dc82f396cc50400252b38155924677dc593c0a3524c2080b3d1bb93b781ee66563b2939ac064bd080296ad4797fcb0021030fa8827c799934e8bf11a7
6
+ metadata.gz: 82c605432eda05966aa5164c00be845e544a1529ea909524141871710f9cccdfece1ae45390f01b64a0c82cc5bc64993874dd2b8206d470aed0d6fa7cb19a9ec
7
+ data.tar.gz: '098a2e908b0f7ea745efd5c0de3ba584278dbf35b517c1c0d1c407b1d2bcc7001133be72b2f4d1c3ae5907c766a3b39e36f82b3bcc2fb5d56b32e418e57ed78b'
data/CHANGES.txt CHANGED
@@ -1,5 +1,9 @@
1
1
  CHANGES
2
2
 
3
+ 8.6.0 (Jun xx, 2025)
4
+ - Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK.
5
+ - Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules.
6
+
3
7
  8.5.0 (Jan 17, 2025)
4
8
  - Fixed high cpu usage when unique keys are cleared every 24 hours.
5
9
  - Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs.
@@ -2,10 +2,11 @@ module SplitIoClient
2
2
  module Cache
3
3
  module Fetchers
4
4
  class SplitFetcher
5
- attr_reader :splits_repository
5
+ attr_reader :splits_repository, :rule_based_segments_repository
6
6
 
7
- def initialize(splits_repository, api_key, config, telemetry_runtime_producer)
7
+ def initialize(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer)
8
8
  @splits_repository = splits_repository
9
+ @rule_based_segments_repository = rule_based_segments_repository
9
10
  @api_key = api_key
10
11
  @config = config
11
12
  @semaphore = Mutex.new
@@ -23,10 +24,11 @@ module SplitIoClient
23
24
 
24
25
  def fetch_splits(fetch_options = { cache_control_headers: false, till: nil })
25
26
  @semaphore.synchronize do
26
- data = splits_since(@splits_repository.get_change_number, fetch_options)
27
-
28
- SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:splits], data[:till], @config)
27
+ data = splits_since(@splits_repository.get_change_number, @rule_based_segments_repository.get_change_number, fetch_options)
28
+ SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:ff][:d], data[:ff][:t], @config, @splits_api.clear_storage)
29
+ SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segments_repository, data[:rbs][:d], data[:rbs][:t], @config)
29
30
  @splits_repository.set_segment_names(data[:segment_names])
31
+ @rule_based_segments_repository.set_segment_names(data[:segment_names])
30
32
  @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
31
33
 
32
34
  { segment_names: data[:segment_names], success: true }
@@ -55,8 +57,8 @@ module SplitIoClient
55
57
  end
56
58
  end
57
59
 
58
- def splits_since(since, fetch_options = { cache_control_headers: false, till: nil })
59
- splits_api.since(since, fetch_options)
60
+ def splits_since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil })
61
+ splits_api.since(since, since_rbs, fetch_options)
60
62
  end
61
63
 
62
64
  def splits_api
@@ -0,0 +1,122 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Repositories
6
+ class RuleBasedSegmentsRepository < Repository
7
+ attr_reader :adapter
8
+ DEFAULT_CONDITIONS_TEMPLATE = [{
9
+ conditionType: "ROLLOUT",
10
+ matcherGroup: {
11
+ combiner: "AND",
12
+ matchers: [
13
+ {
14
+ keySelector: nil,
15
+ matcherType: "ALL_KEYS",
16
+ negate: false,
17
+ userDefinedSegmentMatcherData: nil,
18
+ whitelistMatcherData: nil,
19
+ unaryNumericMatcherData: nil,
20
+ betweenMatcherData: nil,
21
+ dependencyMatcherData: nil,
22
+ booleanMatcherData: nil,
23
+ stringMatcherData: nil
24
+ }]
25
+ }
26
+ }]
27
+ TILL_PREFIX = '.rbsegments.till'
28
+ RB_SEGMENTS_PREFIX = '.rbsegment.'
29
+ REGISTERED_PREFIX = '.segments.registered'
30
+
31
+ def initialize(config)
32
+ super(config)
33
+ @adapter = case @config.cache_adapter.class.to_s
34
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
35
+ SplitIoClient::Cache::Adapters::CacheAdapter.new(@config)
36
+ else
37
+ @config.cache_adapter
38
+ end
39
+ unless @config.mode.equal?(:consumer)
40
+ @adapter.set_string(namespace_key(TILL_PREFIX), '-1')
41
+ @adapter.initialize_map(namespace_key(REGISTERED_PREFIX))
42
+ end
43
+ end
44
+
45
+ def update(to_add, to_delete, new_change_number)
46
+ to_add.each{ |rule_based_segment| add_rule_based_segment(rule_based_segment) }
47
+ to_delete.each{ |rule_based_segment| remove_rule_based_segment(rule_based_segment) }
48
+ set_change_number(new_change_number)
49
+ end
50
+
51
+ def get_rule_based_segment(name)
52
+ rule_based_segment = @adapter.string(namespace_key("#{RB_SEGMENTS_PREFIX}#{name}"))
53
+
54
+ JSON.parse(rule_based_segment, symbolize_names: true) if rule_based_segment
55
+ end
56
+
57
+ def rule_based_segment_names
58
+ @adapter.find_strings_by_prefix(namespace_key(RB_SEGMENTS_PREFIX))
59
+ .map { |rule_based_segment_names| rule_based_segment_names.gsub(namespace_key(RB_SEGMENTS_PREFIX), '') }
60
+ end
61
+
62
+ def set_change_number(since)
63
+ @adapter.set_string(namespace_key(TILL_PREFIX), since)
64
+ end
65
+
66
+ def get_change_number
67
+ @adapter.string(namespace_key(TILL_PREFIX))
68
+ end
69
+
70
+ def set_segment_names(names)
71
+ return if names.nil? || names.empty?
72
+
73
+ names.each do |name|
74
+ @adapter.add_to_set(namespace_key(REGISTERED_PREFIX), name)
75
+ end
76
+ end
77
+
78
+ def exists?(name)
79
+ @adapter.exists?(namespace_key("#{RB_SEGMENTS_PREFIX}#{name}"))
80
+ end
81
+
82
+ def clear
83
+ @adapter.clear(namespace_key)
84
+ end
85
+
86
+ def contains?(segment_names)
87
+ return false if rule_based_segment_names.empty?
88
+ return set(segment_names).subset?(rule_based_segment_names)
89
+ end
90
+
91
+ private
92
+
93
+ def add_rule_based_segment(rule_based_segment)
94
+ return unless rule_based_segment[:name]
95
+
96
+ if check_undefined_matcher(rule_based_segment)
97
+ @config.logger.warn("Rule based segment #{rule_based_segment[:name]} has undefined matcher, setting conditions to default template.")
98
+ rule_based_segment[:conditions] = RuleBasedSegmentsRepository::DEFAULT_CONDITIONS_TEMPLATE
99
+ end
100
+
101
+ @adapter.set_string(namespace_key("#{RB_SEGMENTS_PREFIX}#{rule_based_segment[:name]}"), rule_based_segment.to_json)
102
+ end
103
+
104
+ def check_undefined_matcher(rule_based_segment)
105
+ for condition in rule_based_segment[:conditions]
106
+ for matcher in condition[:matcherGroup][:matchers]
107
+ if !SplitIoClient::Condition.instance_methods(false).map(&:to_s).include?("matcher_#{matcher[:matcherType].downcase}")
108
+ @config.logger.error("Detected undefined matcher #{matcher[:matcherType].downcase} in feature flag #{rule_based_segment[:name]}")
109
+ return true
110
+ end
111
+ end
112
+ end
113
+ return false
114
+ end
115
+
116
+ def remove_rule_based_segment(rule_based_segment)
117
+ @adapter.delete(namespace_key("#{RB_SEGMENTS_PREFIX}#{rule_based_segment[:name]}"))
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -83,6 +83,13 @@ module SplitIoClient
83
83
  0
84
84
  end
85
85
 
86
+ def contains?(segment_names)
87
+ if segment_names.empty?
88
+ return false
89
+ end
90
+ return segment_names.to_set.subset?(used_segment_names.to_set)
91
+ end
92
+
86
93
  private
87
94
 
88
95
  def segment_data(name)
@@ -31,6 +31,9 @@ module SplitIoClient
31
31
  ],
32
32
  label: "targeting rule type unsupported by sdk"
33
33
  }]
34
+ TILL_PREFIX = '.splits.till'
35
+ SPLIT_PREFIX = '.split.'
36
+ READY_PREFIX = '.splits.ready'
34
37
 
35
38
  def initialize(config, flag_sets_repository, flag_set_filter)
36
39
  super(config)
@@ -43,10 +46,7 @@ module SplitIoClient
43
46
  end
44
47
  @flag_sets = flag_sets_repository
45
48
  @flag_set_filter = flag_set_filter
46
- unless @config.mode.equal?(:consumer)
47
- @adapter.set_string(namespace_key('.splits.till'), '-1')
48
- @adapter.initialize_map(namespace_key('.segments.registered'))
49
- end
49
+ initialize_keys
50
50
  end
51
51
 
52
52
  def update(to_add, to_delete, new_change_number)
@@ -87,16 +87,16 @@ module SplitIoClient
87
87
 
88
88
  # Return an array of Split Names excluding control keys like splits.till
89
89
  def split_names
90
- @adapter.find_strings_by_prefix(namespace_key('.split.'))
91
- .map { |split| split.gsub(namespace_key('.split.'), '') }
90
+ @adapter.find_strings_by_prefix(namespace_key(SPLIT_PREFIX))
91
+ .map { |split| split.gsub(namespace_key(SPLIT_PREFIX), '') }
92
92
  end
93
93
 
94
94
  def set_change_number(since)
95
- @adapter.set_string(namespace_key('.splits.till'), since)
95
+ @adapter.set_string(namespace_key(TILL_PREFIX), since)
96
96
  end
97
97
 
98
98
  def get_change_number
99
- @adapter.string(namespace_key('.splits.till'))
99
+ @adapter.string(namespace_key(TILL_PREFIX))
100
100
  end
101
101
 
102
102
  def set_segment_names(names)
@@ -112,21 +112,22 @@ module SplitIoClient
112
112
  end
113
113
 
114
114
  def ready?
115
- @adapter.string(namespace_key('.splits.ready')).to_i != -1
115
+ @adapter.string(namespace_key(READY_PREFIX)).to_i != -1
116
116
  end
117
117
 
118
118
  def not_ready!
119
- @adapter.set_string(namespace_key('.splits.ready'), -1)
119
+ @adapter.set_string(namespace_key(READY_PREFIX), -1)
120
120
  end
121
121
 
122
122
  def ready!
123
- @adapter.set_string(namespace_key('.splits.ready'), Time.now.utc.to_i)
123
+ @adapter.set_string(namespace_key(READY_PREFIX), Time.now.utc.to_i)
124
124
  end
125
125
 
126
126
  def clear
127
127
  @tt_cache.clear
128
128
 
129
129
  @adapter.clear(namespace_key)
130
+ initialize_keys
130
131
  end
131
132
 
132
133
  def kill(change_number, split_name, default_treatment)
@@ -167,6 +168,13 @@ module SplitIoClient
167
168
 
168
169
  private
169
170
 
171
+ def initialize_keys
172
+ unless @config.mode.equal?(:consumer)
173
+ @adapter.set_string(namespace_key(TILL_PREFIX), '-1')
174
+ @adapter.initialize_map(namespace_key('.segments.registered'))
175
+ end
176
+ end
177
+
170
178
  def add_feature_flag(split)
171
179
  return unless split[:name]
172
180
  existing_split = get_split(split[:name])
@@ -22,7 +22,8 @@ module SplitIoClient
22
22
  seed: 2_089_907_429,
23
23
  defaultTreatment: 'control_treatment',
24
24
  configurations: build_configurations(treatments),
25
- conditions: build_conditions(treatments)
25
+ conditions: build_conditions(treatments),
26
+ prerequisites: []
26
27
  }
27
28
  end
28
29
 
@@ -22,6 +22,7 @@ module SplitIoClient
22
22
  @api_key = sdk_key
23
23
  @splits_repository = repositories[:splits]
24
24
  @segments_repository = repositories[:segments]
25
+ @rule_based_segments_repository = repositories[:rule_based_segments]
25
26
  @impressions_repository = repositories[:impressions]
26
27
  @events_repository = repositories[:events]
27
28
  @status_manager = status_manager
@@ -115,6 +116,7 @@ module SplitIoClient
115
116
 
116
117
  @splits_repository.clear
117
118
  @segments_repository.clear
119
+ @rule_based_segments_repository.clear
118
120
 
119
121
  SplitIoClient.load_factory_registry
120
122
  SplitIoClient.split_factory_registry.remove(@api_key)
@@ -20,6 +20,11 @@ module SplitIoClient
20
20
 
21
21
  @config.split_logger.log_if_debug("GET #{url} proxy: #{api_client.proxy}")
22
22
  end
23
+
24
+ rescue Zlib::GzipFile::Error => e
25
+ @config.logger.warn("Incorrect formatted response exception: #{e}\n")
26
+ SplitIoClient::Engine::Models::SplitHttpResponse.new(400, '', true)
27
+
23
28
  rescue StandardError => e
24
29
  @config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
25
30
  raise e, 'Split SDK failed to connect to backend to retrieve information', e.backtrace
@@ -50,6 +55,9 @@ module SplitIoClient
50
55
  raise e, 'Split SDK failed to connect to backend to post information', e.backtrace
51
56
  end
52
57
 
58
+ def sdk_url_overriden?
59
+ @config.sdk_url_overriden?
60
+ end
53
61
  private
54
62
 
55
63
  def api_client
@@ -5,17 +5,40 @@ module SplitIoClient
5
5
  # Retrieves split definitions from the Split Backend
6
6
  class Splits < Client
7
7
 
8
+ PROXY_CHECK_INTERVAL_SECONDS = 24 * 60 * 60
9
+ SPEC_1_1 = "1.1"
10
+
8
11
  def initialize(api_key, config, telemetry_runtime_producer)
9
12
  super(config)
10
13
  @api_key = api_key
11
14
  @telemetry_runtime_producer = telemetry_runtime_producer
12
15
  @flag_sets_filter = @config.flag_sets_filter
16
+ @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
17
+ @last_proxy_check_timestamp = 0
18
+ @clear_storage = false
19
+ @old_spec_since = nil
13
20
  end
14
21
 
15
- def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
22
+ def since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
16
23
  start = Time.now
24
+
25
+ if check_last_proxy_check_timestamp
26
+ @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
27
+ @config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.")
28
+ @old_spec_since = since
29
+ since = -1
30
+ since_rbs = -1
31
+ fetch_options = { cache_control_headers: false, till: nil, sets: nil}
32
+ end
33
+
34
+ if @spec_version == Splits::SPEC_1_1
35
+ since = @old_spec_since unless @old_spec_since.nil?
36
+ params = { s: @spec_version, since: since }
37
+ @old_spec_since = nil
38
+ else
39
+ params = { s: @spec_version, since: since, rbSince: since_rbs }
40
+ end
17
41
 
18
- params = { s: SplitIoClient::Spec::FeatureFlags::SPEC_VERSION, since: since }
19
42
  params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
20
43
  params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
21
44
  @config.logger.debug("Fetching from splitChanges with #{params}: ")
@@ -24,39 +47,92 @@ module SplitIoClient
24
47
  @config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.")
25
48
  raise ApiException.new response.body, 414
26
49
  end
50
+
51
+ if response.status == 400 and sdk_url_overriden? and @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
52
+ @config.logger.warn("Detected proxy response error, changing spec version from #{@spec_version} to #{Splits::SPEC_1_1} and re-fetching.")
53
+ @spec_version = Splits::SPEC_1_1
54
+ @last_proxy_check_timestamp = Time.now
55
+ return since(since, 0, fetch_options = {cache_control_headers: fetch_options[:cache_control_headers], till: fetch_options[:till],
56
+ sets: fetch_options[:sets]})
57
+ end
27
58
  if response.success?
28
- result = splits_with_segment_names(response.body)
29
- unless result[:splits].empty?
30
- @config.split_logger.log_if_debug("#{result[:splits].length} feature flags retrieved. since=#{since}")
31
- end
32
- @config.split_logger.log_if_transport("Feature flag changes response: #{result.to_s}")
59
+ result = JSON.parse(response.body, symbolize_names: true)
33
60
 
34
- bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
35
- @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket)
36
- @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i)
61
+ return process_result(result, since, since_rbs, start)
62
+ end
63
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
37
64
 
38
- result
39
- else
40
- @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
65
+ @config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \
66
+ 'Check your API key and base URI')
41
67
 
42
- @config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \
43
- 'Check your API key and base URI')
68
+ raise 'Split SDK failed to connect to backend to fetch feature flags definitions'
69
+ end
44
70
 
45
- raise 'Split SDK failed to connect to backend to fetch feature flags definitions'
46
- end
71
+ def clear_storage
72
+ @clear_storage
47
73
  end
48
74
 
49
75
  private
50
76
 
51
- def splits_with_segment_names(splits_json)
52
- parsed_splits = JSON.parse(splits_json, symbolize_names: true)
77
+ def process_result(result, since, since_rbs, start)
78
+ if @spec_version == Splits::SPEC_1_1
79
+ result = convert_to_new_spec(result)
80
+ end
81
+
82
+ result[:rbs][:d] = check_rbs_data(result[:rbs][:d])
83
+ result = objects_with_segment_names(result)
84
+
85
+ if @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
86
+ @clear_storage = @last_proxy_check_timestamp != 0
87
+ @last_proxy_check_timestamp = 0
88
+ end
89
+
90
+ unless result[:ff][:d].empty?
91
+ @config.split_logger.log_if_debug("#{result[:ff][:d].length} feature flags retrieved. since=#{since}")
92
+ end
93
+ @config.split_logger.log_if_transport("Feature flag changes response: #{result[:ff].to_s}")
94
+
95
+ unless result[:rbs][:d].empty?
96
+ @config.split_logger.log_if_debug("#{result[:rbs][:d].length} rule based segments retrieved. since=#{since_rbs}")
97
+ end
98
+ @config.split_logger.log_if_transport("rule based segments changes response: #{result[:rbs].to_s}")
99
+
100
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
101
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket)
102
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i)
103
+
104
+ result
105
+ end
106
+
107
+ def check_rbs_data(rbs_data)
108
+ rbs_data.each do |rb_segment|
109
+ rb_segment[:excluded] = {:keys => [], :segments => []} if rb_segment[:excluded].nil?
110
+ rb_segment[:excluded][:keys] = [] if rb_segment[:excluded][:keys].nil?
111
+ rb_segment[:excluded][:segments] = [] if rb_segment[:excluded][:segments].nil?
112
+ end
113
+ rbs_data
114
+ end
53
115
 
54
- parsed_splits[:segment_names] =
55
- parsed_splits[:splits].each_with_object(Set.new) do |split, splits|
56
- splits << Helpers::Util.segment_names_by_feature_flag(split)
116
+ def objects_with_segment_names(parsed_objects)
117
+ parsed_objects[:segment_names] = Set.new
118
+ parsed_objects[:segment_names] =
119
+ parsed_objects[:ff][:d].each_with_object(Set.new) do |split, splits|
120
+ splits << Helpers::Util.segment_names_by_object(split, "IN_SEGMENT")
57
121
  end.flatten
58
122
 
59
- parsed_splits
123
+ parsed_objects[:rbs][:d].each do |rule_based_segment|
124
+ parsed_objects[:segment_names].merge Helpers::Util.segment_names_in_rb_segment(rule_based_segment, "IN_SEGMENT")
125
+ end
126
+
127
+ parsed_objects
128
+ end
129
+
130
+ def check_last_proxy_check_timestamp
131
+ @spec_version == Splits::SPEC_1_1 and ((Time.now - @last_proxy_check_timestamp) >= Splits::PROXY_CHECK_INTERVAL_SECONDS)
132
+ end
133
+
134
+ def convert_to_new_spec(body)
135
+ {:ff => {:d => body[:splits], :s => body[:since], :t => body[:till]}, :rbs => {:d => [], :s => -1, :t => -1}}
60
136
  end
61
137
  end
62
138
  end
@@ -56,7 +56,9 @@ module SplitIoClient
56
56
 
57
57
  @matchers.all? do |matcher|
58
58
  if match_with_key?(matcher)
59
- matcher.match?(value: args[:matching_key])
59
+ key = args[:value]
60
+ key = args[:matching_key] unless args[:matching_key].nil?
61
+ matcher.match?(value: key)
60
62
  else
61
63
  matcher.match?(args)
62
64
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ class PrerequisitesMatcher
5
+ def initialize(prerequisites, logger)
6
+ @prerequisites = prerequisites
7
+ @logger = logger
8
+ end
9
+
10
+ def match?(args)
11
+ keys = { matching_key: args[:matching_key], bucketing_key: args[:bucketing_key] }
12
+
13
+ match = true
14
+ @prerequisites.each do |prerequisite|
15
+ evaluate = args[:evaluator].evaluate_feature_flag(keys, prerequisite[:n], args[:attributes])
16
+ next if prerequisite[:ts].include?(evaluate[:treatment])
17
+
18
+ @logger.log_if_debug("[PrerequisitesMatcher] feature flag #{prerequisite[:n]} evaluated to #{evaluate[:treatment]} \
19
+ that does not exist in prerequisited treatments.")
20
+ match = false
21
+ break
22
+ end
23
+
24
+ match
25
+ end
26
+
27
+ def string_type?
28
+ false
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ #
5
+ # class to implement the user defined matcher
6
+ #
7
+ class RuleBasedSegmentMatcher < Matcher
8
+ MATCHER_TYPE = 'IN_RULE_BASED_SEGMENT'
9
+
10
+ def initialize(segments_repository, rule_based_segments_repository, segment_name, config)
11
+ super(config.logger)
12
+ @rule_based_segments_repository = rule_based_segments_repository
13
+ @segments_repository = segments_repository
14
+ @segment_name = segment_name
15
+ @config = config
16
+ end
17
+
18
+ #
19
+ # evaluates if the key matches the matcher
20
+ #
21
+ # @param key [string] key value to be matched
22
+ #
23
+ # @return [boolean] evaluation of the key against the segment
24
+ def match?(args)
25
+ rule_based_segment = @rule_based_segments_repository.get_rule_based_segment(@segment_name)
26
+ return false if rule_based_segment.nil?
27
+
28
+ key = update_key(args)
29
+ return false if rule_based_segment[:excluded][:keys].include?(key)
30
+
31
+ return false unless check_excluded_segments(rule_based_segment, key, args)
32
+
33
+ matches = false
34
+ rule_based_segment[:conditions].each do |c|
35
+ condition = SplitIoClient::Condition.new(c, @config)
36
+ next if condition.empty?
37
+
38
+ matches = Helpers::EvaluatorHelper.matcher_type(condition, @segments_repository, @rule_based_segments_repository).match?(args)
39
+ end
40
+ @logger.debug("[InRuleSegmentMatcher] #{@segment_name} is in rule based segment -> #{matches}")
41
+ matches
42
+ end
43
+
44
+ private
45
+
46
+ def check_excluded_segments(rule_based_segment, key, args)
47
+ rule_based_segment[:excluded][:segments].each do |segment|
48
+ if segment[:type] == SplitIoClient::Engine::Models::SegmentType::STANDARD &&
49
+ @segments_repository.in_segment?(segment[:name], key)
50
+ return false
51
+ end
52
+ return false if segment[:type] == SplitIoClient::Engine::Models::SegmentType::RULE_BASED_SEGMENT && match_rbs(
53
+ @rule_based_segments_repository.get_rule_based_segment(segment[:name]), args
54
+ )
55
+ end
56
+ true
57
+ end
58
+
59
+ def update_key(args)
60
+ if args[:value].nil? || args[:value].empty?
61
+ args[:matching_key]
62
+ else
63
+ args[:value]
64
+ end
65
+ end
66
+
67
+ def match_rbs(rule_based_segment, args)
68
+ rbs_matcher = RuleBasedSegmentMatcher.new(@segments_repository, @rule_based_segments_repository,
69
+ rule_based_segment[:name], @config)
70
+ rbs_matcher.match?(matching_key: args[:matching_key],
71
+ bucketing_key: args[:value],
72
+ attributes: args[:attributes])
73
+ end
74
+ end
75
+ end
@@ -6,4 +6,5 @@ class SplitIoClient::Engine::Models::Label
6
6
  NOT_IN_SPLIT = 'not in split'.freeze
7
7
  NOT_READY = 'not ready'.freeze
8
8
  NOT_FOUND = 'definition not found'.freeze
9
+ PREREQUISITES_NOT_MET = 'prerequisites not met'.freeze
9
10
  end
@@ -0,0 +1,4 @@
1
+ class SplitIoClient::Engine::Models::SegmentType
2
+ STANDARD = 'standard'
3
+ RULE_BASED_SEGMENT = 'rule-based'
4
+ end
@@ -0,0 +1,19 @@
1
+ module SplitIoClient
2
+ module Engine
3
+ module Models
4
+ class SplitHttpResponse
5
+ attr_accessor :status, :body
6
+
7
+ def initialize(status, body, success)
8
+ @status = status
9
+ @body = body
10
+ @success = success
11
+ end
12
+
13
+ def success?
14
+ @success
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -230,6 +230,13 @@ module SplitIoClient
230
230
  )
231
231
  end
232
232
 
233
+ def matcher_in_rule_based_segment(params)
234
+ matcher = params[:matcher]
235
+ segment_name = matcher[:userDefinedSegmentMatcherData] && matcher[:userDefinedSegmentMatcherData][:segmentName]
236
+
237
+ RuleBasedSegmentMatcher.new(params[:segments_repository], params[:rule_based_segments_repository], segment_name, @config)
238
+ end
239
+
233
240
  #
234
241
  # @return [object] the negate value for this condition
235
242
  def negate
@@ -246,6 +253,8 @@ module SplitIoClient
246
253
  # @return [void]
247
254
  def set_partitions
248
255
  partitions_list = []
256
+ return partitions_list unless @data.key?(:partitions) or @data.key?('partitions')
257
+
249
258
  @data[:partitions].each do |p|
250
259
  partition = SplitIoClient::Partition.new(p)
251
260
  partitions_list << partition