splitclient-rb 8.3.1 → 8.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.txt +8 -0
  3. data/LICENSE +1 -1
  4. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +10 -6
  5. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +4 -0
  6. data/lib/splitclient-rb/cache/repositories/events_repository.rb +4 -0
  7. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +42 -0
  8. data/lib/splitclient-rb/clients/split_client.rb +28 -21
  9. data/lib/splitclient-rb/engine/api/client.rb +3 -1
  10. data/lib/splitclient-rb/engine/api/splits.rb +4 -3
  11. data/lib/splitclient-rb/engine/auth_api_client.rb +1 -1
  12. data/lib/splitclient-rb/engine/common/impressions_manager.rb +24 -27
  13. data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
  14. data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
  15. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
  16. data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
  17. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
  18. data/lib/splitclient-rb/engine/matchers/matcher.rb +18 -0
  19. data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
  20. data/lib/splitclient-rb/engine/parser/condition.rb +41 -0
  21. data/lib/splitclient-rb/engine/synchronizer.rb +4 -4
  22. data/lib/splitclient-rb/helpers/repository_helper.rb +7 -0
  23. data/lib/splitclient-rb/managers/split_manager.rb +2 -1
  24. data/lib/splitclient-rb/spec.rb +9 -0
  25. data/lib/splitclient-rb/split_factory.rb +2 -13
  26. data/lib/splitclient-rb/split_logger.rb +8 -0
  27. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +2 -2
  28. data/lib/splitclient-rb/version.rb +1 -1
  29. data/lib/splitclient-rb.rb +8 -0
  30. metadata +10 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28b17d201426172454ca4aafc72ffa49363c20386fb5e6515e1e734d6e915f18
4
- data.tar.gz: 473d7bf15d91b38d18a05006c0ebee6c013319add4153ac40245f674398ba5d0
3
+ metadata.gz: 49270eca968d09db0947f123ef98071ebf93abe96fd70cca06ae9775fc79c334
4
+ data.tar.gz: 7205c421419bd3ab6da82eeb00361ca008d7d936eb2cc3bf83b404967f7615eb
5
5
  SHA512:
6
- metadata.gz: e9c0aa0c613fb3abb35bda295ad1a9726e9ecc994b64d909b8ea12fa9b63f950785c2c46afb1592a8405871370e1dadc3ffed9e874781752a572209238b032de
7
- data.tar.gz: 412dc67a3514a67def649c74db278cb3c006b8bcfbebc9cc1b231f9c9d7e565754e2b1dcc2729e1eabce33818fb2d1311d9bbcff96f715bab55b0a5413ee2358
6
+ metadata.gz: 9e79b5ea033863981678658a28a6444caef7564d38aad5d4a3b22d60ac569d5574b6cfba0e46a71e590a89b22eabea3ab8103f23320ce2101d96b106712e6eeb
7
+ data.tar.gz: 2f1d582dc82f396cc50400252b38155924677dc593c0a3524c2080b3d1bb93b781ee66563b2939ac064bd080296ad4797fcb0021030fa8827c799934e8bf11a7
data/CHANGES.txt CHANGED
@@ -1,5 +1,13 @@
1
1
  CHANGES
2
2
 
3
+ 8.5.0 (Jan 17, 2025)
4
+ - Fixed high cpu usage when unique keys are cleared every 24 hours.
5
+ - 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.
6
+
7
+ 8.4.0 (May 3, 2024)
8
+ - Fixed issue preventing Impressopns and Events posting if client.destroy is called before the post threads started
9
+ - Added support for targeting rules based on semantic versions (https://semver.org/).
10
+
3
11
  8.3.1 (Mar 22, 2024)
4
12
  - Fixed ruby process hanging due to failed thread.join command, when calling destroy and a http request still active.
5
13
  - Fixed streaming notification parser. Issue ref: https://github.com/splitio/ruby-client/issues/511
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright © 2024 Split Software, Inc.
1
+ Copyright © 2025 Split Software, Inc.
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
@@ -8,8 +8,8 @@ module SplitIoClient
8
8
  class BloomFilter
9
9
  def initialize(capacity, false_positive_probability = 0.001)
10
10
  @capacity = capacity.round
11
- m = best_m(capacity, false_positive_probability)
12
- @ba = BitArray.new(m.round)
11
+ @m = best_m(capacity, false_positive_probability)
12
+ reset_filter
13
13
  @k = best_k(capacity)
14
14
  end
15
15
 
@@ -17,22 +17,26 @@ module SplitIoClient
17
17
  return false if contains?(string)
18
18
 
19
19
  positions = hashes(string)
20
-
21
20
  positions.each { |position| @ba[position] = 1 }
22
21
 
23
22
  true
24
23
  end
25
-
24
+
26
25
  def contains?(string)
27
26
  !hashes(string).any? { |ea| @ba[ea] == 0 }
28
27
  end
29
28
 
30
29
  def clear
31
- @ba.size.times { |i| @ba[i] = 0 }
30
+ @ba = nil
31
+ reset_filter
32
32
  end
33
-
33
+
34
34
  private
35
35
 
36
+ def reset_filter
37
+ @ba = BitArray.new(@m.round)
38
+ end
39
+
36
40
  # m is the required number of bits in the array
37
41
  def best_m(capacity, false_positive_probability)
38
42
  -(capacity * Math.log(false_positive_probability)) / (Math.log(2) ** 2)
@@ -29,6 +29,10 @@ module SplitIoClient
29
29
  @adapter.clear
30
30
  end
31
31
 
32
+ def empty?
33
+ @adapter.empty?
34
+ end
35
+
32
36
  def batch
33
37
  return [] if @config.events_queue_size.zero?
34
38
 
@@ -25,6 +25,10 @@ module SplitIoClient
25
25
  @config.log_found_exception(__method__.to_s, e)
26
26
  end
27
27
 
28
+ def empty?
29
+ @repository.empty?
30
+ end
31
+
28
32
  protected
29
33
 
30
34
  def metadata
@@ -5,6 +5,32 @@ module SplitIoClient
5
5
  module Repositories
6
6
  class SplitsRepository < Repository
7
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
+ partitions: [
27
+ {
28
+ treatment: "control",
29
+ size: 100
30
+ }
31
+ ],
32
+ label: "targeting rule type unsupported by sdk"
33
+ }]
8
34
 
