splitclient-rb 8.4.0 → 8.11.0

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -2
  3. data/CHANGES.txt +24 -0
  4. data/LICENSE +169 -13
  5. data/NOTICE.txt +5 -0
  6. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +9 -7
  7. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +10 -6
  8. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +1 -0
  9. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
  10. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +21 -2
  11. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +42 -12
  12. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +24 -10
  13. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +2 -1
  14. data/lib/splitclient-rb/clients/split_client.rb +146 -62
  15. data/lib/splitclient-rb/engine/api/client.rb +8 -0
  16. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
  17. data/lib/splitclient-rb/engine/api/splits.rb +100 -24
  18. data/lib/splitclient-rb/engine/auth_api_client.rb +3 -3
  19. data/lib/splitclient-rb/engine/common/impressions_manager.rb +56 -30
  20. data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
  21. data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
  22. data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
  23. data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
  24. data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
  25. data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
  26. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +58 -11
  27. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +3 -1
  28. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  29. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
  30. data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
  31. data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
  32. data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
  33. data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
  34. data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
  35. data/lib/splitclient-rb/engine/models/label.rb +1 -0
  36. data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
  37. data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
  38. data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
  39. data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +14 -0
  40. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  41. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  42. data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
  43. data/lib/splitclient-rb/engine/parser/condition.rb +9 -0
  44. data/lib/splitclient-rb/engine/parser/evaluator.rb +24 -30
  45. data/lib/splitclient-rb/engine/status_manager.rb +7 -1
  46. data/lib/splitclient-rb/engine/sync_manager.rb +6 -6
  47. data/lib/splitclient-rb/engine/synchronizer.rb +37 -17
  48. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  49. data/lib/splitclient-rb/helpers/repository_helper.rb +39 -1
  50. data/lib/splitclient-rb/helpers/util.rb +12 -3
  51. data/lib/splitclient-rb/managers/split_manager.rb +2 -1
  52. data/lib/splitclient-rb/spec.rb +1 -1
  53. data/lib/splitclient-rb/split_config.rb +45 -5
  54. data/lib/splitclient-rb/split_factory.rb +27 -21
  55. data/lib/splitclient-rb/sse/event_source/client.rb +86 -29
  56. data/lib/splitclient-rb/sse/event_source/event_parser.rb +2 -2
  57. data/lib/splitclient-rb/sse/event_source/event_types.rb +1 -0
  58. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +3 -3
  59. data/lib/splitclient-rb/sse/notification_processor.rb +3 -1
  60. data/lib/splitclient-rb/sse/workers/segments_worker.rb +4 -4
  61. data/lib/splitclient-rb/sse/workers/splits_worker.rb +63 -24
  62. data/lib/splitclient-rb/utilitites.rb +6 -10
  63. data/lib/splitclient-rb/validators.rb +56 -25
  64. data/lib/splitclient-rb/version.rb +1 -1
  65. data/lib/splitclient-rb.rb +22 -0
  66. data/sonar-project.properties +1 -0
  67. data/splitclient-rb.gemspec +3 -4
  68. metadata +32 -38
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: false
2
2
 
3
- require 'socketry'
3
+ require 'socket'
4
+ require 'openssl'
4
5
  require 'uri'
6
+ require 'timeout'
5
7
 
6
8
  module SplitIoClient
7
9
  module SSE
@@ -36,26 +38,28 @@ module SplitIoClient
36
38
 
37
39
  def close(status = nil)
38
40
  unless connected?
39
- @config.logger.error('SSEClient already disconected.') if @config.debug_enabled
41
+ @config.logger.debug('SSEClient already disconected.') if @config.debug_enabled
40
42
  return
41
43
  end
44
+ @config.logger.debug("Closing SSEClient socket") if @config.debug_enabled
42
45
 
43
- @connected.make_false
44
- @socket&.close
45
46
  push_status(status)
47
+ @connected.make_false
48
+ @socket.sync_close = true if @socket.is_a? OpenSSL::SSL::SSLSocket
49
+ @socket.close
50
+ @config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket && @config.debug_enabled
46
51
  rescue StandardError => e
