splitclient-rb 8.7.0 → 8.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dee5c1546cb828e8f0bd5d8da3be7b3eaa85324e38fcc26ae730d6fc1e5954b7
4
- data.tar.gz: 1951546596f01d3aef2688ad40f8b9c060b0032f5df42f55e2f90ba26c42b4f2
3
+ metadata.gz: e4b77297a7d366bf770ee722755053a57b7335dc0c430976d9c9979df36aa314
4
+ data.tar.gz: 22e38080ffb04c1223637db2d5a38e4fa0f6a7601aeee289bcb66efc661eeb7c
5
5
  SHA512:
6
- metadata.gz: '094314dcffc65c199290db8a2bbcd69902deaaf61adc01dba13da0ce348155ef2731ebe8b0d0cdb006502bb5e4ab699549e21e79008831bd1ddd5a0b44cc5da7'
7
- data.tar.gz: e1f1935ec5f7eee464dd07e1e7cff953434721db0181e1d243671e05df273ed9fea8ce5c3de643674e957d0b3a97949e2b524f53237d2dbce7425d7c7af27b16
6
+ metadata.gz: 89f39548cab9c8d6f1e234a696653940ba928bb2c2f061f26a37a471f964f29895d6530bf480d45eab757bf771aea4436bd7b177eb10dbbbd1cfe10dedb5ebc4
7
+ data.tar.gz: 5222a70eaf6a336543dc3447bca2a2ba2d69ddcf8ef686e68451f645ce721c0b1028c0f8e8416d8bc85c2afe37cbc6a2c4ba8be2c0af39a0c9122398c016a5c6
data/CHANGES.txt CHANGED
@@ -1,5 +1,8 @@
1
1
  CHANGES
2
2
 
3
+ 8.8.0 (Sep 26, 2025)
4
+ - Added a maximum size payload when posting unique keys telemetry in batches
5
+
3
6
  8.7.0 (Aug 1, 2025)
4
7
  - Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs.
5
8
 
@@ -18,7 +18,7 @@ module SplitIoClient
18
18
  # @param sdk_key [String] the SDK key for your split account
19
19
  #
20
20
  # @return [SplitIoClient] split.io client instance
21
- def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator)
21
+ def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator, fallback_treatment_calculator)
22
22
  @api_key = sdk_key
23
23
  @splits_repository = repositories[:splits]
24
24
  @segments_repository = repositories[:segments]
@@ -32,6 +32,7 @@ module SplitIoClient
32
32
  @telemetry_evaluation_producer = telemetry_evaluation_producer
33
33
  @split_validator = split_validator
34
34
  @evaluator = evaluator
35
+ @fallback_treatment_calculator = fallback_treatment_calculator
35
36
  end
36
37
 
37
38
  def get_treatment(
@@ -50,7 +51,9 @@ module SplitIoClient
50
51
  multiple = false, evaluator = nil
51
52
  )
52
53
  log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
53
- treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
54
+ result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
55
+
56
+ { :config => result[:config], :treatment => result[:treatment] }
54
57
  end
55
58
 
56
59
  def get_treatments(key, split_names, attributes = {}, evaluation_options = nil)
@@ -63,7 +66,11 @@ module SplitIoClient
63
66
  end
64
67
 
65
68
  def get_treatments_with_config(key, split_names, attributes = {}, evaluation_options = nil)
66
- treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)
69
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)
70
+
71
+ results.map{|key, value|
72
+ [key, { treatment: value[:treatment], config: value[:config] }]
73
+ }.to_h
67
74
  end
68
75
 
69
76
  def get_treatments_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
@@ -89,13 +96,21 @@ module SplitIoClient
89
96
  def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
90
97
  valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set])
91
98
  split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
92
- treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
99
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
100
+
101
+ results.map{|key, value|
102
+ [key, { treatment: value[:treatment], config: value[:config] }]
103
+ }.to_h
93
104
  end
94
105
 
95
106
  def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
96
107
  valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets)
97
108
  split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
98
- treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
109
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
110
+
111
+ results.map{|key, value|
112
+ [key, { treatment: value[:treatment], config: value[:config] }]
113
+ }.to_h
99
114
  end
100
115
 
101
116
  def destroy
@@ -277,7 +292,7 @@ module SplitIoClient
277
292
  if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes)