9
35
  def initialize(config, flag_sets_repository, flag_set_filter)
10
36
  super(config)
@@ -155,6 +181,10 @@ module SplitIoClient
155
181
  remove_from_flag_sets(existing_split)
156
182
  end
157
183
 
184
+ if check_undefined_matcher(split)
185
+ @config.logger.warn("Feature Flag #{split[:name]} has undefined matcher, setting conditions to default template.")
186
+ split[:conditions] = SplitsRepository::DEFAULT_CONDITIONS_TEMPLATE
187
+ end
158
188
  if !split[:sets].nil?
159
189
  for flag_set in split[:sets]
160
190
  if !@flag_sets.flag_set_exist?(flag_set)
@@ -170,6 +200,18 @@ module SplitIoClient
170
200
  @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json)
171
201
  end
172
202
 
203
+ def check_undefined_matcher(split)
204
+ for condition in split[:conditions]
205
+ for matcher in condition[:matcherGroup][:matchers]
206
+ if !SplitIoClient::Condition.instance_methods(false).map(&:to_s).include?("matcher_#{matcher[:matcherType].downcase}")
207
+ @config.logger.error("Detected undefined matcher #{matcher[:matcherType].downcase} in feature flag #{split[:name]}")
208
+ return true
209
+ end
210
+ end
211
+ end
212
+ return false
213
+ end
214
+
173
215
  def remove_feature_flag(split)
174
216
  decrease_tt_name_count(split[:trafficTypeName])
175
217
  remove_from_flag_sets(split)
@@ -96,7 +96,16 @@ module SplitIoClient
96
96
 
97
97
  def destroy
98
98
  @config.logger.info('Split client shutdown started...') if @config.debug_enabled
99
-
99
+ if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && @config.impressions_mode != :none &&
100
+ (!@impressions_repository.empty? || !@events_repository.empty?)
101
+ @config.logger.debug("Impressions and/or Events cache is not empty")
102
+ # Adding small delay to ensure sender threads are fully running
103
+ sleep(0.1)
104
+ if !@config.threads.key?(:impressions_sender) || !@config.threads.key?(:events_sender)
105
+ @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.")
106
+ @config.threads[:start_sdk].join(5) if @config.threads.key?(:start_sdk)
107
+ end
108
+ end
100
109
  @config.threads.select { |name, thread| name.to_s.end_with? 'sender' }.values.each do |thread|
101
110
  thread.raise(SplitIoClient::SDKShutdownException)
102
111
  thread.join
@@ -258,7 +267,7 @@ module SplitIoClient
258
267
  to_return = Hash.new
259
268
  sanitized_feature_flag_names.each {|name|
260
269
  to_return[name.to_sym] = control_treatment_with_config
261
- impressions << @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ label: Engine::Models::Label::NOT_READY }), { attributes: attributes, time: nil })
270
+ impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil }), :disabled => false }
262
271
  }
263
272
  @impressions_manager.track(impressions)
264
273
  return to_return
@@ -269,7 +278,6 @@ module SplitIoClient
269
278
  valid_feature_flag_names << feature_flag_name unless feature_flag_name.nil?
270
279
  }
271
280
  start = Time.now
272
- impressions_total = []
273
281
 
274
282
  feature_flags = @splits_repository.splits(valid_feature_flag_names)
275
283
  treatments = Hash.new
@@ -282,15 +290,14 @@ module SplitIoClient
282
290
  next
283
291
  end
284
292
  treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method)
285
- impressions_total.concat(impressions) unless impressions.nil?
286
293
  treatments[key] =
287
294
  {
288
295
  treatment: treatments_labels_change_numbers[:treatment],
289
296
  config: treatments_labels_change_numbers[:config]
290
297
  }
298
+ @impressions_manager.track(impressions) unless impressions.empty?
291
299
  end
292
300
  record_latency(calling_method, start)
293
- @impressions_manager.track(impressions_total) unless impressions_total.empty?
294
301
 
295
302
  treatments.merge(invalid_treatments)
296
303
  end
@@ -323,50 +330,50 @@ module SplitIoClient
323
330
  end
324
331
 
325
332
  feature_flag = @splits_repository.get_split(feature_flag_name)
326
- treatments, impressions = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple)
333
+ treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple)
327
334
 
328
- @impressions_manager.track(impressions) unless impressions.nil?
335
+ @impressions_manager.track(impressions_decorator) unless impressions_decorator.nil?
329
336
  treatments
330
337
  end
331
338
 
332
339
  def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false)
333
- impressions = []
340
+ impressions_decorator = []
334
341
  begin