47
52
  @config.logger.error("SSEClient close Error: #{e.inspect}")
48
53
  end
49
54
 
50
55
  def start(url)
51
56
  if connected?
52
- @config.logger.debug('SSEClient already running.')
57
+ @config.logger.debug('SSEClient already running.') if @config.debug_enabled
53
58
  return true
54
59
  end
55
60
 
56
61
  @uri = URI(url)
57
62
  latch = Concurrent::CountDownLatch.new(1)
58
-
59
63
  connect_thread(latch)
60
64
 
61
65
  return false unless latch.wait(CONNECT_TIMEOUT)
@@ -74,42 +78,72 @@ module SplitIoClient
74
78
 
75
79
  def connect_thread(latch)
76
80
  @config.threads[:connect_stream] = Thread.new do
77
- @config.logger.info('Starting connect_stream thread ...') if @config.debug_enabled
81
+ @config.logger.info('Starting connect_stream thread ...')
78
82
  new_status = connect_stream(latch)
79
83
  push_status(new_status)
80
- @config.logger.info('connect_stream thread finished.') if @config.debug_enabled
84
+ @config.logger.info('connect_stream thread finished.')
81
85
  end
82
86
  end
83
87
 
84
88
  def connect_stream(latch)
85
89
  return Constants::PUSH_NONRETRYABLE_ERROR unless socket_write(latch)
86
-
87
90
  while connected? || @first_event.value
88
- begin
89
- partial_data = @socket.readpartial(10_000, timeout: @read_timeout)
90
-
91
- read_first_event(partial_data, latch)
92
-
93
- raise 'eof exception' if partial_data == :eof
94
- rescue Errno::EBADF, IOError => e
95
- @config.logger.error(e.inspect) if @config.debug_enabled
96
- return nil
97
- rescue StandardError => e
98
- return nil if ENV['SPLITCLIENT_ENV'] == 'test'
99
-
100
- @config.logger.error("Error reading partial data: #{e.inspect}") if @config.debug_enabled
101
- return Constants::PUSH_RETRYABLE_ERROR
91
+ begin
92
+ if IO.select([@socket], nil, nil, @read_timeout)
93
+ begin
94
+ partial_data = @socket.readpartial(10_000)
95
+ read_first_event(partial_data, latch)
96
+
97
+ raise 'eof exception' if partial_data == :eof
98
+ rescue IO::WaitReadable => e
99
+ @config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") if @config.debug_enabled
100
+ IO.select([@socket], nil, nil, @read_timeout)
101
+ retry
102
+ rescue Errno::EAGAIN => e
103
+ @config.logger.debug("SSE client transient error: #{e.inspect}") if @config.debug_enabled
104
+ IO.select([@socket], nil, nil, @read_timeout)
105
+ retry
106
+ rescue Errno::ETIMEDOUT => e
107
+ @config.logger.error("SSE read operation timed out!: #{e.inspect}")
108
+ return Constants::PUSH_RETRYABLE_ERROR
109
+ rescue EOFError => e
110
+ @config.logger.error("SSE read operation EOF Exception!: #{e.inspect}")
111
+ raise 'eof exception'
112
+ rescue Errno::EBADF, IOError => e
113
+ @config.logger.error("SSE read operation EBADF or IOError: #{e.inspect}")
114
+ return Constants::PUSH_RETRYABLE_ERROR
115
+ rescue StandardError => e
116
+ @config.logger.error("SSE read operation StandardError: #{e.inspect}")
117
+ return nil if ENV['SPLITCLIENT_ENV'] == 'test'
118
+
119
+ @config.logger.error("Error reading partial data: #{e.inspect}")
120
+ return Constants::PUSH_RETRYABLE_ERROR
121
+ end
122
+ else
123
+ @config.logger.error("SSE read operation timed out, no data available.")
124
+ return Constants::PUSH_RETRYABLE_ERROR
125
+ end
126
+ rescue Errno::EBADF
127
+ @config.logger.debug("SSE socket is not connected (Errno::EBADF)") if @config.debug_enabled
128
+ break
129
+ rescue RuntimeError
130
+ raise 'eof exception'
131
+ rescue Exception => e
132
+ @config.logger.debug("SSE socket is not connected: #{e.inspect}") if @config.debug_enabled
133
+ break
102
134
  end