278
293
  to_return = Hash.new
279
294
  sanitized_feature_flag_names.each {|name|
280
- to_return[name.to_sym] = control_treatment_with_config
295
+ to_return[name.to_sym] = check_fallback_treatment(name, '')
281
296
  }
282
297
  return to_return
283
298
  end
@@ -286,9 +301,11 @@ module SplitIoClient
286
301
  impressions = []
287
302
  to_return = Hash.new
288
303
  sanitized_feature_flag_names.each {|name|
289
- to_return[name.to_sym] = control_treatment_with_config
304
+ treatment_data = check_fallback_treatment(name, Engine::Models::Label::NOT_READY)
305
+ to_return[name.to_sym] = treatment_data
306
+
290
307
  impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym,
291
- control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil },
308
+ get_treatment_without_config(treatment_data), false, { attributes: attributes, time: nil },
292
309
  evaluation_options), :disabled => false }
293
310
  }
294
311
  @impressions_manager.track(impressions)
@@ -308,7 +325,7 @@ module SplitIoClient
308
325
  if feature_flag.nil?
309
326
  @config.logger.warn("#{calling_method}: you passed #{key} that " \
310
327
  'does not exist in this environment, please double check what feature flags exist in the Split user interface')
311
- invalid_treatments[key] = control_treatment_with_config
328
+ invalid_treatments[key] = check_fallback_treatment(key, Engine::Models::Label::NOT_FOUND)
312
329
  next
313
330
  end
314
331
  treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method, false, evaluation_options)
@@ -344,7 +361,7 @@ module SplitIoClient
344
361
 
345
362
  attributes = parsed_attributes(attributes)
346
363
 
347
- return parsed_treatment(control_treatment, multiple) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes)
364
+ return parsed_treatment(check_fallback_treatment(feature_flag_name, ""), multiple) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes)
348
365
 
349
366
  bucketing_key = bucketing_key ? bucketing_key.to_s : nil
350
367
  matching_key = matching_key.to_s
@@ -373,7 +390,7 @@ module SplitIoClient
373
390
  if feature_flag.nil? && ready?
374
391
  @config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \
375
392
  'does not exist in this environment, please double check what feature flags exist in the Split user interface')
376
- return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::NOT_FOUND }), multiple), nil
393
+ return check_fallback_treatment(feature_flag_name, Engine::Models::Label::NOT_FOUND), nil
377
394
  end
378
395
 
379
396
  if !feature_flag.nil? && ready?
@@ -383,7 +400,7 @@ module SplitIoClient
383
400
  impressions_disabled = feature_flag[:impressionsDisabled]
384
401
  else
385
402
  @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.")
386
- treatment_data = control_treatment.merge({ :label => Engine::Models::Label::NOT_READY })
403
+ treatment_data = check_fallback_treatment(feature_flag_name, Engine::Models::Label::NOT_READY)
387
404
  impressions_disabled = false
388
405
  end
389
406
 
@@ -396,22 +413,16 @@ module SplitIoClient
396
413
  rescue StandardError => e
397
414
  @config.log_found_exception(__method__.to_s, e)
398
415
  record_exception(calling_method)
399
- impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }, evaluation_options), :disabled => false }
416
+ treatment_data = check_fallback_treatment(feature_flag_name, Engine::Models::Label::EXCEPTION)
417
+ impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, get_treatment_without_config(treatment_data), false, { attributes: attributes, time: nil }, evaluation_options), :disabled => false }
418
+
400
419
  impressions_decorator << impression_decorator unless impression_decorator.nil?
401
420
 
402
- return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::EXCEPTION }), multiple), impressions_decorator
421
+ return parsed_treatment(treatment_data, multiple), impressions_decorator
403
422
  end
404
423
  return parsed_treatment(treatment_data, multiple), impressions_decorator
405
424
  end
406
425
 
407
- def control_treatment
408
- { :treatment => Engine::Models::Treatment::CONTROL }
409
- end
410
-
411
- def control_treatment_with_config
412
- {:treatment => Engine::Models::Treatment::CONTROL, :config => nil}
413
- end
414
-
415
426
  def variable_size(value)
416
427
  value.is_a?(String) ? value.length : 0
417
428
  end