335
342
  start = Time.now
336
343
  if feature_flag.nil? && ready?
337
344
  @config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \
338
345
  'does not exist in this environment, please double check what feature flags exist in the Split user interface')
339
- return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND }), multiple), nil
346
+ return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::NOT_FOUND }), multiple), nil
340
347
  end
341
- treatment_data =
348
+
342
349
  if !feature_flag.nil? && ready?
343
- @evaluator.evaluate_feature_flag(
350
+ treatment_data = @evaluator.evaluate_feature_flag(
344
351
  { bucketing_key: bucketing_key, matching_key: matching_key }, feature_flag, attributes
345
352
  )
353
+ impressions_disabled = feature_flag[:impressionsDisabled]
346
354
  else
347
355
  @config.logger.error("#{calling_method}: the SDK is not ready, results may be incorrect for feature flag #{feature_flag_name}. Make sure to wait for SDK readiness before using this method.")
348
- control_treatment.merge({ label: Engine::Models::Label::NOT_READY })
356
+ treatment_data = control_treatment.merge({ :label => Engine::Models::Label::NOT_READY })
357
+ impressions_disabled = false
349
358
  end
350
359
 
351
360
  record_latency(calling_method, start)
352
- impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, { attributes: attributes, time: nil })
353
- impressions << impression unless impression.nil?
361
+ impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }), :disabled => impressions_disabled }
362
+ impressions_decorator << impression_decorator unless impression_decorator.nil?
354
363
  rescue StandardError => e
355
364
  @config.log_found_exception(__method__.to_s, e)
356
-
357
365
  record_exception(calling_method)
366
+ impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }), :disabled => false }
367
+ impressions_decorator << impression_decorator unless impression_decorator.nil?
358
368
 
359
- impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, { attributes: attributes, time: nil })
360
- impressions << impression unless impression.nil?
361
-
362
- return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }), multiple), impressions
369
+ return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::EXCEPTION }), multiple), impressions_decorator
363
370
  end
364
371
 
365
- return parsed_treatment(treatment_data, multiple), impressions
372
+ return parsed_treatment(treatment_data, multiple), impressions_decorator
366
373
  end
367
374
 
368
375
  def control_treatment
369
- { treatment: Engine::Models::Treatment::CONTROL }
376
+ { :treatment => Engine::Models::Treatment::CONTROL }
370
377
  end
371
378
 
372
379
  def control_treatment_with_config
@@ -10,6 +10,7 @@ module SplitIoClient
10
10
  end
11
11
 
12
12
  def get_api(url, api_key, params = {}, cache_control_headers = false)
13
+ api_client.options.params_encoder.sort_params = false
13
14
  api_client.get(url, params) do |req|
14
15
  req.headers = common_headers(api_key).merge('Accept-Encoding' => 'gzip')
15
16
  req.headers = req.headers.merge('Cache-Control' => 'no-cache') if cache_control_headers
@@ -29,7 +30,7 @@ module SplitIoClient
29
30
  req.headers = common_headers(api_key)
30
31
  .merge('Content-Type' => 'application/json')
31
32
  .merge(headers)
32
-
33
+
33
34
  machine_ip = @config.machine_ip
34
35
  machine_name = @config.machine_name
35
36
 
@@ -55,6 +56,7 @@ module SplitIoClient
55
56
  @api_client ||= Faraday.new do |builder|
56
57
  builder.use SplitIoClient::FaradayMiddleware::Gzip
57
58
  builder.adapter :net_http_persistent
59
+ builder.options.params_encoder = Faraday::FlatParamsEncoder
58
60
  end
59
61
  end
60
62
 
@@ -4,6 +4,7 @@ module SplitIoClient
4
4
  module Api
5
5
  # Retrieves split definitions from the Split Backend
6
6
  class Splits < Client
7
+
7
8
  def initialize(api_key, config, telemetry_runtime_producer)
8
9
  super(config)
9
10
  @api_key = api_key
@@ -11,12 +12,12 @@ module SplitIoClient
11
12
  @flag_sets_filter = @config.flag_sets_filter
12
13
  end
13
14
 
14
- def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil })
15
+ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
15
16
  start = Time.now
16
17
 
17
- params = { since: since }
18
- params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
18
+ params = { s: SplitIoClient::Spec::FeatureFlags::SPEC_VERSION, since: since }
19
19
  params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
20
+ params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
20
21
  @config.logger.debug("Fetching from splitChanges with #{params}: ")
21
22
  response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
22
23
  if response.status == 414
@@ -14,7 +14,7 @@ module SplitIoClient
14
14
 
15
15
  def authenticate(api_key)
16
16
  start = Time.now
17
- response = @api_client.get_api(@config.auth_service_url, api_key)
17
+ response = @api_client.get_api("#{@config.auth_service_url}?s=#{SplitIoClient::Spec::FeatureFlags::SPEC_VERSION}", api_key)
18
18
 
19
19
  return process_success(response, start) if response.success?
20
20
 
@@ -18,19 +18,16 @@ module SplitIoClient
18
18
  @unique_keys_tracker = unique_keys_tracker
19
19
  end
20
20
 
21
- def build_impression(matching_key, bucketing_key, split_name, treatment, params = {})
22
- impression_data = impression_data(matching_key, bucketing_key, split_name, treatment, params[:time])
23
-
21
+ def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {})
22
+ impression_data = impression_data(matching_key, bucketing_key, split_name, treatment_data, params[:time])
24
23
  begin
