launchdarkly-server-sdk 8.11.2 → 8.11.3

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ldclient-rb/config.rb +66 -3
  3. data/lib/ldclient-rb/context.rb +1 -1
  4. data/lib/ldclient-rb/data_system.rb +243 -0
  5. data/lib/ldclient-rb/events.rb +34 -19
  6. data/lib/ldclient-rb/flags_state.rb +1 -1
  7. data/lib/ldclient-rb/impl/big_segments.rb +4 -4
  8. data/lib/ldclient-rb/impl/cache_store.rb +44 -0
  9. data/lib/ldclient-rb/impl/data_source/polling.rb +108 -0
  10. data/lib/ldclient-rb/impl/data_source/requestor.rb +106 -0
  11. data/lib/ldclient-rb/impl/data_source/status_provider.rb +78 -0
  12. data/lib/ldclient-rb/impl/data_source/stream.rb +198 -0
  13. data/lib/ldclient-rb/impl/data_source.rb +3 -3
  14. data/lib/ldclient-rb/impl/data_store/data_kind.rb +108 -0
  15. data/lib/ldclient-rb/impl/data_store/feature_store_client_wrapper.rb +187 -0
  16. data/lib/ldclient-rb/impl/data_store/in_memory_feature_store.rb +130 -0
  17. data/lib/ldclient-rb/impl/data_store/status_provider.rb +82 -0
  18. data/lib/ldclient-rb/impl/data_store/store.rb +371 -0
  19. data/lib/ldclient-rb/impl/data_store.rb +11 -97
  20. data/lib/ldclient-rb/impl/data_system/fdv1.rb +20 -7
  21. data/lib/ldclient-rb/impl/data_system/fdv2.rb +471 -0
  22. data/lib/ldclient-rb/impl/data_system/polling.rb +601 -0
  23. data/lib/ldclient-rb/impl/data_system/protocolv2.rb +264 -0
  24. data/lib/ldclient-rb/impl/dependency_tracker.rb +21 -9
  25. data/lib/ldclient-rb/impl/evaluator.rb +3 -2
  26. data/lib/ldclient-rb/impl/event_sender.rb +4 -3
  27. data/lib/ldclient-rb/impl/expiring_cache.rb +79 -0
  28. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
  29. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +288 -0
  30. data/lib/ldclient-rb/impl/memoized_value.rb +34 -0
  31. data/lib/ldclient-rb/impl/migrations/migrator.rb +2 -1
  32. data/lib/ldclient-rb/impl/migrations/tracker.rb +2 -1
  33. data/lib/ldclient-rb/impl/model/serialization.rb +6 -6
  34. data/lib/ldclient-rb/impl/non_blocking_thread_pool.rb +48 -0
  35. data/lib/ldclient-rb/impl/repeating_task.rb +2 -2
  36. data/lib/ldclient-rb/impl/simple_lru_cache.rb +27 -0
  37. data/lib/ldclient-rb/impl/util.rb +65 -0
  38. data/lib/ldclient-rb/impl.rb +1 -2
  39. data/lib/ldclient-rb/in_memory_store.rb +1 -18
  40. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +9 -9
  41. data/lib/ldclient-rb/integrations/test_data.rb +11 -11
  42. data/lib/ldclient-rb/integrations/test_data_v2/flag_builder_v2.rb +582 -0
  43. data/lib/ldclient-rb/integrations/test_data_v2.rb +248 -0
  44. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +3 -2
  45. data/lib/ldclient-rb/interfaces/data_system.rb +755 -0
  46. data/lib/ldclient-rb/interfaces/feature_store.rb +3 -0
  47. data/lib/ldclient-rb/ldclient.rb +55 -131
  48. data/lib/ldclient-rb/util.rb +11 -70
  49. data/lib/ldclient-rb/version.rb +1 -1
  50. data/lib/ldclient-rb.rb +8 -17
  51. metadata +35 -17
  52. data/lib/ldclient-rb/cache_store.rb +0 -45
  53. data/lib/ldclient-rb/expiring_cache.rb +0 -77
  54. data/lib/ldclient-rb/memoized_value.rb +0 -32
  55. data/lib/ldclient-rb/non_blocking_thread_pool.rb +0 -46
  56. data/lib/ldclient-rb/polling.rb +0 -102
  57. data/lib/ldclient-rb/requestor.rb +0 -102
  58. data/lib/ldclient-rb/simple_lru_cache.rb +0 -25
  59. data/lib/ldclient-rb/stream.rb +0 -197