@@ -472,5 +483,39 @@ module SplitIoClient
472
483
  @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TRACK)
473
484
  end
474
485
  end
486
+
487
+ def check_fallback_treatment(feature_name, label)
488
+ return {
489
+ label: (label != '')? label : nil,
490
+ treatment: Engine::Models::Treatment::CONTROL,
491
+ config: nil,
492
+ change_number: nil
493
+ } unless feature_name.is_a?(Symbol) || feature_name.is_a?(String)
494
+
495
+ fallback_treatment = @fallback_treatment_calculator.resolve(feature_name.to_sym, label)
496
+
497
+ {
498
+ label: (label != '')? fallback_treatment.label : nil,
499
+ treatment: fallback_treatment.treatment,
500
+ config: get_fallback_config(fallback_treatment),
501
+ change_number: nil
502
+ }
503
+ end
504
+
505
+ def get_treatment_without_config(treatment)
506
+ {
507
+ label: treatment[:label],
508
+ treatment: treatment[:treatment],
509
+ }
510
+ end
511
+
512
+ def get_fallback_config(fallback_treatment)
513
+ if fallback_treatment.config != nil
514
+ return fallback_treatment.config
515
+ end
516
+
517
+ return nil
518
+ end
519
+
475
520
  end
476
521
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ class FallbackTreatmentCalculator
6
+ attr_accessor :fallback_treatments_configuration, :label_prefix
7
+
8
+ def initialize(fallback_treatment_configuration)
9
+ @label_prefix = 'fallback - '
10
+ @fallback_treatments_configuration = fallback_treatment_configuration
11
+ end
12
+
13
+ def resolve(flag_name, label)
14
+ default_fallback_treatment = Engine::Models::FallbackTreatment.new(
15
+ Engine::Models::Treatment::CONTROL,
16
+ nil,
17
+ label
18
+ )
19
+ return default_fallback_treatment if @fallback_treatments_configuration.nil?
20
+
21
+ if !@fallback_treatments_configuration.by_flag_fallback_treatment.nil? \
22
+ && !@fallback_treatments_configuration.by_flag_fallback_treatment.fetch(flag_name, nil).nil?
23
+ return copy_with_label(
24
+ @fallback_treatments_configuration.by_flag_fallback_treatment[flag_name],
25
+ resolve_label(label)
26
+ )
27
+ end
28
+
29
+ return copy_with_label(@fallback_treatments_configuration.global_fallback_treatment, resolve_label(label)) \
30
+ unless @fallback_treatments_configuration.global_fallback_treatment.nil?
31
+
32
+ default_fallback_treatment
33
+ end
34
+
35
+ private
36
+
37
+ def resolve_label(label)
38
+ return nil if label.nil?
39
+
40
+ @label_prefix + label
41
+ end
42
+
43
+ def copy_with_label(fallback_treatment, label)
44
+ Engine::Models::FallbackTreatment.new(fallback_treatment.treatment, fallback_treatment.config, label)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -14,9 +14,9 @@ module SplitIoClient
14
14
  @filter_adapter = filter_adapter
15
15
  @sender_adapter = sender_adapter
16
16
  @cache = cache
17
- @cache_max_size = config.unique_keys_cache_max_size
18
17
  @max_bulk_size = config.unique_keys_bulk_size
19
18
  @semaphore = Mutex.new
19
+ @keys_size = 0
20
20
  end
21
21
 
22
22
  def call
@@ -30,8 +30,9 @@ module SplitIoClient
30
30
  @filter_adapter.add(feature_name, key)
31
31
 
32
32
  add_or_update(feature_name, key)
33
+ @keys_size += 1
33
34
 
34
- send_bulk_data if @cache.size >= @cache_max_size
35
+ send_bulk_data if @keys_size >= @max_bulk_size
35
36
 
36
37
  true
37
38
  rescue StandardError => e
@@ -70,27 +71,73 @@ module SplitIoClient
70
71
  end
71
72
  end
72
73
 
74
+ def clear_cache
75
+ uniques = @cache.clone
76
+ keys_size = @keys_size
77
+ @cache.clear
78
+ @keys_size = 0
79
+
80
+ [uniques, keys_size]
81
+ end
82
+
73
83
  def send_bulk_data
74
84
  @semaphore.synchronize do