103
135
 
104
136
  process_data(partial_data)
105
137
  end
138
+ @config.logger.info("SSE read operation exited: #{connected?}")
139
+
106
140
  nil
107
141
  end
108
142
 
109
143
  def socket_write(latch)
110
144
  @first_event.make_true
111
145
  @socket = socket_connect
112
- @socket.write(build_request(@uri))
146
+ @socket.puts(build_request(@uri))
113
147
  true
114
148
  rescue StandardError => e
115
149
  @config.logger.error("Error during connecting to #{@uri.host}. Error: #{e.inspect}")
@@ -121,7 +155,7 @@ module SplitIoClient
121
155
  return unless @first_event.value
122
156
 
123
157
  response_code = @event_parser.first_event(data)
124
- @config.logger.debug("SSE client first event code: #{response_code}")
158
+ @config.logger.debug("SSE client first event code: #{response_code}") if @config.debug_enabled
125
159
 
126
160
  error_event = false
127
161
  events = @event_parser.parse(data)
@@ -130,6 +164,7 @@ module SplitIoClient
130
164
 
131
165
  if response_code == OK_CODE && !error_event
132
166
  @connected.make_true
167
+ @config.logger.debug("SSE client first event Connected is true") if @config.debug_enabled
133
168
  @telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::SSE_CONNECTION_ESTABLISHED, nil)
134
169
  push_status(Constants::PUSH_CONNECTED)
135
170
  end
@@ -138,15 +173,37 @@ module SplitIoClient
138
173
  end
139
174
 
140
175
  def socket_connect
141
- return Socketry::SSL::Socket.connect(@uri.host, @uri.port) if @uri.scheme.casecmp('https').zero?
176
+ tcp_socket = TCPSocket.new(@uri.host, @uri.port)
177
+ if @uri.scheme.casecmp('https').zero?
178
+ begin
179
+ ssl_context = OpenSSL::SSL::SSLContext.new
180
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
181
+ ssl_socket.hostname = @uri.host
182
+
183
+ begin
184
+ ssl_socket.connect_nonblock
185
+ rescue IO::WaitReadable
186
+ IO.select([ssl_socket])
187
+ retry
188
+ rescue IO::WaitWritable
189
+ IO.select(nil, [ssl_socket])
190
+ retry
191
+ end
192
+ return ssl_socket
193
+
194
+ rescue Exception => e
195
+ @config.logger.error("socket connect error: #{e.inspect}")
196
+ return nil
197
+ end
198
+ end
142
199
 
143
- Socketry::TCP::Socket.connect(@uri.host, @uri.port)
200
+ tcp_socket
144
201
  end
145
202
 
146
203
  def process_data(partial_data)
204
+ @config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
147
205
  return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE
148
206
 
149
- @config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
150
207
  events = @event_parser.parse(partial_data)
151
208
  events.each { |event| process_event(event) }
152
209
  rescue StandardError => e
@@ -197,7 +254,7 @@ module SplitIoClient
197
254
  def push_status(status)
198
255
  return if status.nil?
199
256
 
200
- @config.logger.debug("Pushing new sse status: #{status}")
257
+ @config.logger.debug("Pushing new sse status: #{status}") if @config.debug_enabled
201
258
  @status_queue.push(status)
202
259
  end
203
260
  end
@@ -29,14 +29,14 @@ module SplitIoClient
29
29
 
30
30
  events
31
31
  rescue StandardError => e
32
- @config.logger.debug("Error during parsing a event: #{e.inspect}")
32
+ @config.logger.debug("Error during parsing a event: #{e.inspect}") if @config.debug_enabled
33
33
  []