@@ -0,0 +1,288 @@
1
+ require 'concurrent/atomics'
2
+ require 'ldclient-rb/impl/data_system'
3
+ require 'ldclient-rb/interfaces/data_system'
4
+ require 'ldclient-rb/util'
5
+ require 'thread'
6
+
7
+ module LaunchDarkly
8
+ module Impl
9
+ module Integrations
10
+ module TestData
11
+ #
12
+ # Internal implementation of both Initializer and Synchronizer protocols for TestDataV2.
13
+ #
14
+ # This component bridges the test data management in TestDataV2 with the FDv2 protocol
15
+ # interfaces. Each instance implements both Initializer and Synchronizer protocols
16
+ # and receives change notifications for dynamic updates.
17
+ #
18
+ class TestDataSourceV2
19
+ include LaunchDarkly::Interfaces::DataSystem::Initializer
20
+ include LaunchDarkly::Interfaces::DataSystem::Synchronizer
21
+
22
+ # @api private
23
+ #
24
+ # @param test_data [LaunchDarkly::Integrations::TestDataV2] the test data instance
25
+ #
26
+ def initialize(test_data)
27
+ @test_data = test_data
28
+ @closed = false
29
+ @update_queue = Queue.new
30
+ @lock = Mutex.new
31
+
32
+ # Always register for change notifications
33
+ @test_data.add_instance(self)
34
+ end
35
+
36
+ #
37
+ # Return the name of this data source.
38
+ #
39
+ # @return [String]
40
+ #
41
+ def name
42
+ 'TestDataV2'
43
+ end
44
+
45
+ #
46
+ # Implementation of the Initializer.fetch method.
47
+ #
48
+ # Returns the current test data as a Basis for initial data loading.
49
+ #
50
+ # @param selector_store [LaunchDarkly::Interfaces::DataSystem::SelectorStore] Provides the Selector (unused for test data)
51
+ # @return [LaunchDarkly::Result] A Result containing either a Basis or an error message
52
+ #
53
+ def fetch(selector_store)
54
+ begin
55
+ @lock.synchronize do
56
+ if @closed
57
+ return LaunchDarkly::Result.fail('TestDataV2 source has been closed')
58
+ end
59
+
60
+ # Get all current flags and segments from test data
61
+ init_data = @test_data.make_init_data
62
+ version = @test_data.get_version
63
+
64
+ # Build a full transfer changeset
65
+ builder = LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new
66
+ builder.start(LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL)
67
+
68
+ # Add all flags to the changeset
69
+ init_data[:flags].each do |key, flag_data|
70
+ builder.add_put(
71
+ LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG,
72
+ key,
73
+ flag_data[:version] || 1,
74
+ flag_data
75
+ )
76
+ end
77
+
78
+ # Add all segments to the changeset
79
+ init_data[:segments].each do |key, segment_data|
80
+ builder.add_put(
81
+ LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT,
82
+ key,
83
+ segment_data[:version] || 1,
84
+ segment_data
85
+ )
86
+ end
87
+
88
+ # Create selector for this version
89
+ selector = LaunchDarkly::Interfaces::DataSystem::Selector.new_selector(version.to_s, version)
90
+ change_set = builder.finish(selector)
91
+
92
+ basis = LaunchDarkly::Interfaces::DataSystem::Basis.new(change_set: change_set, persist: false, environment_id: nil)
93
+
94
+ LaunchDarkly::Result.success(basis)
95
+ end
96
+ rescue => e
97
+ LaunchDarkly::Result.fail("Error fetching test data: #{e.message}", e)
98
+ end
99
+ end
100
+
101
+ #
102
+ # Implementation of the Synchronizer.sync method.
103
+ #
104
+ # Yields updates as test data changes occur.
105
+ #
106
+ # @param selector_store [LaunchDarkly::Interfaces::DataSystem::SelectorStore] Provides the Selector (unused for test data)
107
+ # @yield [LaunchDarkly::Interfaces::DataSystem::Update] Yields Update objects as synchronization progresses
108
+ # @return [void]
109
+ #
110
+ def sync(selector_store)
111
+ # First yield initial data
112
+ initial_result = fetch(selector_store)
113
+ unless initial_result.success?
114
+ yield LaunchDarkly::Interfaces::DataSystem::Update.new(
115
+ state: LaunchDarkly::Interfaces::DataSource::Status::OFF,
116
+ error: LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
117
+ LaunchDarkly::Interfaces::DataSource::ErrorInfo::STORE_ERROR,
118
+ 0,
119
+ initial_result.error,
120
+ Time.now
121
+ )
122
+ )
123
+ return
124
+ end
125
+
126
+ # Yield the initial successful state
127
+ yield LaunchDarkly::Interfaces::DataSystem::Update.new(
128
+ state: LaunchDarkly::Interfaces::DataSource::Status::VALID,
129
+ change_set: initial_result.value.change_set
130
+ )
131
+
132
+ # Continue yielding updates as they arrive
133
+ until @closed
134
+ begin
135
+ # stop() will push nil to the queue to wake us up when shutting down
136
+ update = @update_queue.pop
137
+
138
+ # Handle nil sentinel for shutdown
139
+ break if update.nil?
140
+
141
+ # Yield the actual update
142
+ yield update
143
+ rescue => e
144
+ yield LaunchDarkly::Interfaces::DataSystem::Update.new(
145
+ state: LaunchDarkly::Interfaces::DataSource::Status::OFF,
146
+ error: LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
147
+ LaunchDarkly::Interfaces::DataSource::ErrorInfo::UNKNOWN,
148
+ 0,
149
+ "Error in test data synchronizer: #{e.message}",
150
+ Time.now
151
+ )
152
+ )
153
+ break
154
+ end
155
+ end
156
+ end
157
+
158
+ #
159
+ # Stop the data source and clean up resources
160
+ #
161
+ # @return [void]
162
+ #
163
+ def stop
164
+ @lock.synchronize do
165
+ return if @closed
166
+ @closed = true
167
+ end
168
+
169
+ @test_data.closed_instance(self)
170
+ # Signal shutdown to sync generator
171
+ @update_queue.push(nil)
172
+ end
173
+
174
+ #
175
+ # Called by TestDataV2 when a flag is updated.
176
+ #
177
+ # This method converts the flag update into an FDv2 changeset and
178
+ # queues it for delivery through the sync() generator.
179
+ #
180
+ # @param flag_data [Hash] the flag data
181
+ # @return [void]
182
+ #
183
+ def upsert_flag(flag_data)
184
+ @lock.synchronize do
185
+ return if @closed
186
+
187
+ begin
188
+ version = @test_data.get_version
189
+
190
+ # Build a changes transfer changeset
191
+ builder = LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new
192
+ builder.start(LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_CHANGES)
193
+
194
+ # Add the updated flag
195
+ builder.add_put(
196
+ LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG,
197
+ flag_data[:key],
198
+ flag_data[:version] || 1,
199
+ flag_data
200
+ )
201
+
202
+ # Create selector for this version
203
+ selector = LaunchDarkly::Interfaces::DataSystem::Selector.new_selector(version.to_s, version)
204
+ change_set = builder.finish(selector)
205
+
206
+ # Queue the update
207
+ update = LaunchDarkly::Interfaces::DataSystem::Update.new(
208
+ state: LaunchDarkly::Interfaces::DataSource::Status::VALID,
209
+ change_set: change_set
210
+ )
211
+
212
+ @update_queue.push(update)
213
+ rescue => e
214
+ # Queue an error update
215
+ error_update = LaunchDarkly::Interfaces::DataSystem::Update.new(
216
+ state: LaunchDarkly::Interfaces::DataSource::Status::OFF,
217
+ error: LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
218
+ LaunchDarkly::Interfaces::DataSource::ErrorInfo::STORE_ERROR,
219
+ 0,
220
+ "Error processing flag update: #{e.message}",
221
+ Time.now
222
+ )
223
+ )
224
+ @update_queue.push(error_update)
225
+ end
226
+ end
227
+ end
228
+
229
+ #
230
+ # Called by TestDataV2 when a segment is updated.
231
+ #
232
+ # This method converts the segment update into an FDv2 changeset and
233
+ # queues it for delivery through the sync() generator.
234
+ #
235
+ # @param segment_data [Hash] the segment data
236
+ # @return [void]
237
+ #
238
+ def upsert_segment(segment_data)
239
+ @lock.synchronize do
240
+ return if @closed
241
+
242
+ begin
243
+ version = @test_data.get_version
244
+
245
+ # Build a changes transfer changeset
246
+ builder = LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new
247
+ builder.start(LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_CHANGES)
248
+
249
+ # Add the updated segment
250
+ builder.add_put(
251
+ LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT,
252
+ segment_data[:key],
253
+ segment_data[:version] || 1,
254
+ segment_data
255
+ )
256
+
257
+ # Create selector for this version
258
+ selector = LaunchDarkly::Interfaces::DataSystem::Selector.new_selector(version.to_s, version)
259
+ change_set = builder.finish(selector)
260
+
261
+ # Queue the update
262
+ update = LaunchDarkly::Interfaces::DataSystem::Update.new(
263
+ state: LaunchDarkly::Interfaces::DataSource::Status::VALID,
264
+ change_set: change_set
265
+ )
266
+
267
+ @update_queue.push(update)
268
+ rescue => e
269
+ # Queue an error update
270
+ error_update = LaunchDarkly::Interfaces::DataSystem::Update.new(
271
+ state: LaunchDarkly::Interfaces::DataSource::Status::OFF,
272
+ error: LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
273
+ LaunchDarkly::Interfaces::DataSource::ErrorInfo::STORE_ERROR,
274
+ 0,
275
+ "Error processing segment update: #{e.message}",
276
+ Time.now
277
+ )
278
+ )
279
+ @update_queue.push(error_update)
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
288
+
@@ -0,0 +1,34 @@
1
+
2
+ module LaunchDarkly
3
+ module Impl
4
+ # Simple implementation of a thread-safe memoized value whose generator function will never be
5
+ # run more than once, and whose value can be overridden by explicit assignment.
6
+ # Note that we no longer use this class and it will be removed in a future version.
7
+ class MemoizedValue
8
+ def initialize(&generator)
9
+ @generator = generator
10
+ @mutex = Mutex.new
11
+ @inited = false
12
+ @value = nil
13
+ end
14
+
15
+ def get
16
+ @mutex.synchronize do
17
+ unless @inited
18
+ @value = @generator.call
19
+ @inited = true
20
+ end
21
+ end
22
+ @value
23
+ end
24
+
25
+ def set(value)
26
+ @mutex.synchronize do
27
+ @value = value
28
+ @inited = true
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -1,4 +1,5 @@
1
1
  require 'thread'