25
- case @config.impressions_mode
26
- when :debug # In DEBUG mode we should calculate the pt only.
27
- impression_data[:pt] = @impression_observer.test_and_set(impression_data)
28
- when :none # In NONE mode we should track the total amount of evaluations and the unique keys.
24
+ if @config.impressions_mode == :none || impressions_disabled
29
25
  @impression_counter.inc(split_name, impression_data[:m])
30
26
  @unique_keys_tracker.track(split_name, matching_key)
27
+ elsif @config.impressions_mode == :debug # In DEBUG mode we should calculate the pt only.
28
+ impression_data[:pt] = @impression_observer.test_and_set(impression_data)
31
29
  else # In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions.
32
30
  impression_data[:pt] = @impression_observer.test_and_set(impression_data)
33
-
34
31
  @impression_counter.inc(split_name, impression_data[:m]) unless impression_data[:pt].nil?
35
32
  end
36
33
  rescue StandardError => e
@@ -40,24 +37,25 @@ module SplitIoClient
40
37
  impression(impression_data, params[:attributes])
41
38
  end
42
39
 
43
- def track(impressions)
44
- return if impressions.empty?
45
-
46
- stats = { dropped: 0, queued: 0, dedupe: 0 }
47
- begin
48
- case @config.impressions_mode
49
- when :none
50
- return
51
- when :debug
52
- track_debug_mode(impressions, stats)
53
- when :optimized
54
- track_optimized_mode(impressions, stats)
40
+ def track(impressions_decorator)
41
+ return if impressions_decorator.empty?
42
+
43
+ impressions_decorator.each do |impression_decorator|
44
+ impression_router.add_bulk([impression_decorator[:impression]])
45
+ stats = { dropped: 0, queued: 0, dedupe: 0 }
46
+ begin
47
+ next if @config.impressions_mode == :none || impression_decorator[:disabled]
48
+
49
+ if @config.impressions_mode == :debug
50
+ track_debug_mode([impression_decorator[:impression]], stats)
51
+ else
52
+ track_optimized_mode([impression_decorator[:impression]], stats)
53
+ end
54
+ rescue StandardError => e
55
+ @config.log_found_exception(__method__.to_s, e)
56
+ ensure
57
+ record_stats(stats)
55
58
  end
56
- rescue StandardError => e
57
- @config.log_found_exception(__method__.to_s, e)
58
- ensure
59
- record_stats(stats)
60
- impression_router.add_bulk(impressions)
61
59
  end
62
60
  end
63
61
 
@@ -126,11 +124,10 @@ module SplitIoClient
126
124
 
127
125
  def track_optimized_mode(impressions, stats)
128
126
  optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
129
-
127
+ stats[:dedupe] = impressions.length - optimized_impressions.length
130
128
  return if optimized_impressions.empty?
131
129
 
132
130
  stats[:dropped] = @impressions_repository.add_bulk(optimized_impressions)
133
- stats[:dedupe] = impressions.length - optimized_impressions.length
134
131
  stats[:queued] = optimized_impressions.length - stats[:dropped]
135
132
  end