34
34
  end
35
35
 
36
36
  def first_event(raw_data)
37
37
  raw_data.split("\n")[0].split(' ')[1].to_i
38
38
  rescue StandardError => e
39
- @config.logger.debug("Error parsing first event: #{e.inspect}")
39
+ @config.logger.error("Error parsing first event: #{e.inspect}")
40
40
  BAD_REQUEST_CODE
41
41
  end
42
42
 
@@ -8,6 +8,7 @@ module SplitIoClient
8
8
  SPLIT_KILL = 'SPLIT_KILL'
9
9
  SEGMENT_UPDATE = 'SEGMENT_UPDATE'
10
10
  CONTROL = 'CONTROL'
11
+ RB_SEGMENT_UPDATE = 'RB_SEGMENT_UPDATE'
11
12
  end
12
13
  end
13
14
  end
@@ -42,12 +42,12 @@ module SplitIoClient
42
42
  @telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::STREAMING_STATUS, DISABLED)
43
43
  push_status(Constants::PUSH_SUBSYSTEM_OFF)
44
44
  else
45
- @config.logger.error("Incorrect event type: #{incoming_notification}")
45
+ @config.logger.error("Incorrect event type: #{incoming_notification}") if @config.debug_enabled
46
46
  end
47
47
  end
48
48
 
49
49
  def process_event_occupancy(channel, publishers)
50
- @config.logger.debug("Processed occupancy event with #{publishers} publishers. Channel: #{channel}")
50
+ @config.logger.debug("Processed occupancy event with #{publishers} publishers. Channel: #{channel}") if @config.debug_enabled
51
51
 
52
52
  update_publishers(channel, publishers)
53
53
 
@@ -76,7 +76,7 @@ module SplitIoClient
76
76
  end
77
77
 
78
78
  def push_status(status)
79
- @config.logger.debug("Pushing occupancy status: #{status}")
79
+ @config.logger.debug("Pushing occupancy status: #{status}") if @config.debug_enabled
80
80
  @status_queue.push(status)
81
81
  end
82
82
  end
@@ -13,6 +13,8 @@ module SplitIoClient
13
13
  case incoming_notification.data['type']
14
14
  when SSE::EventSource::EventTypes::SPLIT_UPDATE
15
15
  process_split_update(incoming_notification)
16
+ when SSE::EventSource::EventTypes::RB_SEGMENT_UPDATE
17
+ process_split_update(incoming_notification)
16
18
  when SSE::EventSource::EventTypes::SPLIT_KILL
17
19
  process_split_kill(incoming_notification)
18
20
  when SSE::EventSource::EventTypes::SEGMENT_UPDATE
@@ -25,7 +27,7 @@ module SplitIoClient
25
27
  private
26
28
 
27
29
  def process_split_update(notification)
28
- @config.logger.debug("SPLIT UPDATE notification received: #{notification}") if @config.debug_enabled
30
+ @config.logger.debug("#{notification.event_type} notification received: #{notification}") if @config.debug_enabled
29
31
  @splits_worker.add_to_queue(notification)
30
32
  end
31
33
 
@@ -14,13 +14,13 @@ module SplitIoClient
14
14
 
15
15
  def add_to_queue(change_number, segment_name)
16
16
  item = { change_number: change_number, segment_name: segment_name }
17
- @config.logger.debug("SegmentsWorker add to queue #{item}")
17
+ @config.logger.debug("SegmentsWorker add to queue #{item}") if @config.debug_enabled
18
18
  @queue.push(item)
19
19
  end
20
20
 
21
21
  def start
22
22
  if @running.value
23
- @config.logger.debug('segments worker already running.')
23
+ @config.logger.debug('segments worker already running.') if @config.debug_enabled
24
24
  return
25
25
  end
26
26
 
@@ -30,7 +30,7 @@ module SplitIoClient
30
30
 
31
31
  def stop
32
32
  unless @running.value
33
- @config.logger.debug('segments worker not running.')
33
+ @config.logger.debug('segments worker not running.') if @config.debug_enabled
34
34
  return