2
+ require 'ldclient-rb/impl/util'
2
3
 
3
4
  module LaunchDarkly
4
5
  module Impl
@@ -274,7 +275,7 @@ module LaunchDarkly
274
275
  begin
275
276
  result = @fn.call(@payload)
276
277
  rescue => e
277
- LaunchDarkly::Util.log_exception(@logger, "Unexpected error running method for '#{origin}' origin", e)
278
+ Impl::Util.log_exception(@logger, "Unexpected error running method for '#{origin}' origin", e)
278
279
  result = LaunchDarkly::Result.fail("'#{origin}' operation raised an exception", e)
279
280
  end
280
281
 
@@ -1,5 +1,6 @@
1
1
  require "set"
2
2
  require "ldclient-rb/impl/sampler"
3
+ require "ldclient-rb/impl/util"
3
4
  require "logger"
4
5
 
5
6
  module LaunchDarkly
@@ -67,7 +68,7 @@ module LaunchDarkly
67
68
  begin
68
69
  @consistent = is_consistent.call
69
70
  rescue => e
70
- LaunchDarkly::Util.log_exception(@logger, "Exception raised during consistency check; failed to record measurement", e)
71
+ Impl::Util.log_exception(@logger, "Exception raised during consistency check; failed to record measurement", e)
71
72
  end