75
85
  return if @cache.empty?
76
86
 
77
- uniques = @cache.clone
78
- @cache.clear
79
-
80
- if uniques.size <= @max_bulk_size
87
+ uniques, keys_size = clear_cache
88
+ if keys_size <= @max_bulk_size
81
89
  @sender_adapter.record_uniques_key(uniques)
82
90
  return
83
- end
84
-
85
- bulks = SplitIoClient::Utilities.split_bulk_to_send(uniques, uniques.size / @max_bulk_size)
86
91
 
87
- bulks.each do |b|
88
- @sender_adapter.record_uniques_key(b)
89
92
  end
93
+ bulks = flatten_bulks(uniques)
94
+ bulks_to_post = group_bulks_by_max_size(bulks)
95
+ @sender_adapter.record_uniques_key(bulks_to_post)
90
96
  end
91
97
  rescue StandardError => e
92
98
  @config.log_found_exception(__method__.to_s, e)
93
99
  end
100
+
101
+ def group_bulks_by_max_size(bulks)
102
+ current_size = 0
103
+ bulks_to_post = Concurrent::Hash.new
104
+ bulks.each do |bulk|
105
+ key, value = bulk.first
106
+ if (value.size + current_size) > @max_bulk_size
107
+ @sender_adapter.record_uniques_key(bulks_to_post)
108
+ bulks_to_post = Concurrent::Hash.new
109
+ current_size = 0
110
+ end
111
+ bulks_to_post[key] = value
112
+ current_size += value.size
113
+ end
114
+
115
+ bulks_to_post
116
+ end
117
+
118
+ def flatten_bulks(uniques)
119
+ bulks = []
120
+ uniques.each_key do |unique_key|
121
+ bulks += check_keys_and_split_to_bulks(uniques[unique_key], unique_key)
122
+ end
123
+
124
+ bulks
125
+ end
126
+
127
+ def check_keys_and_split_to_bulks(value, key)
128
+ unique_updated = []
129
+ if value.size > @max_bulk_size
130
+ sub_bulks = SplitIoClient::Utilities.split_bulk_to_send(value, @max_bulk_size)
131
+ sub_bulks.each do |sub_bulk|
132
+ unique_updated << { key => sub_bulk.to_set }
133
+ end
134
+ return unique_updated
135
+
136
+ end
137
+ unique_updated << { key => value }
138
+
139
+ unique_updated
140
+ end
94
141
  end
95
142
  end
96
143
  end
@@ -0,0 +1,11 @@
1
+ module SplitIoClient::Engine::Models
2
+ class FallbackTreatment
3
+ attr_accessor :treatment, :config, :label
4
+
5
+ def initialize(treatment, config=nil, label=nil)
6
+ @treatment = treatment
7
+ @config = config
8
+ @label = label
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ module SplitIoClient::Engine::Models
2
+ class FallbackTreatmentsConfiguration
3
+ attr_accessor :global_fallback_treatment, :by_flag_fallback_treatment
4
+
5
+ def initialize(global_fallback_treatment=nil, by_flag_fallback_treatment=nil)
6
+ @global_fallback_treatment = build_global_fallback_treatment(global_fallback_treatment)
7
+ @by_flag_fallback_treatment = build_by_flag_fallback_treatment(by_flag_fallback_treatment)
8
+ end
9
+
10
+ private
11
+
12
+ def build_global_fallback_treatment(global_fallback_treatment)
13
+ if global_fallback_treatment.is_a? String
14
+ return FallbackTreatment.new(global_fallback_treatment)
15
+ end
16
+
17
+ global_fallback_treatment
18
+ end
19
+
20
+ def build_by_flag_fallback_treatment(by_flag_fallback_treatment)
21
+ return nil unless by_flag_fallback_treatment.is_a?(Hash)
22
+ processed_by_flag_fallback_treatment = Hash.new
23
+
24
+ by_flag_fallback_treatment.each do |key, value|
25
+ if value.is_a? String
26
+ processed_by_flag_fallback_treatment[key] = FallbackTreatment.new(value)
27
+ next
28
+ end
29
+
30
+ processed_by_flag_fallback_treatment[key] = value
31
+ end
32
+
33
+ processed_by_flag_fallback_treatment
34
+ end
35
+ end
36
+ end
@@ -112,7 +112,7 @@ module SplitIoClient
112
112
  @telemetry_service_url = opts[:telemetry_service_url] || SplitConfig.default_telemetry_service_url
