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,248 @@
1
+ require 'ldclient-rb/impl/integrations/test_data/test_data_source_v2'
2
+ require 'ldclient-rb/impl/model/feature_flag'
3
+ require 'ldclient-rb/integrations/test_data_v2/flag_builder_v2'
4
+ require 'concurrent/atomics'
5
+
6
+ module LaunchDarkly
7
+ module Integrations
8
+ #
9
+ # A mechanism for providing dynamically updatable feature flag state in a
10
+ # simplified form to an SDK client in test scenarios using the FDv2 protocol.
11
+ #
12
+ # This type is not stable, and not subject to any backwards
13
+ # compatibility guarantees or semantic versioning. It is not suitable for production usage.
14
+ #
15
+ # Do not use it.
16
+ # You have been warned.
17
+ #
18
+ # Unlike {LaunchDarkly::Integrations::FileData}, this mechanism does not use any external resources. It
19
+ # provides only the data that the application has put into it using the {#update} method.
20
+ #
21
+ # @example
22
+ # require 'ldclient-rb/integrations/test_data_v2'
23
+ #
24
+ # td = LaunchDarkly::Integrations::TestDataV2.data_source
25
+ # td.update(td.flag('flag-key-1').variation_for_all(true))
26
+ #
27
+ # # Configure the data system with TestDataV2 as both initializer and synchronizer
28
+ # # Note: This example assumes FDv2 data system configuration is available
29
+ # # data_config = LaunchDarkly::Impl::DataSystem::Config.custom
30
+ # # data_config.initializers([td.method(:build_initializer)])
31
+ # # data_config.synchronizers(td.method(:build_synchronizer))
32
+ #
33
+ # # config = LaunchDarkly::Config.new(
34
+ # # sdk_key,
35
+ # # datasystem_config: data_config.build
36
+ # # )
37
+ #
38
+ # # flags can be updated at any time:
39
+ # td.update(td.flag('flag-key-1')
40
+ # .variation_for_user('some-user-key', true)
41
+ # .fallthrough_variation(false))
42
+ #
43
+ # The above example uses a simple boolean flag, but more complex configurations are possible using
44
+ # the methods of the {FlagBuilderV2} that is returned by {#flag}. {FlagBuilderV2}
45
+ # supports many of the ways a flag can be configured on the LaunchDarkly dashboard, but does not
46
+ # currently support 1. rule operators other than "in" and "not in", or 2. percentage rollouts.
47
+ #
48
+ # If the same `TestDataV2` instance is used to configure multiple `LDClient` instances,
49
+ # any changes made to the data will propagate to all of the `LDClient` instances.
50
+ #
51
+ class TestDataV2
52
+ # Creates a new instance of the test data source.
53
+ #
54
+ # @return [TestDataV2] a new configurable test data source
55
+ def self.data_source
56
+ self.new
57
+ end
58
+
59
+ # @api private
60
+ def initialize
61
+ @flag_builders = Hash.new
62
+ @current_flags = Hash.new
63
+ @current_segments = Hash.new
64
+ @lock = Concurrent::ReadWriteLock.new
65
+ @instances = Array.new
66
+ @version = 0
67
+ end
68
+
69
+ #
70
+ # Creates or copies a {FlagBuilderV2} for building a test flag configuration.
71
+ #
72
+ # If this flag key has already been defined in this `TestDataV2` instance, then the builder
73
+ # starts with the same configuration that was last provided for this flag.
74
+ #
75
+ # Otherwise, it starts with a new default configuration in which the flag has `true` and
76
+ # `false` variations, is `true` for all contexts when targeting is turned on and
77
+ # `false` otherwise, and currently has targeting turned on. You can change any of those
78
+ # properties, and provide more complex behavior, using the {FlagBuilderV2} methods.
79
+ #
80
+ # Once you have set the desired configuration, pass the builder to {#update}.
81
+ #
82
+ # @param key [String] the flag key
83
+ # @return [FlagBuilderV2] a flag configuration builder
84
+ #
85
+ def flag(key)
86
+ existing_builder = @lock.with_read_lock do
87
+ if @flag_builders.key?(key) && !@flag_builders[key].nil?
88
+ @flag_builders[key]
89
+ else
90
+ nil
91
+ end
92
+ end
93
+
94
+ if existing_builder.nil?
95
+ LaunchDarkly::Integrations::TestDataV2::FlagBuilderV2.new(key).boolean_flag
96
+ else
97
+ existing_builder.clone
98
+ end
99
+ end
100
+
101
+ #
102
+ # Updates the test data with the specified flag configuration.
103
+ #
104
+ # This has the same effect as if a flag were added or modified on the LaunchDarkly dashboard.
105
+ # It immediately propagates the flag change to any `LDClient` instance(s) that you have
106
+ # already configured to use this `TestDataV2`. If no `LDClient` has been started yet,
107
+ # it simply adds this flag to the test data which will be provided to any `LDClient` that
108
+ # you subsequently configure.
109
+ #
110
+ # Any subsequent changes to this {FlagBuilderV2} instance do not affect the test data,
111
+ # unless you call {#update} again.
112
+ #
113
+ # @param flag_builder [FlagBuilderV2] a flag configuration builder
114
+ # @return [TestDataV2] the TestDataV2 instance
115
+ #
116
+ def update(flag_builder)
117
+ instances_copy = []
118
+ new_flag = nil
119
+ @lock.with_write_lock do
120
+ old_flag = @current_flags[flag_builder._key]
121
+ old_version = old_flag ? old_flag[:version] : 0
122
+
123
+ new_flag = flag_builder.build(old_version + 1)
124
+
125
+ @current_flags[flag_builder._key] = new_flag
126
+ @flag_builders[flag_builder._key] = flag_builder.clone
127
+
128
+ # Create a copy of instances while holding the lock to avoid race conditions
129
+ instances_copy = @instances.dup
130
+ end
131
+
132
+ instances_copy.each do |instance|
133
+ instance.upsert_flag(new_flag)
134
+ end
135
+
136
+ self
137
+ end
138
+
139
+ # @api private
140
+ def make_init_data
141
+ @lock.with_read_lock do
142
+ {
143
+ flags: @current_flags.dup,
144
+ segments: @current_segments.dup,
145
+ }
146
+ end
147
+ end
148
+
149
+ # @api private
150
+ def get_version
151
+ @lock.with_write_lock do
152
+ version = @version
153
+ @version += 1
154
+ version
155
+ end
156
+ end
157
+
158
+ # @api private
159
+ # @param instance [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] the TestDataSourceV2 instance to remove
160
+ def closed_instance(instance)
161
+ @lock.with_write_lock do
162
+ @instances.delete(instance) if @instances.include?(instance)
163
+ end
164
+ end
165
+
166
+ # @api private
167
+ # @param instance [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] the TestDataSourceV2 instance to add
168
+ def add_instance(instance)
169
+ @lock.with_write_lock do
170
+ @instances.push(instance)
171
+ end
172
+ end
173
+
174
+ #
175
+ # Copies a full segment data model object into the test data.
176
+ #
177
+ # It immediately propagates the change to any `LDClient` instance(s) that you have already
178
+ # configured to use this `TestDataV2`. If no `LDClient` has been started yet, it simply adds
179
+ # this segment to the test data which will be provided to any LDClient that you subsequently
180
+ # configure.
181
+ #
182
+ # This method is currently the only way to inject segment data, since there is no builder
183
+ # API for segments. It is mainly intended for the SDK's own tests of segment functionality,
184
+ # since application tests that need to produce a desired evaluation state could do so more easily
185
+ # by just setting flag values.
186
+ #
187
+ # @param segment [Hash, LaunchDarkly::Impl::Model::Segment] the segment configuration as a hash or
188
+ # a Segment model object.
189
+ # @return [TestDataV2] the TestDataV2 instance
190
+ #
191
+ def use_preconfigured_segment(segment)
192
+ instances_copy = []
193
+ segment_key = nil
194
+ updated_segment = nil
195
+
196
+ @lock.with_write_lock do
197
+ # Convert to hash and normalize keys to symbols
198
+ segment_hash = if segment.is_a?(Hash)
199
+ segment.transform_keys(&:to_sym)
200
+ else
201
+ segment.as_json
202
+ end
203
+ segment_key = segment_hash[:key]
204
+
205
+ old_segment = @current_segments[segment_key]
206
+ old_version = old_segment ? old_segment[:version] : 0
207
+
208
+ updated_segment = segment_hash.dup
209
+ updated_segment[:version] = old_version + 1
210
+
211
+ @current_segments[segment_key] = updated_segment
212
+
213
+ # Create a copy of instances while holding the lock to avoid race conditions
214
+ instances_copy = @instances.dup
215
+ end
216
+
217
+ instances_copy.each do |instance|
218
+ instance.upsert_segment(updated_segment)
219
+ end
220
+
221
+ self
222
+ end
223
+
224
+ #
225
+ # Creates an initializer that can be used with the FDv2 data system.
226
+ #
227
+ # @param sdk_key [String] the SDK key
228
+ # @param config [LaunchDarkly::Config] the SDK configuration
229
+ # @return [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] a test data initializer
230
+ #
231
+ def build_initializer(sdk_key, config)
232
+ LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2.new(self)
233
+ end
234
+
235
+ #
236
+ # Creates a synchronizer that can be used with the FDv2 data system.
237
+ #
238
+ # @param sdk_key [String] the SDK key
239
+ # @param config [LaunchDarkly::Config] the SDK configuration
240
+ # @return [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] a test data synchronizer
241
+ #
242
+ def build_synchronizer(sdk_key, config)
243
+ LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2.new(self)
244
+ end
245
+ end
246
+ end
247
+ end
248
+
@@ -1,6 +1,7 @@
1
1
  require "concurrent/atomics"
2
2
 
3
- require "ldclient-rb/expiring_cache"
3
+ require "ldclient-rb/impl/expiring_cache"
4
+ require "ldclient-rb/interfaces"
4
5
 
5
6
  module LaunchDarkly
6
7
  module Integrations
@@ -37,7 +38,7 @@ module LaunchDarkly
37
38
  expiration_seconds = opts[:expiration] || 15
38
39
  if expiration_seconds > 0
39
40
  capacity = opts[:capacity] || 1000
40
- @cache = ExpiringCache.new(capacity, expiration_seconds)
41
+ @cache = Impl::ExpiringCache.new(capacity, expiration_seconds)
41
42
  else
42
43
  @cache = nil
43
44
  end