72
73
  end
73
74
  end
@@ -34,19 +34,19 @@ module LaunchDarkly
34
34
  # SDK code outside of Impl::Model should use this method instead of calling the model class
35
35
  # constructors directly, so as not to rely on implementation details.
36
36
  #
37
- # @param kind [Hash] normally either FEATURES or SEGMENTS
37
+ # @param kind [Hash] normally either Impl::DataStore::FEATURES or Impl::DataStore::SEGMENTS
38
38
  # @param input [object] a JSON string or a parsed hash (or a data model object, in which case
39
39
  # we'll just return the original object)
40
40
  # @param logger [Logger|nil] logs errors if there are any data validation problems
41
41
  # @return [Object] the flag or segment (or, for an unknown data kind, the data as a hash)
42
42
  def self.deserialize(kind, input, logger = nil)
43
43
  return nil if input.nil?
44
- return input if !input.is_a?(String) && !input.is_a?(Hash)
44
+ return input unless input.is_a?(String) || input.is_a?(Hash)
45
45
  data = input.is_a?(Hash) ? input : JSON.parse(input, symbolize_names: true)
46
46
  case kind
47
- when FEATURES
47
+ when Impl::DataStore::FEATURES
48
48
  FeatureFlag.new(data, logger)
49
- when SEGMENTS
49
+ when Impl::DataStore::SEGMENTS
50
50
  Segment.new(data, logger)