35
35
  end
36
36
 
@@ -44,7 +44,7 @@ module SplitIoClient
44
44
  while (item = @queue.pop)
45
45
  segment_name = item[:segment_name]
46
46
  cn = item[:change_number]
47
- @config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}")
47
+ @config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}") if @config.debug_enabled
48
48
 
49
49
  @synchronizer.fetch_segment(segment_name, cn)
50
50
  end
@@ -4,7 +4,8 @@ module SplitIoClient
4
4
  module SSE
5
5
  module Workers
6
6
  class SplitsWorker
7
- def initialize(synchronizer, config, feature_flags_repository, telemetry_runtime_producer, segment_fetcher)
7
+ def initialize(synchronizer, config, feature_flags_repository, telemetry_runtime_producer,
8
+ segment_fetcher, rule_based_segment_repository)
8
9
  @synchronizer = synchronizer
9
10
  @config = config
10
11
  @feature_flags_repository = feature_flags_repository
@@ -12,11 +13,12 @@ module SplitIoClient
12
13
  @running = Concurrent::AtomicBoolean.new(false)
13
14
  @telemetry_runtime_producer = telemetry_runtime_producer
14
15
  @segment_fetcher = segment_fetcher
16
+ @rule_based_segment_repository = rule_based_segment_repository
15
17
  end
16
18
 
17
19
  def start
18
20
  if @running.value
19
- @config.logger.debug('feature_flags_worker already running.')
21
+ @config.logger.debug('feature_flags_worker already running.') if @config.debug_enabled
20
22
  return
21
23
  end
22
24
 
@@ -26,7 +28,7 @@ module SplitIoClient
26
28
 
27
29
  def stop
28
30
  unless @running.value
29
- @config.logger.debug('feature_flags_worker not running.')
31
+ @config.logger.debug('feature_flags_worker not running.') if @config.debug_enabled
30
32
  return
31
33
  end
32
34
 
@@ -35,7 +37,7 @@ module SplitIoClient
35
37
  end
36
38
 
37
39
  def add_to_queue(notification)
38
- @config.logger.debug("feature_flags_worker add to queue #{notification.data['changeNumber']}")
40
+ @config.logger.debug("feature_flags_worker add to queue #{notification.data['changeNumber']}") if @config.debug_enabled
39
41
  @queue.push(notification)
40
42
  end
41
43
 
@@ -50,11 +52,14 @@ module SplitIoClient
50
52
 
51
53
  def perform
52
54
  while (notification = @queue.pop)
53
- @config.logger.debug("feature_flags_worker change_number dequeue #{notification.data['changeNumber']}")
55
+ @config.logger.debug("feature_flags_worker change_number dequeue #{notification.data['changeNumber']}") if @config.debug_enabled
54
56
  case notification.data['type']
55
57
  when SSE::EventSource::EventTypes::SPLIT_UPDATE
56
58
  success = update_feature_flag(notification)
57
- @synchronizer.fetch_splits(notification.data['changeNumber']) unless success
59
+ @synchronizer.fetch_splits(notification.data['changeNumber'], 0) unless success
60
+ when SSE::EventSource::EventTypes::RB_SEGMENT_UPDATE
61
+ success = update_rule_based_segment(notification)
62
+ @synchronizer.fetch_splits(0, notification.data['changeNumber']) unless success
58
63
  when SSE::EventSource::EventTypes::SPLIT_KILL
59
64
  kill_feature_flag(notification)
60
65
  end
@@ -65,11 +70,12 @@ module SplitIoClient
65
70
  return true if @feature_flags_repository.get_change_number.to_i >= notification.data['changeNumber']
66
71
  return false unless !notification.data['d'].nil? && @feature_flags_repository.get_change_number == notification.data['pcn']
67
72
 
