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