51
51
  else
52
52
  data
@@ -63,8 +63,8 @@ module LaunchDarkly
63
63
  # Translates a { flags: ..., segments: ... } object received from LaunchDarkly to the data store format.
64
64
  def self.make_all_store_data(received_data, logger = nil)
65
65
  {
66
- FEATURES => (received_data[:flags] || {}).transform_values { |data| FeatureFlag.new(data, logger) },
67
- SEGMENTS => (received_data[:segments] || {}).transform_values { |data| Segment.new(data, logger) },
66
+ Impl::DataStore::FEATURES => (received_data[:flags] || {}).transform_values { |data| FeatureFlag.new(data, logger) },
67
+ Impl::DataStore::SEGMENTS => (received_data[:segments] || {}).transform_values { |data| Segment.new(data, logger) },
68
68
  }
69
69
  end
70
70
  end
@@ -0,0 +1,48 @@
1
+ require "concurrent"
2
+ require "concurrent/atomics"
3
+ require "concurrent/executors"
4
+ require "thread"
5
+
6
+ module LaunchDarkly
7
+ module Impl
8
+ # Simple wrapper for a FixedThreadPool that rejects new jobs if all the threads are busy, rather
9
+ # than blocking. Also provides a way to wait for all jobs to finish without shutting down.
10
+ class NonBlockingThreadPool
11
+ def initialize(capacity, name = 'LD/NonBlockingThreadPool')
12
+ @capacity = capacity
13
+ @pool = Concurrent::FixedThreadPool.new(capacity, name: name)
14
+ @semaphore = Concurrent::Semaphore.new(capacity)
15
+ end
16
+
17
+ # Attempts to submit a job, but only if a worker is available. Unlike the regular post method,
18
+ # this returns a value: true if the job was submitted, false if all workers are busy.
19
+ def post
20
+ unless @semaphore.try_acquire(1)
21
+ return
22
+ end
23
+ @pool.post do
24
+ begin
25
+ yield
26
+ ensure
27
+ @semaphore.release(1)
28
+ end
29
+ end
30
+ end
31
+
32
+ # Waits until no jobs are executing, without shutting down the pool.
33
+ def wait_all
34
+ @semaphore.acquire(@capacity)
35
+ @semaphore.release(@capacity)
36
+ end
37
+
38
+ def shutdown
39
+ @pool.shutdown
40
+ end
41
+
42
+ def wait_for_termination
43
+ @pool.wait_for_termination
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -1,4 +1,4 @@
1
- require "ldclient-rb/util"
1
+ require "ldclient-rb/impl/util"
2
2
 