68
- new_split = return_split_from_json(notification)
69
- SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@feature_flags_repository,
70
- [new_split],
71
- notification.data['changeNumber'], @config)
72
- fetch_segments_if_not_exists(new_split)
73
+ new_split = update_feature_flag_repository(notification)
74
+ fetch_segments_if_not_exists(Helpers::Util.segment_names_by_object(new_split, 'IN_SEGMENT'), @feature_flags_repository)
75
+ if fetch_rule_based_segments_if_not_exists(Helpers::Util.segment_names_by_object(new_split, 'IN_RULE_BASED_SEGMENT'),
76
+ notification.data['changeNumber'])
77
+ return true
78
+ end
73
79
 
74
80
  @telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS)
75
81
 
@@ -80,30 +86,63 @@ module SplitIoClient
80
86
  false
81
87
  end
82
88
 
89
+ def update_feature_flag_repository(notification)
90
+ new_split = return_object_from_json(notification)
91
+ SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@feature_flags_repository, [new_split],
92
+ notification.data['changeNumber'], @config, false)
93
+ new_split
94
+ end
95
+
96
+ def update_rule_based_segment(notification)
97
+ return true if @rule_based_segment_repository.get_change_number.to_i >= notification.data['changeNumber']
98
+ return false unless !notification.data['d'].nil? &&
99
+ @rule_based_segment_repository.get_change_number == notification.data['pcn']
100
+
101
+ new_rb_segment = return_object_from_json(notification)
102
+ SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segment_repository,
103
+ [new_rb_segment],
104
+ notification.data['changeNumber'], @config)
105
+ fetch_segments_if_not_exists(Helpers::Util.segment_names_in_rb_segment(new_rb_segment, 'IN_SEGMENT'),
106
+ @rule_based_segment_repository)
107
+
108
+ # @telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS)
109
+
110
+ true
111
+ rescue StandardError => e
112
+ @config.logger.debug("Failed to update Split: #{e.inspect}") if @config.debug_enabled
113
+
114
+ false
115
+ end
116
+
83
117
  def kill_feature_flag(notification)
84
118
  return if @feature_flags_repository.get_change_number.to_i > notification.data['changeNumber']
85
119
 
86
- @config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}")
87
- @feature_flags_repository.kill(
88
- notification.data['changeNumber'],
89
- notification.data['splitName'],
90
- notification.data['defaultTreatment']
91
- )
92
- @synchronizer.fetch_splits(notification.data['changeNumber'])
120
+ @config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}") if @config.debug_enabled
121
+ @feature_flags_repository.kill(notification.data['changeNumber'],
122
+ notification.data['splitName'],
123
+ notification.data['defaultTreatment'])
124
+ @synchronizer.fetch_splits(notification.data['changeNumber'], 0)
93
125
  end
94
126
 
95
- def return_split_from_json(notification)
96
- split_json = Helpers::DecryptionHelper.get_encoded_definition(notification.data['c'], notification.data['d'])
97
- JSON.parse(split_json, symbolize_names: true)
127
+ def return_object_from_json(notification)
128
+ object_json = Helpers::DecryptionHelper.get_encoded_definition(notification.data['c'], notification.data['d'])
129
+ JSON.parse(object_json, symbolize_names: true)
98
130
  end
99
131
 
100
- def fetch_segments_if_not_exists(feature_flag)
101
- segment_names = Helpers::Util.segment_names_by_feature_flag(feature_flag)
132
+ def fetch_segments_if_not_exists(segment_names, object_repository)
102
133
  return if segment_names.nil?
103
134
 
104
- @feature_flags_repository.set_segment_names(segment_names)
135
+ object_repository.set_segment_names(segment_names)
105
136
  @segment_fetcher.fetch_segments_if_not_exists(segment_names)
106
137
  end
138
+
139
+ def fetch_rule_based_segments_if_not_exists(segment_names, change_number)
140
+ return false if segment_names.nil? || segment_names.empty? || @rule_based_segment_repository.contains?(segment_names.to_a)
141
+
142
+ @synchronizer.fetch_splits(0, change_number)
143
+
144
+ true
145
+ end
107
146
  end
108
147
  end
109
148
  end
