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
@@ -42,7 +42,7 @@ module LaunchDarkly
42
42
  self.new
43
43
  end
44
44
 
45
- # @private
45
+ # @api private
46
46
  def initialize
47
47
  @flag_builders = Hash.new
48
48
  @current_flags = Hash.new
@@ -56,7 +56,7 @@ module LaunchDarkly
56
56
  # Called internally by the SDK to determine what arguments to pass to call
57
57
  # You do not need to call this method.
58
58
  #
59
- # @private
59
+ # @api private
60
60
  def arity
61
61
  2
62
62
  end
@@ -65,7 +65,7 @@ module LaunchDarkly
65
65
  # Called internally by the SDK to associate this test data source with an {@code LDClient} instance.
66
66
  # You do not need to call this method.
67
67
  #
68
- # @private
68
+ # @api private
69
69
  def call(_, config)
70
70
  impl = LaunchDarkly::Impl::Integrations::TestData::TestDataSource.new(config.feature_store, self)
71
71
  @instances_lock.with_write_lock { @instances.push(impl) }
@@ -121,10 +121,10 @@ module LaunchDarkly
121
121
  if @current_flags[flag_key]
122
122
  version = @current_flags[flag_key][:version]
123
123
  end
124
- new_flag = Impl::Model.deserialize(FEATURES, flag_builder.build(version+1))
124
+ new_flag = LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Impl::DataStore::FEATURES, flag_builder.build(version+1))
125
125
  @current_flags[flag_key] = new_flag
126
126
  end
127
- update_item(FEATURES, new_flag)
127
+ update_item(LaunchDarkly::Impl::DataStore::FEATURES, new_flag)
128
128
  self
129
129
  end
130
130
 
@@ -147,7 +147,7 @@ module LaunchDarkly
147
147
  # @return [TestData] the TestData instance
148
148
  #
149
149
  def use_preconfigured_flag(flag)
150
- use_preconfigured_item(FEATURES, flag, @current_flags)
150
+ use_preconfigured_item(LaunchDarkly::Impl::DataStore::FEATURES, flag, @current_flags)
151
151
  end
152
152
 
153
153
  #
@@ -167,7 +167,7 @@ module LaunchDarkly
167
167
  # @return [TestData] the TestData instance
168
168
  #
169
169
  def use_preconfigured_segment(segment)
170
- use_preconfigured_item(SEGMENTS, segment, @current_segments)
170
+ use_preconfigured_item(LaunchDarkly::Impl::DataStore::SEGMENTS, segment, @current_segments)
171
171
  end
172
172
 
173
173
  private def use_preconfigured_item(kind, item, current)
@@ -194,17 +194,17 @@ module LaunchDarkly
194
194
  end
195
195
  end
196
196
 
197
- # @private
197
+ # @api private
198
198
  def make_init_data
199
199
  @lock.with_read_lock do
200
200
  {
201
- FEATURES => @current_flags.clone,
202
- SEGMENTS => @current_segments.clone,
201
+ LaunchDarkly::Impl::DataStore::FEATURES => @current_flags.clone,
202
+ LaunchDarkly::Impl::DataStore::SEGMENTS => @current_segments.clone,
203
203
  }
204
204
  end
205
205
  end
206
206
 
207
- # @private
207
+ # @api private
208
208
  def closed_instance(instance)
209
209
  @instances_lock.with_write_lock { @instances.delete(instance) }
210
210
  end