136
133
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ class BetweenSemverMatcher < Matcher
5
+ MATCHER_TYPE = 'BETWEEN_SEMVER'
6
+
7
+ attr_reader :attribute
8
+
9
+ def initialize(attribute, start_value, end_value, logger, validator)
10
+ super(logger)
11
+ @validator = validator
12
+ @attribute = attribute
13
+ @semver_start = SplitIoClient::Semver.build(start_value, logger)
14
+ @semver_end = SplitIoClient::Semver.build(end_value, logger)
15
+ @logger = logger
16
+ end
17
+
18
+ def match?(args)
19
+ return false unless verify_semver_arg?(args, 'BetweenSemverMatcher')
20
+
21
+ value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
22
+ if value_to_match.nil? || @semver_start.nil? || @semver_end.nil?
23
+ @logger.error('betweenStringMatcherData is required for BETWEEN_SEMVER matcher type')
24
+ return false
25
+
26
+ end
27
+ matches = ([0, -1].include?(@semver_start.compare(value_to_match)) &&
28
+ [0, 1].include?(@semver_end.compare(value_to_match)))
29
+ @logger.debug("[BetweenMatcher] #{value_to_match} matches -> #{matches}")
30
+ matches
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ class EqualToSemverMatcher < Matcher
5
+ MATCHER_TYPE = 'EQUAL_TO_SEMVER'
6
+
7
+ attr_reader :attribute
8
+
9
+ def initialize(attribute, string_value, logger, validator)
10
+ super(logger)
11
+ @validator = validator
12
+ @attribute = attribute
13
+ @semver = SplitIoClient::Semver.build(string_value, logger)
14
+ @logger = logger
15
+ end
16
+
17
+ def match?(args)
18
+ return false unless verify_semver_arg?(args, 'EqualsToSemverMatcher')
19
+
20
+ value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
21
+ return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE)
22
+
23
+ matches = (@semver.version == value_to_match.version)
24
+ @logger.debug("[EqualsToSemverMatcher] #{value_to_match} matches -> #{matches}")
25
+ matches
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ class GreaterThanOrEqualToSemverMatcher < Matcher
5
+ MATCHER_TYPE = 'GREATER_THAN_OR_EQUAL_TO_SEMVER'
6
+
7
+ attr_reader :attribute
8
+
9
+ def initialize(attribute, string_value, logger, validator)
10
+ super(logger)
11
+ @validator = validator
12
+ @attribute = attribute
13
+ @semver = SplitIoClient::Semver.build(string_value, logger)
14
+ @logger = logger
15
+ end
16
+
17
+ def match?(args)
18
+ return false unless verify_semver_arg?(args, 'GreaterThanOrEqualsToSemverMatcher')
19
+
20
+ value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
21
+ return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE)
22
+
23
+ matches = [0, 1].include?(value_to_match.compare(@semver))
24
+ @logger.debug("[GreaterThanOrEqualsToSemverMatcher] #{value_to_match} matches -> #{matches}")
25
+ matches
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ class InListSemverMatcher < Matcher
5
+ MATCHER_TYPE = 'IN_LIST_SEMVER'
6
+
7
+ attr_reader :attribute
8
+
9
+ def initialize(attribute, list_value, logger, validator)
10
+ super(logger)
11
+ @validator = validator
12
+ @attribute = attribute
13
+ @semver_list = []
14
+
15
+ list_value.map do |item|
16
+ version = SplitIoClient::Semver.build(item, logger)
17
+ @semver_list << version unless version.nil?
18
+ end
19
+ @logger = logger
20
+ end
21
+
22
+ def match?(args)
23
+ return false if @semver_list.empty? || !verify_semver_arg?(args, 'InListSemverMatcher')
24
+
25
+ value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
26
+ if value_to_match.nil?
27
+ @logger.error('whitelistMatcherData is required for IN_LIST_SEMVER matcher type')
28
+ return false
29
+
30
+ end
31
+ matches = (@semver_list.map { |item| item.version == value_to_match.version }).any? { |item| item == true }
32
+ @logger.debug("[InListSemverMatcher] #{value_to_match} matches -> #{matches}")
33
+ matches
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ class LessThanOrEqualToSemverMatcher < Matcher
5
+ MATCHER_TYPE = 'LESS_THAN_OR_EQUAL_TO_SEMVER'
6
+
7
+ attr_reader :attribute
8
+
9
+ def initialize(attribute, string_value, logger, validator)
10
+ super(logger)
11
+ @validator = validator
12
+ @attribute = attribute
13
+ @semver = SplitIoClient::Semver.build(string_value, logger)
14
+ @logger = logger
15
+ end
16
+
17
+ def match?(args)
18
+ return false unless verify_semver_arg?(args, 'LessThanOrEqualsToSemverMatcher')
19
+
20
+ value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
21
+ return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE)
22
+
23
+ matches = [0, -1].include?(value_to_match.compare(@semver))
24
+ @logger.debug("[LessThanOrEqualsToSemverMatcher] #{value_to_match} matches -> #{matches}")
25
+ matches
26
+ end
27
+ end
28
+ end
@@ -30,5 +30,23 @@ module SplitIoClient
30
30
  def string_type?
31
31
  false
32
32
  end
33
+
34
+ private
35
+
36
+ def verify_semver_arg?(args, matcher_name)
37
+ @logger.debug("[#{matcher_name}] evaluating value and attributes.")
38
+ return false unless @validator.valid_matcher_arguments(args)
39
+
40
+ true
41
+ end
42
+
43
+ def check_semver_value_to_match(value_to_match, matcher_spec_name)
44
+ if value_to_match.nil? || @semver.nil?
45
+ @logger.error("stringMatcherData is required for #{matcher_spec_name} matcher type")
46
+ return false
47
+
48
+ end
49
+ true
50
+ end
33
51
  end
34
52
  end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ class Semver