113
113
 
114
114
  @unique_keys_refresh_rate = SplitConfig.default_unique_keys_refresh_rate(@cache_adapter)
115
- @unique_keys_cache_max_size = SplitConfig.default_unique_keys_cache_max_size
115
+ # @unique_keys_cache_max_size = SplitConfig.default_unique_keys_cache_max_size
116
116
  @unique_keys_bulk_size = SplitConfig.default_unique_keys_bulk_size(@cache_adapter)
117
117
 
118
118
  @counter_refresh_rate = SplitConfig.default_counter_refresh_rate(@cache_adapter)
@@ -123,6 +123,8 @@ module SplitIoClient
123
123
  @on_demand_fetch_max_retries = SplitConfig.default_on_demand_fetch_max_retries
124
124
 
125
125
  @flag_sets_filter = SplitConfig.sanitize_flag_set_filter(opts[:flag_sets_filter], @split_validator, opts[:cache_adapter], @logger)
126
+
127
+ @fallback_treatments_configuration = SplitConfig.sanitize_fallback_config(opts[:fallback_treatments], @split_validator, @logger)
126
128
  startup_log
127
129
  end
128
130
 
@@ -292,7 +294,7 @@ module SplitIoClient
292
294
  attr_accessor :on_demand_fetch_max_retries
293
295
 
294
296
  attr_accessor :unique_keys_refresh_rate
295
- attr_accessor :unique_keys_cache_max_size
297
+ #attr_accessor :unique_keys_cache_max_size
296
298
  attr_accessor :unique_keys_bulk_size
297
299
 
298
300
  attr_accessor :counter_refresh_rate
@@ -303,6 +305,8 @@ module SplitIoClient
303
305
  # @return [Array]
304
306
  attr_accessor :flag_sets_filter
305
307
 
308
+ attr_accessor :fallback_treatments_configuration
309
+
306
310
  def self.default_counter_refresh_rate(adapter)
307
311
  return 300 if adapter == :redis # Send bulk impressions count - Refresh rate: 5 min.
308
312
 
@@ -498,9 +502,9 @@ module SplitIoClient
498
502
  900
499
503
  end
500
504
 
501
- def self.default_unique_keys_cache_max_size
502
- 30000
503
- end
505
+ # def self.default_unique_keys_cache_max_size
506
+ # 30000
507
+ # end
504
508
 
505
509
  def self.default_unique_keys_bulk_size(adapter)
506
510
  return 2000 if adapter == :redis
@@ -697,5 +701,37 @@ module SplitIoClient
697
701
 
698
702
  return ''.freeze
699
703
  end
704
+
705
+ def self.sanitize_fallback_config(fallback_config, validator, logger)
706
+ return fallback_config if fallback_config.nil?
707
+
708
+ processed = Engine::Models::FallbackTreatmentsConfiguration.new
709
+ if !fallback_config.is_a?(Engine::Models::FallbackTreatmentsConfiguration)
710
+ logger.warn('Config: fallbackTreatments parameter should be of `FallbackTreatmentsConfiguration` class.')
711
+ return processed
712
+ end
713
+
714
+ sanitized_global_fallback_treatment = fallback_config.global_fallback_treatment
715
+ if !fallback_config.global_fallback_treatment.nil? && !validator.validate_fallback_treatment('Config', fallback_config.global_fallback_treatment)
716
+ logger.warn('Config: global fallbacktreatment parameter is discarded.')
717
+ sanitized_global_fallback_treatment = nil
718
+ end
719
+
720
+ sanitized_flag_fallback_treatments = nil
721
+ if !fallback_config.by_flag_fallback_treatment.nil? && fallback_config.by_flag_fallback_treatment.is_a?(Hash)
722
+ sanitized_flag_fallback_treatments = Hash.new
723
+ for feature_name in fallback_config.by_flag_fallback_treatment.keys()
724
+ if !validator.valid_split_name?('Config', feature_name) || !validator.validate_fallback_treatment('Config', fallback_config.by_flag_fallback_treatment[feature_name])
725
+ logger.warn("Config: fallback treatment parameter for feature flag #{feature_name} is discarded.")
726
+ next
727
+ end
728
+
729
+ sanitized_flag_fallback_treatments[feature_name] = fallback_config.by_flag_fallback_treatment[feature_name]
730
+ end
731
+ end
732
+ processed = Engine::Models::FallbackTreatmentsConfiguration.new(sanitized_global_fallback_treatment, sanitized_flag_fallback_treatments)
733
+
734
+ processed
735
+ end
700
736
  end