@@ -0,0 +1,582 @@
1
+ require 'ldclient-rb/util'
2
+ require 'ldclient-rb/context'
3
+ require 'set'
4
+
5
+ module LaunchDarkly
6
+ module Integrations
7
+ class TestDataV2
8
+ # Constants for boolean flag variation indices
9
+ TRUE_VARIATION_INDEX = 0
10
+ FALSE_VARIATION_INDEX = 1
11
+
12
+ # @api private
13
+ def self.variation_for_boolean(variation)
14
+ variation ? TRUE_VARIATION_INDEX : FALSE_VARIATION_INDEX
15
+ end
16
+
17
+ #
18
+ # A builder for feature flag configurations to be used with {TestDataV2}.
19
+ #
20
+ # @see TestDataV2#flag
21
+ # @see TestDataV2#update
22
+ #
23
+ class FlagBuilderV2
24
+ # @api private
25
+ attr_reader :_key
26
+
27
+ # @api private
28
+ def initialize(key)
29
+ @_key = key
30
+ @_on = true
31
+ @_variations = []
32
+ @_off_variation = nil
33
+ @_fallthrough_variation = nil
34
+ @_targets = {}
35
+ @_rules = []
36
+ end
37
+
38
+ # Creates a deep copy of the flag builder when the object is duplicated or cloned.
39
+ # Subsequent updates to the original `FlagBuilderV2` object will not update the
40
+ # copy and vice versa.
41
+ #
42
+ # This method is automatically invoked by Ruby's `dup` and `clone` methods.
43
+ # Immutable instance variables (strings, numbers, booleans, nil) are automatically
44
+ # copied by the `super` call. Only mutable collections need explicit deep copying.
45
+ #
46
+ # @api private
47
+ def initialize_copy(other)
48
+ super(other)
49
+ @_variations = @_variations.clone
50
+ @_targets = deep_copy_targets
51
+ @_rules = deep_copy_rules
52
+ end
53
+
54
+ #
55
+ # Sets targeting to be on or off for this flag.
56
+ #
57
+ # The effect of this depends on the rest of the flag configuration, just as it does on the
58
+ # real LaunchDarkly dashboard. In the default configuration that you get from calling
59
+ # {TestDataV2#flag} with a new flag key, the flag will return `false`
60
+ # whenever targeting is off, and `true` when targeting is on.
61
+ #
62
+ # @param on [Boolean] true if targeting should be on
63
+ # @return [FlagBuilderV2] the flag builder
64
+ #
65
+ def on(on)
66
+ @_on = on
67
+ self
68
+ end
69
+
70
+ #
71
+ # Specifies the fallthrough variation. The fallthrough is the value
72
+ # that is returned if targeting is on and the context was not matched by a more specific
73
+ # target or rule.
74
+ #
75
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
76
+ # this also changes it to a boolean flag.
77
+ #
78
+ # @param variation [Boolean, Integer] true or false or the desired fallthrough variation index:
79
+ # 0 for the first, 1 for the second, etc.
80
+ # @return [FlagBuilderV2] the flag builder
81
+ #
82
+ def fallthrough_variation(variation)
83
+ if LaunchDarkly::Impl::Util.bool?(variation)
84
+ boolean_flag.fallthrough_variation(TestDataV2.variation_for_boolean(variation))
85
+ else
86
+ @_fallthrough_variation = variation
87
+ self
88
+ end
89
+ end
90
+
91
+ #
92
+ # Specifies the off variation. This is the variation that is returned
93
+ # whenever targeting is off.
94
+ #
95
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
96
+ # this also changes it to a boolean flag.
97
+ #
98
+ # @param variation [Boolean, Integer] true or false or the desired off variation index:
99
+ # 0 for the first, 1 for the second, etc.
100
+ # @return [FlagBuilderV2] the flag builder
101
+ #
102
+ def off_variation(variation)
103
+ if LaunchDarkly::Impl::Util.bool?(variation)
104
+ boolean_flag.off_variation(TestDataV2.variation_for_boolean(variation))
105
+ else
106
+ @_off_variation = variation
107
+ self
108
+ end
109
+ end
110
+
111
+ #
112
+ # A shortcut for setting the flag to use the standard boolean configuration.
113
+ #
114
+ # This is the default for all new flags created with {TestDataV2#flag}.
115
+ #
116
+ # The flag will have two variations, `true` and `false` (in that order);
117
+ # it will return `false` whenever targeting is off, and `true` when targeting is on
118
+ # if no other settings specify otherwise.
119
+ #
120
+ # @return [FlagBuilderV2] the flag builder
121
+ #
122
+ def boolean_flag
123
+ return self if boolean_flag?
124
+
125
+ variations(true, false).fallthrough_variation(TRUE_VARIATION_INDEX).off_variation(FALSE_VARIATION_INDEX)
126
+ end
127
+
128
+ #
129
+ # Changes the allowable variation values for the flag.
130
+ #
131
+ # The value may be of any valid JSON type. For instance, a boolean flag
132
+ # normally has `true, false`; a string-valued flag might have
133
+ # `'red', 'green'`; etc.
134
+ #
135
+ # @example A single variation
136
+ # td.flag('new-flag').variations(true)
137
+ #
138
+ # @example Multiple variations
139
+ # td.flag('new-flag').variations('red', 'green', 'blue')
140
+ #
141
+ # @param variations [Array<Object>] the desired variations
142
+ # @return [FlagBuilderV2] the flag builder
143
+ #
144
+ def variations(*variations)
145
+ @_variations = variations
146
+ self
147
+ end
148
+
149
+ #
150
+ # Sets the flag to always return the specified variation for all contexts.
151
+ #
152
+ # The variation is specified, targeting is switched on, and any existing targets or rules are removed.
153
+ # The fallthrough variation is set to the specified value. The off variation is left unchanged.
154
+ #
155
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
156
+ # this also changes it to a boolean flag.
157
+ #
158
+ # @param variation [Boolean, Integer] true or false or the desired variation index to return:
159
+ # 0 for the first, 1 for the second, etc.
160
+ # @return [FlagBuilderV2] the flag builder
161
+ #
162
+ def variation_for_all(variation)
163
+ if LaunchDarkly::Impl::Util.bool?(variation)
164
+ return boolean_flag.variation_for_all(TestDataV2.variation_for_boolean(variation))
165
+ end
166
+
167
+ clear_rules.clear_targets.on(true).fallthrough_variation(variation)
168
+ end
169
+
170
+ #
171
+ # Sets the flag to always return the specified variation value for all contexts.
172
+ #
173
+ # The value may be of any valid JSON type. This method changes the flag to have only
174
+ # a single variation, which is this value, and to return the same variation
175
+ # regardless of whether targeting is on or off. Any existing targets or rules
176
+ # are removed.
177
+ #
178
+ # @param value [Object] the desired value to be returned for all contexts
179
+ # @return [FlagBuilderV2] the flag builder
180
+ #
181
+ def value_for_all(value)
182
+ variations(value).variation_for_all(0)
183
+ end
184
+
185
+ #
186
+ # Sets the flag to return the specified variation for a specific user key when targeting
187
+ # is on.
188
+ #
189
+ # This is a shortcut for calling {#variation_for_key} with
190
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
191
+ #
192
+ # This has no effect when targeting is turned off for the flag.
193
+ #
194
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
195
+ # this also changes it to a boolean flag.
196
+ #
197
+ # @param user_key [String] a user key
198
+ # @param variation [Boolean, Integer] true or false or the desired variation index to return:
199
+ # 0 for the first, 1 for the second, etc.
200
+ # @return [FlagBuilderV2] the flag builder
201
+ #
202
+ def variation_for_user(user_key, variation)
203
+ variation_for_key(LaunchDarkly::LDContext::KIND_DEFAULT, user_key, variation)
204
+ end
205
+
206
+ #
207
+ # Sets the flag to return the specified variation for a specific context, identified
208
+ # by context kind and key, when targeting is on.
209
+ #
210
+ # This has no effect when targeting is turned off for the flag.
211
+ #
212
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
213
+ # this also changes it to a boolean flag.
214
+ #
215
+ # @param context_kind [String] the context kind
216
+ # @param context_key [String] the context key
217
+ # @param variation [Boolean, Integer] true or false or the desired variation index to return:
218
+ # 0 for the first, 1 for the second, etc.
219
+ # @return [FlagBuilderV2] the flag builder
220
+ #
221
+ def variation_for_key(context_kind, context_key, variation)
222
+ if LaunchDarkly::Impl::Util.bool?(variation)
223
+ return boolean_flag.variation_for_key(context_kind, context_key, TestDataV2.variation_for_boolean(variation))
224
+ end
225
+
226
+ targets = @_targets[context_kind]
227
+ if targets.nil?
228
+ targets = {}
229
+ @_targets[context_kind] = targets
230
+ end
231
+
232
+ @_variations.each_index do |idx|
233
+ if idx == variation
234
+ (targets[idx] ||= Set.new).add(context_key)
235
+ elsif targets.key?(idx)
236
+ targets[idx].delete(context_key)
237
+ end
238
+ end
239
+
240
+ self
241
+ end
242
+
243
+ #
244
+ # Starts defining a flag rule, using the "is one of" operator.
245
+ #
246
+ # This is a shortcut for calling {#if_match_context} with
247
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
248
+ #
249
+ # @example create a rule that returns `true` if the name is "Patsy" or "Edina"
250
+ # td.flag("flag")
251
+ # .if_match('name', 'Patsy', 'Edina')
252
+ # .then_return(true)
253
+ #
254
+ # @param attribute [String] the user attribute to match against
255
+ # @param values [Array<Object>] values to compare to
256
+ # @return [FlagRuleBuilderV2] the flag rule builder
257
+ #
258
+ def if_match(attribute, *values)
259
+ if_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
260
+ end
261
+
262
+ #
263
+ # Starts defining a flag rule, using the "is one of" operator. This matching expression only
264
+ # applies to contexts of a specific kind.
265
+ #
266
+ # @example create a rule that returns `true` if the name attribute for the
267
+ # "company" context is "Ella" or "Monsoon":
268
+ # td.flag("flag")
269
+ # .if_match_context('company', 'name', 'Ella', 'Monsoon')
270
+ # .then_return(True)
271
+ #
272
+ # @param context_kind [String] the context kind
273
+ # @param attribute [String] the context attribute to match against
274
+ # @param values [Array<Object>] values to compare to
275
+ # @return [FlagRuleBuilderV2] the flag rule builder
276
+ #
277
+ def if_match_context(context_kind, attribute, *values)
278
+ flag_rule_builder = FlagRuleBuilderV2.new(self)
279
+ flag_rule_builder.and_match_context(context_kind, attribute, *values)
280
+ end
281
+
282
+ #
283
+ # Starts defining a flag rule, using the "is not one of" operator.
284
+ #
285
+ # This is a shortcut for calling {#if_not_match_context} with
286
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
287
+ #
288
+ # @example create a rule that returns `true` if the name is neither "Saffron" nor "Bubble"
289
+ # td.flag("flag")
290
+ # .if_not_match('name', 'Saffron', 'Bubble')
291
+ # .then_return(true)
292
+ #
293
+ # @param attribute [String] the user attribute to match against
294
+ # @param values [Array<Object>] values to compare to
295
+ # @return [FlagRuleBuilderV2] the flag rule builder
296
+ #
297
+ def if_not_match(attribute, *values)
298
+ if_not_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
299
+ end
300
+
301
+ #
302
+ # Starts defining a flag rule, using the "is not one of" operator. This matching expression only
303
+ # applies to contexts of a specific kind.
304
+ #
305
+ # @example create a rule that returns `true` if the name attribute for the
306
+ # "company" context is neither "Pendant" nor "Sterling Cooper":
307
+ # td.flag("flag")
308
+ # .if_not_match_context('company', 'name', 'Pendant', 'Sterling Cooper')
309
+ # .then_return(true)
310
+ #
311
+ # @param context_kind [String] the context kind
312
+ # @param attribute [String] the context attribute to match against
313
+ # @param values [Array<Object>] values to compare to
314
+ # @return [FlagRuleBuilderV2] the flag rule builder
315
+ #
316
+ def if_not_match_context(context_kind, attribute, *values)
317
+ flag_rule_builder = FlagRuleBuilderV2.new(self)
318
+ flag_rule_builder.and_not_match_context(context_kind, attribute, *values)
319
+ end
320
+
321
+ #
322
+ # Removes any existing rules from the flag.
323
+ # This undoes the effect of methods like {#if_match}.
324
+ #
325
+ # @return [FlagBuilderV2] the same flag builder
326
+ #
327
+ def clear_rules
328
+ @_rules = []
329
+ self
330
+ end
331
+
332
+ #
333
+ # Removes any existing targets from the flag.
334
+ # This undoes the effect of methods like {#variation_for_user}.
335
+ #
336
+ # @return [FlagBuilderV2] the same flag builder
337
+ #
338
+ def clear_targets
339
+ @_targets = {}
340
+ self
341
+ end
342
+
343
+ # Note that build is private by convention, because we don't want developers to
344
+ # consider it part of the public API, but it is still called from TestDataV2.
345
+ #
346
+ # Creates a dictionary representation of the flag
347
+ #
348
+ # @api private
349
+ # @param version [Integer] the version number of the flag
350
+ # @return [Hash] the dictionary representation of the flag
351
+ #
352
+ def build(version)
353
+ base_flag_object = {
354
+ key: @_key,
355
+ version: version,
356
+ on: @_on,
357
+ variations: @_variations,
358
+ prerequisites: [],
359
+ salt: '',
360
+ }
361
+
362
+ base_flag_object[:offVariation] = @_off_variation unless @_off_variation.nil?
363
+ base_flag_object[:fallthrough] = { variation: @_fallthrough_variation } unless @_fallthrough_variation.nil?
364
+
365
+ targets = []
366
+ context_targets = []
367
+ @_targets.each do |target_context_kind, target_variations|
368
+ target_variations.each do |var_index, target_keys|
369
+ if target_context_kind == LaunchDarkly::LDContext::KIND_DEFAULT
370
+ targets << { variation: var_index, values: target_keys.to_a.sort } # sorting just for test determinacy
371
+ context_targets << { contextKind: target_context_kind, variation: var_index, values: [] }
372
+ else
373
+ context_targets << { contextKind: target_context_kind, variation: var_index, values: target_keys.to_a.sort } # sorting just for test determinacy
374
+ end
375
+ end
376
+ end
377
+ base_flag_object[:targets] = targets unless targets.empty?
378
+ base_flag_object[:contextTargets] = context_targets unless context_targets.empty?
379
+
380
+ rules = []
381
+ @_rules.each_with_index do |rule, idx|
382
+ rules << rule.build(idx.to_s)
383
+ end
384
+ base_flag_object[:rules] = rules unless rules.empty?
385
+
386
+ base_flag_object
387
+ end
388
+
389
+ # @api private
390
+ def add_rule(flag_rule_builder)
391
+ @_rules << flag_rule_builder
392
+ end
393
+
394
+ private def boolean_flag?
395
+ @_variations.length == 2 &&
396
+ @_variations[TRUE_VARIATION_INDEX] == true &&
397
+ @_variations[FALSE_VARIATION_INDEX] == false
398
+ end
399
+
400
+ private def deep_copy_targets
401
+ to = {}
402
+ @_targets.each do |k, v|
403
+ to[k] = {}
404
+ v.each do |var_idx, keys|
405
+ to[k][var_idx] = keys.clone
406
+ end
407
+ end
408
+ to
409
+ end
410
+
411
+ private def deep_copy_rules
412
+ @_rules.map(&:clone)
413
+ end
414
+ end
415
+
416
+ #
417
+ # A builder for feature flag rules to be used with {FlagBuilderV2}.
418
+ #
419
+ # In the LaunchDarkly model, a flag can have any number of rules, and a rule can have any number of
420
+ # clauses. A clause is an individual test such as "name is 'X'". A rule matches a context if all of the
421
+ # rule's clauses match the context.
422
+ #
423
+ # To start defining a rule, use one of the flag builder's matching methods such as
424
+ # {FlagBuilderV2#if_match}. This defines the first clause for the rule.
425
+ # Optionally, you may add more clauses with the rule builder's methods such as
426
+ # {#and_match} or {#and_not_match}.
427
+ # Finally, call {#then_return} to finish defining the rule.
428
+ #
429
+ class FlagRuleBuilderV2
430
+ # @api private
431
+ #
432
+ # @param flag_builder [FlagBuilderV2] the flag builder instance
433
+ #
434
+ def initialize(flag_builder)
435
+ @_flag_builder = flag_builder
436
+ @_clauses = []
437
+ @_variation = nil
438
+ end
439
+
440
+ # @api private
441
+ def initialize_copy(other)
442
+ super(other)
443
+ @_clauses = @_clauses.map(&:clone)
444
+ end
445
+
446
+ #
447
+ # Adds another clause, using the "is one of" operator.
448
+ #
449
+ # This is a shortcut for calling {#and_match_context} with
450
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
451
+ #
452
+ # @example create a rule that returns `true` if the name is "Patsy" and the country is "gb"
453
+ # td.flag('flag')
454
+ # .if_match('name', 'Patsy')
455
+ # .and_match('country', 'gb')
456
+ # .then_return(true)
457
+ #
458
+ # @param attribute [String] the user attribute to match against
459
+ # @param values [Array<Object>] values to compare to
460
+ # @return [FlagRuleBuilderV2] the flag rule builder
461
+ #
462
+ def and_match(attribute, *values)
463
+ and_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
464
+ end
465
+
466
+ #
467
+ # Adds another clause, using the "is one of" operator. This matching expression only
468
+ # applies to contexts of a specific kind.
469
+ #
470
+ # @example create a rule that returns `true` if the name attribute for the
471
+ # "company" context is "Ella", and the country attribute for the "company" context is "gb":
472
+ # td.flag('flag')
473
+ # .if_match_context('company', 'name', 'Ella')
474
+ # .and_match_context('company', 'country', 'gb')
475
+ # .then_return(true)
476
+ #
477
+ # @param context_kind [String] the context kind
478
+ # @param attribute [String] the context attribute to match against
479
+ # @param values [Array<Object>] values to compare to
480
+ # @return [FlagRuleBuilderV2] the flag rule builder
481
+ #
482
+ def and_match_context(context_kind, attribute, *values)
483
+ @_clauses << {
484
+ contextKind: context_kind,
485
+ attribute: attribute,
486
+ op: 'in',
487
+ values: values.to_a,
488
+ negate: false,
489
+ }
490
+ self
491
+ end
492
+
493
+ #
494
+ # Adds another clause, using the "is not one of" operator.
495
+ #
496
+ # This is a shortcut for calling {#and_not_match_context} with
497
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
498
+ #
499
+ # @example create a rule that returns `true` if the name is "Patsy" and the country is not "gb"
500
+ # td.flag('flag')
501
+ # .if_match('name', 'Patsy')
502
+ # .and_not_match('country', 'gb')
503
+ # .then_return(true)
504
+ #
505
+ # @param attribute [String] the user attribute to match against
506
+ # @param values [Array<Object>] values to compare to
507
+ # @return [FlagRuleBuilderV2] the flag rule builder
508
+ #
509
+ def and_not_match(attribute, *values)
510
+ and_not_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
511
+ end
512
+
513
+ #
514
+ # Adds another clause, using the "is not one of" operator. This matching expression only
515
+ # applies to contexts of a specific kind.
516
+ #
517
+ # @example create a rule that returns `true` if the name attribute for the
518
+ # "company" context is "Ella", and the country attribute for the "company" context is not "gb":
519
+ # td.flag('flag')
520
+ # .if_match_context('company', 'name', 'Ella')
521
+ # .and_not_match_context('company', 'country', 'gb')
522
+ # .then_return(true)
523
+ #
524
+ # @param context_kind [String] the context kind
525
+ # @param attribute [String] the context attribute to match against
526
+ # @param values [Array<Object>] values to compare to
527
+ # @return [FlagRuleBuilderV2] the flag rule builder
528
+ #
529
+ def and_not_match_context(context_kind, attribute, *values)
530
+ @_clauses << {
531
+ contextKind: context_kind,
532
+ attribute: attribute,
533
+ op: 'in',
534
+ values: values.to_a,
535
+ negate: true,
536
+ }
537
+ self
538
+ end
539
+
540
+ #
541
+ # Finishes defining the rule, specifying the result as either a boolean
542
+ # or a variation index.
543
+ #
544
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
545
+ # this also changes it to a boolean flag.
546
+ #
547
+ # @param variation [Boolean, Integer] true or false or the desired variation index:
548
+ # 0 for the first, 1 for the second, etc.
549
+ # @return [FlagBuilderV2] the flag builder with this rule added
550
+ #
551
+ def then_return(variation)
552
+ if LaunchDarkly::Impl::Util.bool?(variation)
553
+ @_flag_builder.boolean_flag
554
+ return then_return(TestDataV2.variation_for_boolean(variation))
555
+ end
556
+
557
+ @_variation = variation
558
+ @_flag_builder.add_rule(self)
559
+ @_flag_builder
560
+ end
561
+
562
+ # Note that build is private by convention, because we don't want developers to
563
+ # consider it part of the public API, but it is still called from FlagBuilderV2.
564
+ #
565
+ # Creates a dictionary representation of the rule
566
+ #
567
+ # @api private
568
+ # @param id [String] the rule id
569
+ # @return [Hash] the dictionary representation of the rule
570
+ #
571
+ def build(id)
572
+ {
573
+ id: 'rule' + id,
574
+ variation: @_variation,
575
+ clauses: @_clauses,
576
+ }
577
+ end
578
+ end
579
+ end
580
+ end
581
+ end
582
+