5
+ METADATA_DELIMITER = '+'
6
+ PRE_RELEASE_DELIMITER = '-'
7
+ VALUE_DELIMITER = '.'
8
+
9
+ attr_reader :major, :minor, :patch, :pre_release, :is_stable, :version
10
+
11
+ def initialize(version)
12
+ @major = 0
13
+ @minor = 0
14
+ @patch = 0
15
+ @pre_release = []
16
+ @is_stable = false
17
+ @version = ''
18
+ @metadata = ''
19
+ parse(version)
20
+ end
21
+
22
+ #
23
+ # Class builder
24
+ #
25
+ # @param version [String] raw version as read from splitChanges response.
26
+ #
27
+ # @return [type] Semver instance
28
+ def self.build(version, logger)
29
+ new(version)
30
+ rescue NoMethodError => e
31
+ logger.error("Failed to parse Semver data, incorrect data type: #{e}")
32
+ nil
33
+ rescue StandardError => e
34
+ logger.error("Failed to parse Semver data: #{e}")
35
+ nil
36
+ end
37
+
38
+ #
39
+ # Check if there is any metadata characters in version.
40
+ #
41
+ # @return [type] String semver without the metadata
42
+ #
43
+ def remove_metadata_if_exists(old_version)
44
+ index = old_version.index(METADATA_DELIMITER)
45
+ return old_version if index.nil?
46
+
47
+ @metadata = old_version[index + 1, old_version.length]
48
+ old_version[0, index]
49
+ end
50
+
51
+ # Compare the current Semver object to a given Semver object, return:
52
+ # 0: if self == passed
53
+ # 1: if self > passed
54
+ # -1: if self < passed
55
+ #
56
+ # @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
57
+ #
58
+ # @returns [Integer] based on comparison
59
+ def compare(to_compare)
60
+ return 0 if @version == to_compare.version
61
+
62
+ # Compare major, minor, and patch versions numerically
63
+ result = compare_attributes(to_compare)
64
+ return result if result != 0
65
+
66
+ # Compare pre-release versions lexically
67
+ compare_pre_release(to_compare)
68
+ end
69
+
70
+ private
71
+
72
+ def integer?(value)
73
+ !!value.match(/^(\d)+$/)
74
+ end
75
+
76
+ #
77
+ # Parse the string in version to update the other internal variables
78
+ #
79
+ def parse(old_version)
80
+ without_metadata = remove_metadata_if_exists(old_version)
81
+
82
+ index = without_metadata.index(PRE_RELEASE_DELIMITER)
83
+ if index.nil?
84
+ @is_stable = true
85
+ else
86
+ pre_release_data = without_metadata[index + 1..-1]
87
+ without_metadata = without_metadata[0, index]
88
+ @pre_release = pre_release_data.split(VALUE_DELIMITER)
89
+ end
90
+ assign_major_minor_and_patch(without_metadata)
91
+ end
92
+
93
+ #
94
+ # Set the major, minor and patch internal variables based on string passed.
95
+ #
96
+ # @param version [String] raw version containing major.minor.patch numbers.
97
+ def assign_major_minor_and_patch(version)
98
+ parts = version.split(VALUE_DELIMITER)
99
+ if parts.length != 3 ||
100
+ !(integer?(parts[0]) &&
101
+ integer?(parts[1]) &&
102
+ integer?(parts[2]))
103
+ raise "Unable to convert to Semver, incorrect format: #{version}"
104
+ end
105
+
106
+ @major = parts[0].to_i
107
+ @minor = parts[1].to_i
108
+ @patch = parts[2].to_i
109
+ @version = "#{@major}#{VALUE_DELIMITER}#{@minor}#{VALUE_DELIMITER}#{@patch}"
110
+ @version += parse_pre_release
111
+ @version += "#{METADATA_DELIMITER}#{@metadata}" unless @metadata.empty?
112
+ end
113
+
114
+ def parse_pre_release
115
+ return '' if @pre_release.empty?
116
+
117
+ pre_parsed = []
118
+ @pre_release.each do |pre_digit|
119
+ pre_digit = pre_digit.to_i if integer?(pre_digit)
120
+ pre_parsed << pre_digit
121
+ end
122
+ "#{PRE_RELEASE_DELIMITER}#{pre_parsed.join('.')}"
123
+ end
124
+
125
+ #
126
+ # Compare 2 variables and return int as follows:
127
+ # 0: if var1 == var2
128
+ # 1: if var1 > var2
129
+ # -1: if var1 < var2
130
+ #
131
+ # @param var1 [type] String/Integer object that accept ==, < or > operators
132
+ # @param var2 [type] String/Integer object that accept ==, < or > operators
133
+ #
134
+ # @returns [Integer] based on comparison
135
+ def compare_vars(var1, var2)
136
+ return 0 if var1 == var2
137
+
138
+ return 1 if var1 > var2
139
+
140
+ -1
141
+ end
142
+
143
+ # Compare the current Semver object's major, minor, patch and is_stable attributes to a given Semver object, return:
144
+ # 0: if self == passed
145
+ # 1: if self > passed
146
+ # -1: if self < passed
147
+ #
148
+ # @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
149
+ #
150
+ # @returns [Integer] based on comparison
151
+ def compare_attributes(to_compare)
152
+ result = compare_vars(@major, to_compare.major)
153
+ return result if result != 0
154
+
155
+ result = compare_vars(@minor, to_compare.minor)
156
+ return result if result != 0
157
+
158
+ result = compare_vars(@patch, to_compare.patch)
159
+ return result if result != 0
160
+
161
+ return -1 if !@is_stable && to_compare.is_stable
162
+
163
+ return 1 if @is_stable && !to_compare.is_stable
164
+
165
+ 0
166
+ end
167
+
168
+ # Compare the current Semver object's pre_release attribute to a given Semver object, return:
169
+ # 0: if self == passed
170
+ # 1: if self > passed
171
+ # -1: if self < passed
172
+ #
173
+ # @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
174
+ #
175
+ # @returns [Integer] based on comparison
176
+ def compare_pre_release(to_compare)
177
+ min_length = get_pre_min_length(to_compare)
178
+ 0.upto(min_length - 1) do |i|
179
+ next if @pre_release[i] == to_compare.pre_release[i]
180
+
181
+ if integer?(@pre_release[i]) && integer?(to_compare.pre_release[i])
182
+ return compare_vars(@pre_release[i].to_i, to_compare.pre_release[i].to_i)
183
+
184
+ end
185
+
186
+ return compare_vars(@pre_release[i], to_compare.pre_release[i])
187
+ end
188
+ # Compare lengths of pre-release versions
189
+ compare_vars(@pre_release.length, to_compare.pre_release.length)
190
+ end
191
+
192
+ # Get minimum of current Semver object's pre_release attributes length to a given Semver object
193
+ #
194
+ # @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
195
+ #
196
+ # @returns [Integer]
197
+ def get_pre_min_length(to_compare)
198
+ [@pre_release.length, to_compare.pre_release.length].min
199
+ end
200
+ end
201
+ end
@@ -189,6 +189,47 @@ module SplitIoClient
189
189
  )
190
190
  end
191
191
 
