launchdarkly-server-sdk 6.2.5 → 7.0.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/lib/ldclient-rb/config.rb +203 -43
  4. data/lib/ldclient-rb/context.rb +487 -0
  5. data/lib/ldclient-rb/evaluation_detail.rb +85 -26
  6. data/lib/ldclient-rb/events.rb +185 -146
  7. data/lib/ldclient-rb/flags_state.rb +25 -14
  8. data/lib/ldclient-rb/impl/big_segments.rb +117 -0
  9. data/lib/ldclient-rb/impl/context.rb +96 -0
  10. data/lib/ldclient-rb/impl/context_filter.rb +145 -0
  11. data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
  12. data/lib/ldclient-rb/impl/evaluator.rb +428 -132
  13. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
  14. data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
  15. data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
  16. data/lib/ldclient-rb/impl/event_sender.rb +6 -6
  17. data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
  18. data/lib/ldclient-rb/impl/event_types.rb +78 -0
  19. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
  20. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +92 -28
  21. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
  22. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +165 -32
  23. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
  24. data/lib/ldclient-rb/impl/model/clause.rb +39 -0
  25. data/lib/ldclient-rb/impl/model/feature_flag.rb +213 -0
  26. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
  27. data/lib/ldclient-rb/impl/model/segment.rb +126 -0
  28. data/lib/ldclient-rb/impl/model/serialization.rb +54 -44
  29. data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
  30. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
  31. data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
  32. data/lib/ldclient-rb/impl/util.rb +62 -1
  33. data/lib/ldclient-rb/in_memory_store.rb +2 -2
  34. data/lib/ldclient-rb/integrations/consul.rb +9 -2
  35. data/lib/ldclient-rb/integrations/dynamodb.rb +47 -2
  36. data/lib/ldclient-rb/integrations/file_data.rb +108 -0
  37. data/lib/ldclient-rb/integrations/redis.rb +43 -3
  38. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +594 -0
  39. data/lib/ldclient-rb/integrations/test_data.rb +213 -0
  40. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +14 -9
  41. data/lib/ldclient-rb/integrations.rb +2 -51
  42. data/lib/ldclient-rb/interfaces.rb +151 -1
  43. data/lib/ldclient-rb/ldclient.rb +175 -133
  44. data/lib/ldclient-rb/memoized_value.rb +1 -1
  45. data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
  46. data/lib/ldclient-rb/polling.rb +22 -41
  47. data/lib/ldclient-rb/reference.rb +274 -0
  48. data/lib/ldclient-rb/requestor.rb +7 -7
  49. data/lib/ldclient-rb/stream.rb +9 -9
  50. data/lib/ldclient-rb/util.rb +11 -17
  51. data/lib/ldclient-rb/version.rb +1 -1
  52. data/lib/ldclient-rb.rb +2 -4
  53. metadata +49 -23
  54. data/lib/ldclient-rb/event_summarizer.rb +0 -55
  55. data/lib/ldclient-rb/file_data_source.rb +0 -314
  56. data/lib/ldclient-rb/impl/event_factory.rb +0 -126
  57. data/lib/ldclient-rb/newrelic.rb +0 -17
  58. data/lib/ldclient-rb/redis_store.rb +0 -88
  59. data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -0,0 +1,594 @@
