launchdarkly-server-sdk 8.8.3-java

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