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.
- checksums.yaml +7 -0
- data/LICENSE.txt +13 -0
- data/README.md +61 -0
- data/lib/launchdarkly-server-sdk.rb +1 -0
- data/lib/ldclient-rb/cache_store.rb +45 -0
- data/lib/ldclient-rb/config.rb +658 -0
- data/lib/ldclient-rb/context.rb +565 -0
- data/lib/ldclient-rb/evaluation_detail.rb +387 -0
- data/lib/ldclient-rb/events.rb +642 -0
- data/lib/ldclient-rb/expiring_cache.rb +77 -0
- data/lib/ldclient-rb/flags_state.rb +88 -0
- data/lib/ldclient-rb/impl/big_segments.rb +117 -0
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +166 -0
- data/lib/ldclient-rb/impl/data_source.rb +188 -0
- data/lib/ldclient-rb/impl/data_store.rb +109 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +129 -0
- data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
- data/lib/ldclient-rb/impl/evaluator.rb +539 -0
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +86 -0
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +131 -0
- data/lib/ldclient-rb/impl/event_sender.rb +100 -0
- data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
- data/lib/ldclient-rb/impl/event_types.rb +136 -0
- data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +170 -0
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +300 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +229 -0
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +306 -0
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
- data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
- data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
- data/lib/ldclient-rb/impl/model/clause.rb +45 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +254 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
- data/lib/ldclient-rb/impl/model/segment.rb +132 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +72 -0
- data/lib/ldclient-rb/impl/repeating_task.rb +46 -0
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +141 -0
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +55 -0
- data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
- data/lib/ldclient-rb/impl/util.rb +95 -0
- data/lib/ldclient-rb/impl.rb +13 -0
- data/lib/ldclient-rb/in_memory_store.rb +100 -0
- data/lib/ldclient-rb/integrations/consul.rb +45 -0
- data/lib/ldclient-rb/integrations/dynamodb.rb +92 -0
- data/lib/ldclient-rb/integrations/file_data.rb +108 -0
- data/lib/ldclient-rb/integrations/redis.rb +98 -0
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +663 -0
- data/lib/ldclient-rb/integrations/test_data.rb +213 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +246 -0
- data/lib/ldclient-rb/integrations.rb +6 -0
- data/lib/ldclient-rb/interfaces.rb +974 -0
- data/lib/ldclient-rb/ldclient.rb +822 -0
- data/lib/ldclient-rb/memoized_value.rb +32 -0
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +46 -0
- data/lib/ldclient-rb/polling.rb +102 -0
- data/lib/ldclient-rb/reference.rb +295 -0
- data/lib/ldclient-rb/requestor.rb +102 -0
- data/lib/ldclient-rb/simple_lru_cache.rb +25 -0
- data/lib/ldclient-rb/stream.rb +196 -0
- data/lib/ldclient-rb/util.rb +132 -0
- data/lib/ldclient-rb/version.rb +3 -0
- data/lib/ldclient-rb.rb +27 -0
- 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
|