192
+ def matcher_equal_to_semver(params)
193
+ EqualToSemverMatcher.new(
194
+ params[:matcher][:keySelector][:attribute],
195
+ params[:matcher][:stringMatcherData],
196
+ @config.split_logger, @config.split_validator
197
+ )
198
+ end
199
+
200
+ def matcher_greater_than_or_equal_to_semver(params)
201
+ GreaterThanOrEqualToSemverMatcher.new(
202
+ params[:matcher][:keySelector][:attribute],
203
+ params[:matcher][:stringMatcherData],
204
+ @config.split_logger, @config.split_validator
205
+ )
206
+ end
207
+
208
+ def matcher_less_than_or_equal_to_semver(params)
209
+ LessThanOrEqualToSemverMatcher.new(
210
+ params[:matcher][:keySelector][:attribute],
211
+ params[:matcher][:stringMatcherData],
212
+ @config.split_logger, @config.split_validator
213
+ )
214
+ end
215
+
216
+ def matcher_between_semver(params)
217
+ BetweenSemverMatcher.new(
218
+ params[:matcher][:keySelector][:attribute],
219
+ params[:matcher][:betweenStringMatcherData][:start],
220
+ params[:matcher][:betweenStringMatcherData][:end],
221
+ @config.split_logger, @config.split_validator
222
+ )
223
+ end
224
+
225
+ def matcher_in_list_semver(params)
226
+ InListSemverMatcher.new(
227
+ params[:matcher][:keySelector][:attribute],
228
+ params[:matcher][:whitelistMatcherData][:whitelist],
229
+ @config.split_logger, @config.split_validator
230
+ )
231
+ end
232
+
192
233
  #
193
234
  # @return [object] the negate value for this condition
194
235
  def negate
@@ -48,7 +48,7 @@ module SplitIoClient
48
48
  events_sender
49
49
  start_telemetry_sync_task
50
50
  end
51
-
51
+
52
52
  impressions_count_sender
53
53
  start_unique_keys_tracker_task
54
54
  end
@@ -175,7 +175,7 @@ module SplitIoClient
175
175
 
176
176
  # Starts thread which loops constantly and sends impressions to the Split API
177
177
  def impressions_sender
178
- ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call unless @config.impressions_mode == :none
178
+ ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call
179
179
  end
180
180
 
181
181
  # Starts thread which loops constantly and sends events to the Split API
@@ -185,7 +185,7 @@ module SplitIoClient
185
185
 
186
186
  # Starts thread which loops constantly and sends impressions count to the Split API
187
187
  def impressions_count_sender
188
- ImpressionsCountSender.new(@config, @impression_counter, @impressions_sender_adapter).call unless @config.impressions_mode == :debug
188
+ ImpressionsCountSender.new(@config, @impression_counter, @impressions_sender_adapter).call
189
189
  end
190
190
 
191
191
  def start_telemetry_sync_task
@@ -203,7 +203,7 @@ module SplitIoClient
203
203
  def sync_splits_and_segments
204
204
  @config.logger.debug('Synchronizing feature flags and segments ...') if @config.debug_enabled
205
205
  splits_result = @split_fetcher.fetch_splits
206
-
206
+
207
207
  splits_result[:success] && @segment_fetcher.fetch_segments
208
208
  end
209
209
  end
@@ -13,6 +13,13 @@ module SplitIoClient
13
13
  next
14
14
  end
15
15
 
16
+ unless feature_flag.key?(:impressionsDisabled)
17
+ feature_flag[:impressionsDisabled] = false
18
+ if config.debug_enabled
19
+ config.logger.debug("feature flag (#{feature_flag[:name]}) does not have impressionsDisabled field, setting it to false")
20
+ end
21
+ end
22
+
16
23
  config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled
17
24
  to_add.push(feature_flag)
18
25
  end
@@ -107,7 +107,8 @@ module SplitIoClient
107
107
  change_number: split[:changeNumber],
108
108
  configs: split[:configurations] || {},
109
109
  sets: split[:sets] || [],
110
- default_treatment: split[:defaultTreatment]
110
+ default_treatment: split[:defaultTreatment],
111
+ impressions_disabled: split[:impressionsDisabled]
111
112
  }
112
113
  end
113
114
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Spec
5
+ class FeatureFlags
6
+ SPEC_VERSION = "1.1"
7
+ end
8
+ end
9
+ end
@@ -230,11 +230,6 @@ module SplitIoClient
230
230
  end
231
231
 
232
232
  def build_unique_keys_tracker
233
- if @config.impressions_mode != :none
234
- @unique_keys_tracker = Engine::Impressions::NoopUniqueKeysTracker.new
235
- return
236
- end
237
-
238
233
  bf = Cache::Filter::BloomFilter.new(30_000_000)
239
234
  filter_adapter = Cache::Filter::FilterAdapter.new(@config, bf)
240
235
  cache = Concurrent::Hash.new
@@ -242,8 +237,7 @@ module SplitIoClient
242
237
  end
243
238
 
244
239
  def build_impressions_observer
245
- if (@config.cache_adapter == :redis && @config.impressions_mode != :optimized) ||
246
- (@config.cache_adapter == :memory && @config.impressions_mode == :none)
240
+ if (@config.cache_adapter == :redis && @config.impressions_mode != :optimized)
247
241
  @impression_observer = Observers::NoopImpressionObserver.new
248
242
  else
249
243
  @impression_observer = Observers::ImpressionObserver.new
@@ -251,12 +245,7 @@ module SplitIoClient
251
245
  end
252
246
 
253
247
  def build_impression_counter