701
737
  end
@@ -58,8 +58,8 @@ module SplitIoClient
58
58
  @evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @rule_based_segment_repository, @config)
59
59
 
60
60
  start!
61
-
62
- @client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer, @evaluator, @split_validator)
61
+ fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(@config.fallback_treatments_configuration)
62
+ @client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer, @evaluator, @split_validator, fallback_treatment_calculator)
63
63
  @manager = SplitManager.new(@splits_repository, @status_manager, @config)
64
64
  end
65
65
 
@@ -38,16 +38,12 @@ module SplitIoClient
38
38
  interval * random_factor
39
39
  end
40
40
 
41
- def split_bulk_to_send(hash, divisions)
42
- count = 0
43
-
44
- hash.each_with_object([]) do |key_value, final|
45
- final[count % divisions] ||= {}
46
- final[count % divisions][key_value[0]] = key_value[1]
47
- count += 1
48
- end
49
- rescue StandardError
50
- []
41
+ def split_bulk_to_send(items, divisions)
42
+ to_return = []
43
+ items.to_a.each_slice(divisions) {|bulk|
44
+ to_return.push(bulk.to_set)
45
+ }
46
+ to_return
51
47
  end
52
48
  end
53
49
  end
@@ -4,6 +4,8 @@ module SplitIoClient
4
4
  class Validators
5
5
 
6
6
  Flagset_regex = /^[a-z0-9][_a-z0-9]{0,49}$/
7
+ Fallback_treatment_regex = /^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/
8
+ Fallback_treatment_size = 100
7
9
 
8
10
  def initialize(config)
9
11
  @config = config
@@ -68,7 +70,7 @@ module SplitIoClient
68
70
  log_invalid_flag_set_type(method)
69
71
  elsif flag_set.is_a?(String) && flag_set.empty?
70
72
  log_invalid_flag_set_type(method)
71
- elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method)
73
+ elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method, Flagset_regex, :log_invalid_match)
72
74
  valid_flag_sets.add(flag_set.strip.downcase)
73
75
  else
74
76
  log_invalid_flag_set_type(method)
@@ -77,6 +79,46 @@ module SplitIoClient
77
79
  !valid_flag_sets.empty? ? valid_flag_sets.to_a.sort : []
78
80
  end
79
81
 
82
+ def validate_fallback_treatment(method, fallback_treatment)
83
+ if !fallback_treatment.is_a? Engine::Models::FallbackTreatment
84
+ @config.logger.warn("#{method}: Fallback treatment instance should be FallbackTreatment, input is discarded")
85
+ return false
86
+ end
87
+
88
+ if !fallback_treatment.treatment.is_a? String
89
+ @config.logger.warn("#{method}: Fallback treatment value should be str type, input is discarded")
90
+ return false
91
+ end
92
+
93
+ return false unless string_match?(fallback_treatment.treatment, method, Fallback_treatment_regex, :log_invalid_fallback_treatment)
94
+
95
+ if fallback_treatment.treatment.size > Fallback_treatment_size
96
+ @config.logger.warn("#{method}: Fallback treatment size should not exceed #{Fallback_treatment_size} characters")
97
+ return false
98
+ end
99
+
100
+ true
101
+ end
102
+
103
+ def valid_split_name?(method, split_name)
104
+ if split_name.nil?
105
+ log_nil(:split_name, method)
106
+ return false
107
+ end
108
+
109
+ unless string?(split_name)
110
+ log_invalid_type(:split_name, method)
111
+ return false
112
+ end
113
+
114
+ if empty_string?(split_name)
115
+ log_empty_string(:split_name, method)
116
+ return false
117
+ end
118
+
119
+ true
120
+ end
121
+
80
122
  private
81
123
 
82
124
  def string?(value)
