launchdarkly-server-sdk 6.2.5 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
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