254
- case @config.impressions_mode
255
- when :debug
256
- @impression_counter = Engine::Common::NoopImpressionCounter.new
257
- else
258
- @impression_counter = Engine::Common::ImpressionCounter.new
259
- end
248
+ @impression_counter = Engine::Common::ImpressionCounter.new
260
249
  end
261
250
 
262
251
  def build_impressions_sender_adapter
@@ -11,5 +11,13 @@ module SplitIoClient
11
11
  def log_if_transport(message)
12
12
  @config.logger.debug(message) if @config.transport_debug_enabled
13
13
  end
14
+
15
+ def error(message)
16
+ @config.logger.error(message)
17
+ end
18
+
19
+ def debug(message)
20
+ @config.logger.debug(message) if @config.debug_enabled
21
+ end
14
22
  end
15
23
  end
@@ -14,8 +14,8 @@ module SplitIoClient
14
14
  @telemetry_runtime_producer = telemetry_runtime_producer
15
15
  @status_queue = status_queue
16
16
  @publisher_available = Concurrent::AtomicBoolean.new(true)
17
- @publishers_pri = Concurrent::AtomicFixnum.new
18
- @publishers_sec = Concurrent::AtomicFixnum.new
17
+ @publishers_pri = Concurrent::AtomicFixnum.new(2)
18
+ @publishers_sec = Concurrent::AtomicFixnum.new(2)
19
19
  end
20
20
 
21
21
  def handle_incoming_occupancy_event(event)
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '8.3.1'
2
+ VERSION = '8.5.0'
3
3
  end
@@ -90,6 +90,12 @@ require 'splitclient-rb/engine/matchers/dependency_matcher'
90
90
  require 'splitclient-rb/engine/matchers/equal_to_boolean_matcher'
91
91
  require 'splitclient-rb/engine/matchers/equal_to_matcher'
92
92
  require 'splitclient-rb/engine/matchers/matches_string_matcher'
93
+ require 'splitclient-rb/engine/matchers/semver'
94
+ require 'splitclient-rb/engine/matchers/equal_to_semver_matcher'
95
+ require 'splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher'
96
+ require 'splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher'
97
+ require 'splitclient-rb/engine/matchers/between_semver_matcher'
98
+ require 'splitclient-rb/engine/matchers/in_list_semver_matcher'
93
99
  require 'splitclient-rb/engine/evaluator/splitter'
94
100
  require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker'
95
101
  require 'splitclient-rb/engine/impressions/unique_keys_tracker'
@@ -105,6 +111,8 @@ require 'splitclient-rb/engine/sync_manager'
105
111
  require 'splitclient-rb/engine/synchronizer'
106
112
  require 'splitclient-rb/utilitites'
107
113
 
114
+ require 'splitclient-rb/spec.rb'
115
+
108
116
  # SSE
109
117
  require 'splitclient-rb/sse/event_source/client'
110
118
  require 'splitclient-rb/sse/event_source/event_parser'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: splitclient-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.3.1
4
+ version: 8.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-25 00:00:00.000000000 Z
11
+ date: 2025-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: allocation_stats
@@ -489,6 +489,7 @@ files:
489
489
  - lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb
490
490
  - lib/splitclient-rb/engine/matchers/all_keys_matcher.rb
491
491
  - lib/splitclient-rb/engine/matchers/between_matcher.rb
492
+ - lib/splitclient-rb/engine/matchers/between_semver_matcher.rb
492
493
  - lib/splitclient-rb/engine/matchers/combiners.rb
493
494
  - lib/splitclient-rb/engine/matchers/combining_matcher.rb
494
495
  - lib/splitclient-rb/engine/matchers/contains_all_matcher.rb
@@ -498,13 +499,18 @@ files:
498
499
  - lib/splitclient-rb/engine/matchers/ends_with_matcher.rb
499
500
  - lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb
500
501
  - lib/splitclient-rb/engine/matchers/equal_to_matcher.rb
502
+ - lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb
501
503
  - lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb
502
504
  - lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb
505
+ - lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb
506
+ - lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb
503
507
  - lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb
508
+ - lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb
504
509
  - lib/splitclient-rb/engine/matchers/matcher.rb
505
510
  - lib/splitclient-rb/engine/matchers/matches_string_matcher.rb
506
511
  - lib/splitclient-rb/engine/matchers/negation_matcher.rb
507
512
  - lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb
513
+ - lib/splitclient-rb/engine/matchers/semver.rb
508
514
  - lib/splitclient-rb/engine/matchers/set_matcher.rb
509
515
  - lib/splitclient-rb/engine/matchers/starts_with_matcher.rb
510
516
  - lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb
@@ -526,6 +532,7 @@ files:
526
532
  - lib/splitclient-rb/helpers/thread_helper.rb
527
533
  - lib/splitclient-rb/helpers/util.rb
528
534
  - lib/splitclient-rb/managers/split_manager.rb
535
+ - lib/splitclient-rb/spec.rb
529
536
  - lib/splitclient-rb/split_config.rb
530
537
  - lib/splitclient-rb/split_factory.rb
531
538
  - lib/splitclient-rb/split_factory_builder.rb
@@ -587,7 +594,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
587
594
  - !ruby/object:Gem::Version
588
595
  version: '0'
589
596
  requirements: []
590
- rubygems_version: 3.2.3
597
+ rubygems_version: 3.0.9
591
598
  signing_key:
592
599
  specification_version: 4
593
600
  summary: Ruby client for split SDK.