3
3
  require "concurrent/atomics"
4
4
 
@@ -26,7 +26,7 @@ module LaunchDarkly
26
26
  begin
27
27
  @task.call
28
28
  rescue => e
29
- LaunchDarkly::Util.log_exception(@logger, "Uncaught exception from repeating task", e)
29
+ Impl::Util.log_exception(@logger, "Uncaught exception from repeating task", e)
30
30
  end
31
31
  delta = @interval - (Time.now - started_at)
32
32
  if delta > 0
@@ -0,0 +1,27 @@
1
+
2
+ module LaunchDarkly
3
+ module Impl
4
+ # A non-thread-safe implementation of a LRU cache set with only add and reset methods.
5
+ # Based on https://github.com/SamSaffron/lru_redux/blob/master/lib/lru_redux/cache.rb
6
+ class SimpleLRUCacheSet
7
+ def initialize(capacity)
8
+ @values = {}
9
+ @capacity = capacity
10
+ end
11
+
12
+ # Adds a value to the cache or marks it recent if it was already there. Returns true if already there.
13
+ def add(value)
14
+ found = true
15
+ @values.delete(value) { found = false }
16
+ @values[value] = true
17
+ @values.shift if @values.length > @capacity
18
+ found
19
+ end
20
+
21
+ def clear
22
+ @values = {}
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -1,3 +1,6 @@
1
+ require "uri"
2
+ require "http"
3
+
1
4
  module LaunchDarkly
2
5
  module Impl
3
6
  module Util
@@ -93,6 +96,68 @@ module LaunchDarkly
93
96
  }
94
97
  nil
95
98
  end
99
+
100
+ #
101
+ # Append the payload filter key query parameter to the provided URI.
102
+ #
103
+ # @param uri [String]
104
+ # @param config [Config]
105
+ # @return [String]
106
+ #
107
+ def self.add_payload_filter_key(uri, config)
108
+ return uri if config.payload_filter_key.nil?
109
+
110
+ begin
111
+ parsed = URI.parse(uri)
112
+ new_query_params = URI.decode_www_form(String(parsed.query)) << ["filter", config.payload_filter_key]
113
+ parsed.query = URI.encode_www_form(new_query_params)
114
+ parsed.to_s
115
+ rescue URI::InvalidURIError
116
+ config.logger.warn { "[LDClient] URI could not be parsed. No filtering will be applied." }
117
+ uri
118
+ end
119
+ end
120
+
121
+ def self.new_http_client(uri_s, config)
122
+ http_client_options = {}
123
+ if config.socket_factory
124
+ http_client_options["socket_class"] = config.socket_factory
125
+ end
126
+ proxy = URI.parse(uri_s).find_proxy
127
+ unless proxy.nil?
128
+ http_client_options["proxy"] = {
129
+ proxy_address: proxy.host,
130
+ proxy_port: proxy.port,
131
+ proxy_username: proxy.user,
132
+ proxy_password: proxy.password,
133
+ }
134
+ end
135
+ HTTP::Client.new(http_client_options)
136
+ .timeout({
137
+ read: config.read_timeout,
138
+ connect: config.connect_timeout,
139
+ })
140
+ .persistent(uri_s)
141
+ end
142
+
143
+ def self.log_exception(logger, message, exc)
144
+ logger.error { "[LDClient] #{message}: #{exc.inspect}" }
145
+ logger.debug { "[LDClient] Exception trace: #{exc.backtrace}" }
146
+ end
147
+
148
+ def self.http_error_recoverable?(status)
149
+ if status >= 400 && status < 500
150
+ status == 400 || status == 408 || status == 429
151
+ else
152
+ true
153
+ end
154
+ end
155
+
156
+ def self.http_error_message(status, context, recoverable_message)
157
+ desc = (status == 401 || status == 403) ? " (invalid SDK key)" : ""
158
+ message = http_error_recoverable?(status) ? recoverable_message : "giving up permanently"
159
+ "HTTP error #{status}#{desc} for #{context} - #{message}"
160
+ end
96
161
  end