1
+ require 'ldclient-rb/util'
2
+
3
+ module LaunchDarkly
4
+ module Integrations
5
+ class TestData
6
+ #
7
+ # A builder for feature flag configurations to be used with {TestData}.
8
+ #
9
+ # @see TestData#flag
10
+ # @see TestData#update
11
+ #
12
+ class FlagBuilder
13
+ attr_reader :key
14
+
15
+ # @private
16
+ def initialize(key)
17
+ @key = key
18
+ @on = true
19
+ @variations = []
20
+ end
21
+
22
+ # @private
23
+ def initialize_copy(other)
24
+ super(other)
25
+ @variations = @variations.clone
26
+ @rules = @rules.nil? ? nil : deep_copy_array(@rules)
27
+ @targets = @targets.nil? ? nil : deep_copy_hash(@targets)
28
+ end
29
+
30
+ #
31
+ # Sets targeting to be on or off for this flag.
32
+ #
33
+ # The effect of this depends on the rest of the flag configuration, just as it does on the
34
+ # real LaunchDarkly dashboard. In the default configuration that you get from calling
35
+ # {TestData#flag} with a new flag key, the flag will return `false`
36
+ # whenever targeting is off, and `true` when targeting is on.
37
+ #
38
+ # @param on [Boolean] true if targeting should be on
39
+ # @return [FlagBuilder] the builder
40
+ #
41
+ def on(on)
42
+ @on = on
43
+ self
44
+ end
45
+
46
+ #
47
+ # Specifies the fallthrough variation. The fallthrough is the value
48
+ # that is returned if targeting is on and the context was not matched by a more specific
49
+ # target or rule.
50
+ #
51
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
52
+ # this also changes it to a boolean flag.
53
+ #
54
+ # @param variation [Boolean, Integer] true or false or the desired fallthrough variation index:
55
+ # 0 for the first, 1 for the second, etc.
56
+ # @return [FlagBuilder] the builder
57
+ #
58
+ def fallthrough_variation(variation)
59
+ if LaunchDarkly::Impl::Util.bool? variation
60
+ boolean_flag.fallthrough_variation(variation_for_boolean(variation))
61
+ else
62
+ @fallthrough_variation = variation
63
+ self
64
+ end
65
+ end
66
+
67
+ #
68
+ # Specifies the off variation for a flag. This is the variation that is returned
69
+ # whenever targeting is off.
70
+ #
71
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
72
+ # this also changes it to a boolean flag.
73
+ #
74
+ # @param variation [Boolean, Integer] true or false or the desired off variation index:
75
+ # 0 for the first, 1 for the second, etc.
76
+ # @return [FlagBuilder] the builder
77
+ #
78
+ def off_variation(variation)
79
+ if LaunchDarkly::Impl::Util.bool? variation
80
+ boolean_flag.off_variation(variation_for_boolean(variation))
81
+ else
82
+ @off_variation = variation
83
+ self
84
+ end
85
+ end
86
+
87
+ #
88
+ # Changes the allowable variation values for the flag.
89
+ #
90
+ # The value may be of any valid JSON type. For instance, a boolean flag
91
+ # normally has `true, false`; a string-valued flag might have
92
+ # `'red', 'green'`; etc.
93
+ #
94
+ # @example A single variation
95
+ # td.flag('new-flag')
96
+ # .variations(true)
97
+ #
98
+ # @example Multiple variations
99
+ # td.flag('new-flag')
100
+ # .variations('red', 'green', 'blue')
101
+ #
102
+ # @param variations [Array<Object>] the the desired variations
103
+ # @return [FlagBuilder] the builder
104
+ #
105
+ def variations(*variations)
106
+ @variations = variations
107
+ self
108
+ end
109
+
110
+ #
111
+ # Sets the flag to always return the specified variation for all contexts.
112
+ #
113
+ # The variation is specified, Targeting is switched on, and any existing targets or rules are removed.
114
+ # The fallthrough variation is set to the specified value. The off variation is left unchanged.
115
+ #
116
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
117
+ # this also changes it to a boolean flag.
118
+ #
119
+ # @param variation [Boolean, Integer] true or false or the desired variation index to return:
120
+ # 0 for the first, 1 for the second, etc.
121
+ # @return [FlagBuilder] the builder
122
+ #
123
+ def variation_for_all(variation)
124
+ if LaunchDarkly::Impl::Util.bool? variation
125
+ boolean_flag.variation_for_all(variation_for_boolean(variation))
126
+ else
127
+ on(true).clear_rules.clear_targets.fallthrough_variation(variation)
128
+ end
129
+ end
130
+
131
+ #
132
+ # @deprecated Backwards compatibility alias for #variation_for_all
133
+ #
134
+ alias_method :variation_for_all_users, :variation_for_all
135
+
136
+ #
137
+ # Sets the flag to always return the specified variation value for all context.
138
+ #
139
+ # The value may be of any valid JSON type. This method changes the
140
+ # flag to have only a single variation, which is this value, and to return the same
141
+ # variation regardless of whether targeting is on or off. Any existing targets or rules
142
+ # are removed.
143
+ #
144
+ # @param value [Object] the desired value to be returned for all contexts
145
+ # @return [FlagBuilder] the builder
146
+ #
147
+ def value_for_all(value)
148
+ variations(value).variation_for_all(0)
149
+ end
150
+
151
+ #
152
+ # @deprecated Backwards compatibility alias for #value_for_all
153
+ #
154
+ alias_method :value_for_all_users, :value_for_all
155
+
156
+ #
157
+ # Sets the flag to return the specified variation for a specific context key when targeting
158
+ # is on.
159
+ #
160
+ # This has no effect when targeting is turned off for the flag.
161
+ #
162
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
163
+ # this also changes it to a boolean flag.
164
+ #
165
+ # @param context_kind [String] a context kind
166
+ # @param context_key [String] a context key
167
+ # @param variation [Boolean, Integer] true or false or the desired variation index to return:
168
+ # 0 for the first, 1 for the second, etc.
169
+ # @return [FlagBuilder] the builder
170
+ #
171
+ def variation_for_key(context_kind, context_key, variation)
172
+ if LaunchDarkly::Impl::Util.bool? variation
173
+ return boolean_flag.variation_for_key(context_kind, context_key, variation_for_boolean(variation))
174
+ end
175
+
176
+ if @targets.nil?
177
+ @targets = Hash.new
178
+ end
179
+
180
+ targets = @targets[context_kind] || []
181
+ @variations.count.times do | i |
182
+ if i == variation
183
+ if targets[i].nil?
184
+ targets[i] = [context_key]
185
+ else
186
+ targets[i].push(context_key)
187
+ end
188
+ elsif not targets[i].nil?
189
+ targets[i].delete(context_key)
190
+ end
191
+ end
192
+
193
+ @targets[context_kind] = targets
194
+
195
+ self
196
+ end
197
+
198
+ #
199
+ # Sets the flag to return the specified variation for a specific user key when targeting
200
+ # is on.
201
+ #
202
+ # This is a shortcut for calling {variation_for_key} with
203
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
204
+ #
205
+ # This has no effect when targeting is turned off for the flag.
206
+ #
207
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
208
+ # this also changes it to a boolean flag.
209
+ #
210
+ # @param user_key [String] a user key
211
+ # @param variation [Boolean, Integer] true or false or the desired variation index to return:
212
+ # 0 for the first, 1 for the second, etc.
213
+ # @return [FlagBuilder] the builder
214
+ #
215
+ def variation_for_user(user_key, variation)
216
+ variation_for_key(LaunchDarkly::LDContext::KIND_DEFAULT, user_key, variation)
217
+ end
218
+
219
+ #
220
+ # Starts defining a flag rule, using the "is one of" operator.
221
+ #
222
+ # @example create a rule that returns `true` if the name is "Patsy" or "Edina" and the context kind is "user"
223
+ # testData.flag("flag")
224
+ # .if_match_context("user", :name, 'Patsy', 'Edina')
225
+ # .then_return(true);
226
+ #
227
+ # @param context_kind [String] a context kind
228
+ # @param attribute [Symbol] the context attribute to match against
229
+ # @param values [Array<Object>] values to compare to
230
+ # @return [FlagRuleBuilder] a flag rule builder
231
+ #
232
+ # @see FlagRuleBuilder#then_return
233
+ # @see FlagRuleBuilder#and_match
234
+ # @see FlagRuleBuilder#and_not_match
235
+ #
236
+ def if_match_context(context_kind, attribute, *values)
237
+ FlagRuleBuilder.new(self).and_match_context(context_kind, attribute, *values)
238
+ end
239
+
240
+ #
241
+ # Starts defining a flag rule, using the "is one of" operator.
242
+ #
243
+ # This is a shortcut for calling {if_match_context} with
244
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
245
+ #
246
+ # @example create a rule that returns `true` if the name is "Patsy" or "Edina"
247
+ # testData.flag("flag")
248
+ # .if_match(:name, 'Patsy', 'Edina')
249
+ # .then_return(true);
250
+ #
251
+ # @param attribute [Symbol] the user attribute to match against
252
+ # @param values [Array<Object>] values to compare to
253
+ # @return [FlagRuleBuilder] a flag rule builder
254
+ #
255
+ # @see FlagRuleBuilder#then_return
256
+ # @see FlagRuleBuilder#and_match
257
+ # @see FlagRuleBuilder#and_not_match
258
+ #
259
+ def if_match(attribute, *values)
260
+ if_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
261
+ end
262
+
263
+ #
264
+ # Starts defining a flag rule, using the "is not one of" operator.
265
+ #
266
+ # @example create a rule that returns `true` if the name is neither "Saffron" nor "Bubble"
267
+ # testData.flag("flag")
268
+ # .if_not_match_context("user", :name, 'Saffron', 'Bubble')
269
+ # .then_return(true)
270
+ #
271
+ # @param context_kind [String] a context kind
272
+ # @param attribute [Symbol] the context attribute to match against
273
+ # @param values [Array<Object>] values to compare to
274
+ # @return [FlagRuleBuilder] a flag rule builder
275
+ #
276
+ # @see FlagRuleBuilder#then_return
277
+ # @see FlagRuleBuilder#and_match
278
+ # @see FlagRuleBuilder#and_not_match
279
+ #
280
+ def if_not_match_context(context_kind, attribute, *values)
281
+ FlagRuleBuilder.new(self).and_not_match_context(context_kind, attribute, *values)
282
+ end
283
+
284
+ #
285
+ # Starts defining a flag rule, using the "is not one of" operator.
286
+ #
287
+ # This is a shortcut for calling {if_not_match_context} with
288
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
289
+ #
290
+ # @example create a rule that returns `true` if the name is neither "Saffron" nor "Bubble"
291
+ # testData.flag("flag")
292
+ # .if_not_match(:name, 'Saffron', 'Bubble')
293
+ # .then_return(true)
294
+ #
295
+ # @param attribute [Symbol] the user attribute to match against
296
+ # @param values [Array<Object>] values to compare to
297
+ # @return [FlagRuleBuilder] a flag rule builder
298
+ #
299
+ # @see FlagRuleBuilder#then_return
300
+ # @see FlagRuleBuilder#and_match
301
+ # @see FlagRuleBuilder#and_not_match
302
+ #
303
+ def if_not_match(attribute, *values)
304
+ if_not_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
305
+ end
306
+
307
+ #
308
+ # Removes any existing targets from the flag.
309
+ # This undoes the effect of methods like {#variation_for_key}
310
+ #
311
+ # @return [FlagBuilder] the same builder
312
+ #
313
+ def clear_targets
314
+ @targets = nil
315
+ self
316
+ end
317
+
318
+ #
319
+ # @deprecated Backwards compatibility alias for #clear_targets
320
+ #
321
+ alias_method :clear_user_targets, :clear_targets
322
+
323
+ #
324
+ # Removes any existing rules from the flag.
325
+ # This undoes the effect of methods like {#if_match}
326
+ #
327
+ # @return [FlagBuilder] the same builder
328
+ #
329
+ def clear_rules
330
+ @rules = nil
331
+ self
332
+ end
333
+
334
+ # @private
335
+ def add_rule(rule)
336
+ if @rules.nil?
337
+ @rules = Array.new
338
+ end
339
+ @rules.push(rule)
340
+ self
341
+ end
342
+
343
+ #
344
+ # A shortcut for setting the flag to use the standard boolean configuration.
345
+ #
346
+ # This is the default for all new flags created with {TestData#flag}.
347
+ # The flag will have two variations, `true` and `false` (in that order);
348
+ # it will return `false` whenever targeting is off, and `true` when targeting is on
349
+ # if no other settings specify otherwise.
350
+ #
351
+ # @return [FlagBuilder] the builder
352
+ #
353
+ def boolean_flag
354
+ if boolean_flag?
355
+ self
356
+ else
357
+ variations(true, false)
358
+ .fallthrough_variation(TRUE_VARIATION_INDEX)
359
+ .off_variation(FALSE_VARIATION_INDEX)
360
+ end
361
+ end
362
+
363
+ # @private
364
+ def build(version)
365
+ res = { key: @key,
366
+ version: version,
367
+ on: @on,
368
+ variations: @variations,
369
+ }
370
+
371
+ unless @off_variation.nil?
372
+ res[:offVariation] = @off_variation
373
+ end
374
+
375
+ unless @fallthrough_variation.nil?
376
+ res[:fallthrough] = { variation: @fallthrough_variation }
377
+ end
378
+
379
+ unless @targets.nil?
380
+ targets = []
381
+ context_targets = []
382
+
383
+ @targets.each do |kind, targets_for_kind|
384
+ targets_for_kind.each_with_index do |values, variation|
385
+ next if values.nil?
386
+ if kind == LaunchDarkly::LDContext::KIND_DEFAULT
387
+ targets << { variation: variation, values: values }
388
+ context_targets << { contextKind: LaunchDarkly::LDContext::KIND_DEFAULT, variation: variation, values: [] }
389
+ else
390
+ context_targets << { contextKind: kind, variation: variation, values: values }
391
+ end
392
+ end
393
+ end
394
+
395
+ res[:targets] = targets
396
+ res[:contextTargets] = context_targets
397
+ end
398
+
399
+ unless @rules.nil?
400
+ res[:rules] = @rules.each_with_index.map { | rule, i | rule.build(i) }
401
+ end
402
+
403
+ res
404
+ end
405
+
406
+ #
407
+ # A builder for feature flag rules to be used with {FlagBuilder}.
408
+ #
409
+ # In the LaunchDarkly model, a flag can have any number of rules, and a rule can have any number of
410
+ # clauses. A clause is an individual test such as "name is 'X'". A rule matches a context if all of the
411
+ # rule's clauses match the context.
412
+ #
413
+ # To start defining a rule, use one of the flag builder's matching methods such as
414
+ # {FlagBuilder#if_match}. This defines the first clause for the rule.
415
+ # Optionally, you may add more clauses with the rule builder's methods such as
416
+ # {#and_match} or {#and_not_match}.
417
+ # Finally, call {#then_return} to finish defining the rule.
418
+ #
419
+ class FlagRuleBuilder
420
+ # @private
421
+ FlagRuleClause = Struct.new(:contextKind, :attribute, :op, :values, :negate, keyword_init: true)
422
+
423
+ # @private
424
+ def initialize(flag_builder)
425
+ @flag_builder = flag_builder
426
+ @clauses = Array.new
427
+ end
428
+
429
+ # @private
430
+ def intialize_copy(other)
431
+ super(other)
432
+ @clauses = @clauses.clone
433
+ end
434
+
435
+ #
436
+ # Adds another clause, using the "is one of" operator.
437
+ #
438
+ # @example create a rule that returns `true` if the name is "Patsy", the country is "gb", and the context kind is "user"
439
+ # testData.flag("flag")
440
+ # .if_match_context("user", :name, 'Patsy')
441
+ # .and_match_context("user", :country, 'gb')
442
+ # .then_return(true)
443
+ #
444
+ # @param context_kind [String] a context kind
445
+ # @param attribute [Symbol] the context attribute to match against
446
+ # @param values [Array<Object>] values to compare to
447
+ # @return [FlagRuleBuilder] the rule builder
448
+ #
449
+ def and_match_context(context_kind, attribute, *values)
450
+ @clauses.push(FlagRuleClause.new(
451
+ contextKind: context_kind,
452
+ attribute: attribute,
453
+ op: 'in',
454
+ values: values,
455
+ negate: false
456
+ ))
457
+ self
458
+ end
459
+
460
+ #
461
+ # Adds another clause, using the "is one of" operator.
462
+ #
463
+ # This is a shortcut for calling {and_match_context} with
464
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
465
+ #
466
+ # @example create a rule that returns `true` if the name is "Patsy" and the country is "gb"
467
+ # testData.flag("flag")
468
+ # .if_match(:name, 'Patsy')
469
+ # .and_match(:country, 'gb')
470
+ # .then_return(true)
471
+ #
472
+ # @param attribute [Symbol] the user attribute to match against
473
+ # @param values [Array<Object>] values to compare to
474
+ # @return [FlagRuleBuilder] the rule builder
475
+ #
476
+ def and_match(attribute, *values)
477
+ and_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
478
+ end
479
+
480
+ #
481
+ # Adds another clause, using the "is not one of" operator.
482
+ #
483
+ # @example create a rule that returns `true` if the name is "Patsy" and the country is not "gb"
484
+ # testData.flag("flag")
485
+ # .if_match_context("user", :name, 'Patsy')
486
+ # .and_not_match_context("user", :country, 'gb')
487
+ # .then_return(true)
488
+ #
489
+ # @param context_kind [String] a context kind
490
+ # @param attribute [Symbol] the context attribute to match against
491
+ # @param values [Array<Object>] values to compare to
492
+ # @return [FlagRuleBuilder] the rule builder
493
+ #
494
+ def and_not_match_context(context_kind, attribute, *values)
495
+ @clauses.push(FlagRuleClause.new(
496
+ contextKind: context_kind,
497
+ attribute: attribute,
498
+ op: 'in',
499
+ values: values,
500
+ negate: true
501
+ ))
502
+ self
503
+ end
504
+
505
+ #
506
+ # Adds another clause, using the "is not one of" operator.
507
+ #
508
+ # This is a shortcut for calling {and_not_match} with
509
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
510
+ #
511
+ # @example create a rule that returns `true` if the name is "Patsy" and the country is not "gb"
512
+ # testData.flag("flag")
513
+ # .if_match(:name, 'Patsy')
514
+ # .and_not_match(:country, 'gb')
515
+ # .then_return(true)
516
+ #
517
+ # @param attribute [Symbol] the user attribute to match against
518
+ # @param values [Array<Object>] values to compare to
519
+ # @return [FlagRuleBuilder] the rule builder
520
+ #
521
+ def and_not_match(attribute, *values)
522
+ and_not_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
523
+ end
524
+
525
+ #
526
+ # Finishes defining the rule, specifying the result as either a boolean
527
+ # or a variation index.
528
+ #
529
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
530
+ # this also changes it to a boolean flag.
531
+ #
532
+ # @param variation [Boolean, Integer] true or false or the desired variation index:
533
+ # 0 for the first, 1 for the second, etc.
534
+ # @return [FlagBuilder] the flag builder with this rule added
535
+ #
536
+ def then_return(variation)
537
+ if LaunchDarkly::Impl::Util.bool? variation
538
+ @variation = @flag_builder.variation_for_boolean(variation)
539
+ @flag_builder.boolean_flag.add_rule(self)
540
+ else
541
+ @variation = variation
542
+ @flag_builder.add_rule(self)
543
+ end
544
+ end
545
+
546
+ # @private
547
+ def build(ri)
548
+ {
549
+ id: 'rule' + ri.to_s,
550
+ variation: @variation,
551
+ clauses: @clauses.map(&:to_h),
552
+ }
553
+ end
554
+ end
555
+
556
+ # @private
557
+ def variation_for_boolean(variation)
558
+ variation ? TRUE_VARIATION_INDEX : FALSE_VARIATION_INDEX
559
+ end
560
+
561
+ private
562
+
563
+ TRUE_VARIATION_INDEX = 0
564
+ FALSE_VARIATION_INDEX = 1
565
+
566
+ def boolean_flag?
567
+ @variations.size == 2 &&
568
+ @variations[TRUE_VARIATION_INDEX] == true &&
569
+ @variations[FALSE_VARIATION_INDEX] == false
570
+ end
571
+
572
+ def deep_copy_hash(from)
573
+ to = Hash.new
574
+ from.each do |k, v|
575
+ if v.is_a?(Hash)
576
+ to[k] = deep_copy_hash(v)
577
+ elsif v.is_a?(Array)
578
+ to[k] = deep_copy_array(v)
579
+ else
580
+ to[k] = v.clone
581
+ end
582
+ end
583
+ to
584
+ end
585
+
586
+ def deep_copy_array(from)
587
+ to = Array.new
588
+ from.each { |v| to.push(v.clone) }
589
+ to
590
+ end
591
+ end
592
+ end
593
+ end
594
+ end