@@ -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
@@ -55,8 +57,12 @@ module SplitIoClient
55
57
  end
56
58
  without_nil = Array.new
57
59
  flag_sets.each { |flag_set|
58
- without_nil.push(flag_set) if !flag_set.nil?
59
- log_nil("flag set", method) if flag_set.nil?
60
+ if !flag_set.nil?
61
+ without_nil.push(flag_set)
62
+ next
63
+ end
64
+
65
+ log_nil("flag set", method)
60
66
  }
61
67
  if without_nil.length() == 0
62
68
  log_invalid_flag_set_type(method)
@@ -68,7 +74,7 @@ module SplitIoClient
68
74
  log_invalid_flag_set_type(method)
69
75
  elsif flag_set.is_a?(String) && flag_set.empty?
70
76
  log_invalid_flag_set_type(method)
71
- elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method)
77
+ elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method, Flagset_regex, :log_invalid_match)
72
78
  valid_flag_sets.add(flag_set.strip.downcase)
73
79
  else
74
80
  log_invalid_flag_set_type(method)
@@ -77,6 +83,46 @@ module SplitIoClient
77
83
  !valid_flag_sets.empty? ? valid_flag_sets.to_a.sort : []
78
84
  end
79
85
 
86
+ def validate_fallback_treatment(method, fallback_treatment)
87
+ if !fallback_treatment.is_a? Engine::Models::FallbackTreatment
88
+ @config.logger.warn("#{method}: Fallback treatment instance should be FallbackTreatment, input is discarded")
89
+ return false
90
+ end
91
+
92
+ if !fallback_treatment.treatment.is_a? String
93
+ @config.logger.warn("#{method}: Fallback treatment value should be str type, input is discarded")
94
+ return false
95
+ end
96
+
97
+ return false unless string_match?(fallback_treatment.treatment, method, Fallback_treatment_regex, :log_invalid_fallback_treatment)
98
+
99
+ if fallback_treatment.treatment.size > Fallback_treatment_size
100
+ @config.logger.warn("#{method}: Fallback treatment size should not exceed #{Fallback_treatment_size} characters")
101
+ return false
102
+ end
103
+
104
+ true
105
+ end
106
+
107
+ def valid_split_name?(method, split_name)
108
+ if split_name.nil?
109
+ log_nil(:split_name, method)
110
+ return false
111
+ end
112
+
113
+ unless string?(split_name)
114
+ log_invalid_type(:split_name, method)
115
+ return false
116
+ end
117
+
118
+ if empty_string?(split_name)
119
+ log_empty_string(:split_name, method)
120
+ return false
121
+ end
122
+
123
+ true
124
+ end
125
+
80
126
  private
81
127
 
82
128
  def string?(value)
@@ -91,9 +137,9 @@ module SplitIoClient
91
137
  (value.is_a?(Numeric) && !value.to_f.nan?) || string?(value)
92
138
  end
93
139
 
94
- def string_match?(value, method)
95
- if Flagset_regex.match(value) == nil
96
- log_invalid_match(value, method)
140
+ def string_match?(value, method, regex_exp, log_if_invalid)
141
+ if regex_exp.match(value) == nil
142
+ method(log_if_invalid).call(value, method)
97
143
  false
98
144
  else
99
145
  true
@@ -132,25 +178,6 @@ module SplitIoClient
132
178
  @config.logger.error("#{method}: #{key} is too long - must be #{@config.max_key_size} characters or less")
133
179
  end
134
180
 
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
181
  def valid_key?(method, key)
155
182
  if key.nil?
156
183
  log_nil(:key, method)
@@ -326,5 +353,9 @@ module SplitIoClient
326
353
 
327
354
  true
328
355
  end
356
+
357
+ def log_invalid_fallback_treatment(key, method)
358
+ @config.logger.warn("#{method}: Invalid treatment #{key}, Fallback treatment should match regex #{Fallback_treatment_regex}")
359
+ end
329
360
  end
330
361
  end
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '8.4.0'
2
+ VERSION = '8.11.0'
3
3
  end