97
162
  end
98
163
  end
@@ -5,8 +5,7 @@ module LaunchDarkly
5
5
  # and subject to change.
6
6
  #
7
7
  # @since 5.5.0
8
- # @private
9
- #
8
+ # @api private
10
9
  module Impl
11
10
  # code is in ldclient-rb/impl/
12
11
  end
@@ -1,24 +1,7 @@
1
1
  require "concurrent/atomics"
2
+ require "ldclient-rb/impl/data_store"
2
3
 
3
4
  module LaunchDarkly
4
-
5
- # These constants denote the types of data that can be stored in the feature store. If
6
- # we add another storable data type in the future, as long as it follows the same pattern
7
- # (having "key", "version", and "deleted" properties), we only need to add a corresponding
8
- # constant here and the existing store should be able to handle it.
9
- #
10
- # The :priority and :get_dependency_keys properties are used by FeatureStoreDataSetSorter
11
- # to ensure data consistency during non-atomic updates.
12
-
13
- # @private
14
- FEATURES = Impl::DataStore::DataKind.new(namespace: "features", priority: 1).freeze
15
-
16
- # @private
17
- SEGMENTS = Impl::DataStore::DataKind.new(namespace: "segments", priority: 0).freeze
18
-
19
- # @private
20
- ALL_KINDS = [FEATURES, SEGMENTS].freeze
21
-
22
5
  #
23
6
  # Default implementation of the LaunchDarkly client's feature store, using an in-memory
24
7
  # cache. This object holds feature flags and related data received from LaunchDarkly.
@@ -12,14 +12,14 @@ module LaunchDarkly
12
12
  class FlagBuilder
13
13
  attr_reader :key
14
14
 
15
- # @private
15
+ # @api private
16
16
  def initialize(key)
17
17
  @key = key
18
18
  @on = true
19
19
  @variations = []
20
20
  end
21
21
 
22
- # @private
22
+ # @api private
23
23
  def initialize_copy(other)
24
24
  super(other)
25
25
  @variations = @variations.clone
@@ -357,7 +357,7 @@ module LaunchDarkly
357
357
  self
358
358
  end
359
359
 
360
- # @private
360
+ # @api private
361
361
  def add_rule(rule)
362
362
  if @rules.nil?
363
363
  @rules = Array.new
@@ -386,7 +386,7 @@ module LaunchDarkly
386
386
  end
387
387
  end
388
388
 
389
- # @private
389
+ # @api private
390
390
  def build(version)
391
391
  res = { key: @key,
392
392
  version: version,
@@ -486,16 +486,16 @@ module LaunchDarkly
486
486
  # Finally, call {#then_return} to finish defining the rule.
487
487
  #
488
488
  class FlagRuleBuilder
489
- # @private
489
+ # @api private
490
490
  FlagRuleClause = Struct.new(:contextKind, :attribute, :op, :values, :negate, keyword_init: true) # rubocop:disable Naming/MethodName:
491
491
 
492
- # @private
492
+ # @api private
493
493
  def initialize(flag_builder)
494
494
  @flag_builder = flag_builder
495
495
  @clauses = Array.new
496
496
  end
497
497
 
498
- # @private
498
+ # @api private
499
499
  def intialize_copy(other)
500
500
  super(other)
501
501
  @clauses = @clauses.clone
@@ -612,7 +612,7 @@ module LaunchDarkly
612
612
  end
613
613
  end
614
614
 
615
- # @private
615
+ # @api private
616
616
  def build(ri)
617
617
  {
618
618
  id: 'rule' + ri.to_s,
@@ -622,7 +622,7 @@ module LaunchDarkly
622
622
  end
623
623
  end
624
624
 
625
- # @private
625
+ # @api private
626
626
  def variation_for_boolean(variation)
627
627
  variation ? TRUE_VARIATION_INDEX : FALSE_VARIATION_INDEX
628
628
  end