@@ -91,9 +133,9 @@ module SplitIoClient
91
133
  (value.is_a?(Numeric) && !value.to_f.nan?) || string?(value)
92
134
  end
93
135
 
94
- def string_match?(value, method)
95
- if Flagset_regex.match(value) == nil
96
- log_invalid_match(value, method)
136
+ def string_match?(value, method, regex_exp, log_if_invalid)
137
+ if regex_exp.match(value) == nil
138
+ method(log_if_invalid).call(value, method)
97
139
  false
98
140
  else
99
141
  true
@@ -132,25 +174,6 @@ module SplitIoClient
132
174
  @config.logger.error("#{method}: #{key} is too long - must be #{@config.max_key_size} characters or less")
133
175
  end
134
176
 
135
- def valid_split_name?(method, split_name)
136
- if split_name.nil?
137
- log_nil(:split_name, method)
138
- return false
139
- end
140
-
141
- unless string?(split_name)
142
- log_invalid_type(:split_name, method)
143
- return false
144
- end
145
-
146
- if empty_string?(split_name)
147
- log_empty_string(:split_name, method)
148
- return false
149
- end
150
-
151
- true
152
- end
153
-
154
177
  def valid_key?(method, key)
155
178
  if key.nil?
156
179
  log_nil(:key, method)
@@ -326,5 +349,9 @@ module SplitIoClient
326
349
 
327
350
  true
328
351
  end
352
+
353
+ def log_invalid_fallback_treatment(key, method)
354
+ @config.logger.warn("#{method}: Invalid treatment #{key}, Fallback treatment should match regex #{Fallback_treatment_regex}")
355
+ end
329
356
  end
330
357
  end
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '8.7.0'
2
+ VERSION = '8.9.0-rc1'
3
3
  end
@@ -110,8 +110,11 @@ require 'splitclient-rb/engine/models/segment_type'
110
110
  require 'splitclient-rb/engine/models/treatment'
111
111
  require 'splitclient-rb/engine/models/split_http_response'
112
112
  require 'splitclient-rb/engine/models/evaluation_options'
113
+ require 'splitclient-rb/engine/models/fallback_treatment.rb'
114
+ require 'splitclient-rb/engine/models/fallback_treatments_configuration.rb'
113
115
  require 'splitclient-rb/engine/auth_api_client'
114
116
  require 'splitclient-rb/engine/back_off'
117
+ require 'splitclient-rb/engine/fallback_treatment_calculator.rb'
115
118
  require 'splitclient-rb/engine/push_manager'
116
119
  require 'splitclient-rb/engine/status_manager'
117
120
  require 'splitclient-rb/engine/sync_manager'
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.7.0
4
+ version: 8.9.0.pre.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-01 00:00:00.000000000 Z
11
+ date: 2025-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: allocation_stats
@@ -481,6 +481,7 @@ files:
481
481
  - lib/splitclient-rb/engine/common/impressions_manager.rb
482
482
  - lib/splitclient-rb/engine/common/noop_impressions_counter.rb
483
483
  - lib/splitclient-rb/engine/evaluator/splitter.rb
484
+ - lib/splitclient-rb/engine/fallback_treatment_calculator.rb
484
485
  - lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb
485
486
  - lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb
486
487
  - lib/splitclient-rb/engine/matchers/all_keys_matcher.rb
@@ -515,6 +516,8 @@ files:
515
516
  - lib/splitclient-rb/engine/matchers/whitelist_matcher.rb
516
517
  - lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb
517
518
  - lib/splitclient-rb/engine/models/evaluation_options.rb
519
+ - lib/splitclient-rb/engine/models/fallback_treatment.rb
520
+ - lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb
518
521
  - lib/splitclient-rb/engine/models/label.rb
519
522
  - lib/splitclient-rb/engine/models/segment_type.rb
520
523
  - lib/splitclient-rb/engine/models/split.rb
@@ -592,9 +595,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
592
595
  version: 2.5.0
593
596
  required_rubygems_version: !ruby/object:Gem::Requirement
594
597
  requirements:
595
- - - ">="
598
+ - - ">"
596
599
  - !ruby/object:Gem::Version
597
- version: '0'
600
+ version: 1.3.1
598
601
  requirements: []
599
602
  rubyforge_project:
600
603
  rubygems_version: 2.7.6.2