launchdarkly-server-sdk 6.3.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -4
- data/lib/ldclient-rb/config.rb +112 -62
- data/lib/ldclient-rb/context.rb +444 -0
- data/lib/ldclient-rb/evaluation_detail.rb +26 -22
- data/lib/ldclient-rb/events.rb +256 -146
- data/lib/ldclient-rb/flags_state.rb +26 -15
- data/lib/ldclient-rb/impl/big_segments.rb +18 -18
- 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 +145 -0
- data/lib/ldclient-rb/impl/data_source.rb +188 -0
- data/lib/ldclient-rb/impl/data_store.rb +59 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
- data/lib/ldclient-rb/impl/evaluator.rb +386 -142
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
- data/lib/ldclient-rb/impl/event_sender.rb +7 -6
- 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 +19 -7
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +38 -30
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +24 -11
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +109 -12
- 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 +255 -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 +54 -44
- data/lib/ldclient-rb/impl/repeating_task.rb +3 -4
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
- data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
- data/lib/ldclient-rb/impl/util.rb +59 -1
- data/lib/ldclient-rb/in_memory_store.rb +9 -2
- data/lib/ldclient-rb/integrations/consul.rb +2 -2
- data/lib/ldclient-rb/integrations/dynamodb.rb +2 -2
- data/lib/ldclient-rb/integrations/file_data.rb +4 -4
- data/lib/ldclient-rb/integrations/redis.rb +5 -5
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +287 -62
- data/lib/ldclient-rb/integrations/test_data.rb +18 -14
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +20 -9
- data/lib/ldclient-rb/interfaces.rb +600 -14
- data/lib/ldclient-rb/ldclient.rb +314 -134
- data/lib/ldclient-rb/memoized_value.rb +1 -1
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
- data/lib/ldclient-rb/polling.rb +52 -6
- data/lib/ldclient-rb/reference.rb +274 -0
- data/lib/ldclient-rb/requestor.rb +9 -11
- data/lib/ldclient-rb/stream.rb +96 -34
- data/lib/ldclient-rb/util.rb +97 -14
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +3 -4
- metadata +65 -23
- data/lib/ldclient-rb/event_summarizer.rb +0 -55
- data/lib/ldclient-rb/file_data_source.rb +0 -23
- data/lib/ldclient-rb/impl/event_factory.rb +0 -126
- data/lib/ldclient-rb/newrelic.rb +0 -17
- data/lib/ldclient-rb/redis_store.rb +0 -88
- data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -1,9 +1,94 @@
|
|
1
1
|
require "ldclient-rb/evaluation_detail"
|
2
2
|
require "ldclient-rb/impl/evaluator_bucketing"
|
3
|
+
require "ldclient-rb/impl/evaluator_helpers"
|
3
4
|
require "ldclient-rb/impl/evaluator_operators"
|
5
|
+
require "ldclient-rb/impl/model/feature_flag"
|
6
|
+
require "ldclient-rb/impl/model/segment"
|
4
7
|
|
5
8
|
module LaunchDarkly
|
6
9
|
module Impl
|
10
|
+
# Used internally to record that we evaluated a prerequisite flag.
|
11
|
+
PrerequisiteEvalRecord = Struct.new(
|
12
|
+
:prereq_flag, # the prerequisite flag that we evaluated
|
13
|
+
:prereq_of_flag, # the flag that it was a prerequisite of
|
14
|
+
:detail # the EvaluationDetail representing the evaluation result
|
15
|
+
)
|
16
|
+
|
17
|
+
class EvaluationException < StandardError
|
18
|
+
def initialize(msg, error_kind = EvaluationReason::ERROR_MALFORMED_FLAG)
|
19
|
+
super(msg)
|
20
|
+
@error_kind = error_kind
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Symbol]
|
24
|
+
attr_reader :error_kind
|
25
|
+
end
|
26
|
+
|
27
|
+
class InvalidReferenceException < EvaluationException
|
28
|
+
end
|
29
|
+
|
30
|
+
class EvaluatorState
|
31
|
+
# @param original_flag [LaunchDarkly::Impl::Model::FeatureFlag]
|
32
|
+
def initialize(original_flag)
|
33
|
+
@prereq_stack = EvaluatorStack.new(original_flag.key)
|
34
|
+
@segment_stack = EvaluatorStack.new(nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :prereq_stack
|
38
|
+
attr_reader :segment_stack
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# A helper class for managing cycle detection.
|
43
|
+
#
|
44
|
+
# Each time a method sees a new flag or segment, they can push that
|
45
|
+
# object's key onto the stack. Once processing for that object has
|
46
|
+
# finished, you can call pop to remove it.
|
47
|
+
#
|
48
|
+
# Because the most common use case would be a flag or segment without ANY
|
49
|
+
# prerequisites, this stack has a small optimization in place-- the stack
|
50
|
+
# is not created until absolutely necessary.
|
51
|
+
#
|
52
|
+
class EvaluatorStack
|
53
|
+
# @param original [String, nil]
|
54
|
+
def initialize(original)
|
55
|
+
@original = original
|
56
|
+
# @type [Array<String>, nil]
|
57
|
+
@stack = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param key [String]
|
61
|
+
def push(key)
|
62
|
+
# No need to store the key if we already have a record in our instance
|
63
|
+
# variable.
|
64
|
+
return if @original == key
|
65
|
+
|
66
|
+
# The common use case is that flags/segments won't have prereqs, so we
|
67
|
+
# don't allocate the stack memory until we absolutely must.
|
68
|
+
if @stack.nil?
|
69
|
+
@stack = []
|
70
|
+
end
|
71
|
+
|
72
|
+
@stack.push(key)
|
73
|
+
end
|
74
|
+
|
75
|
+
def pop
|
76
|
+
return if @stack.nil? || @stack.empty?
|
77
|
+
@stack.pop
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# @param key [String]
|
82
|
+
# @return [Boolean]
|
83
|
+
#
|
84
|
+
def include?(key)
|
85
|
+
return true if key == @original
|
86
|
+
return false if @stack.nil?
|
87
|
+
|
88
|
+
@stack.include? key
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
7
92
|
# Encapsulates the feature flag evaluation logic. The Evaluator has no knowledge of the rest of the SDK environment;
|
8
93
|
# if it needs to retrieve flags or segments that are referenced by a flag, it does so through a simple function that
|
9
94
|
# is provided in the constructor. It also produces feature requests as appropriate for any referenced prerequisite
|
@@ -14,7 +99,7 @@ module LaunchDarkly
|
|
14
99
|
# @param get_flag [Function] called if the Evaluator needs to query a different flag from the one that it is
|
15
100
|
# currently evaluating (i.e. a prerequisite flag); takes a single parameter, the flag key, and returns the
|
16
101
|
# flag data - or nil if the flag is unknown or deleted
|
17
|
-
# @param get_segment [Function] similar to `get_flag`, but is used to query a
|
102
|
+
# @param get_segment [Function] similar to `get_flag`, but is used to query a context segment.
|
18
103
|
# @param logger [Logger] the client's logger
|
19
104
|
def initialize(get_flag, get_segment, get_big_segments_membership, logger)
|
20
105
|
@get_flag = get_flag
|
@@ -29,12 +114,12 @@ module LaunchDarkly
|
|
29
114
|
# or retaining it anywhere, we don't have to be quite as strict about immutability.
|
30
115
|
#
|
31
116
|
# The big_segments_status and big_segments_membership properties are not used by the caller; they are used
|
32
|
-
# during an evaluation to cache the result of any Big Segments query that we've done for this
|
33
|
-
# we don't want to do multiple queries for the same
|
117
|
+
# during an evaluation to cache the result of any Big Segments query that we've done for this context, because
|
118
|
+
# we don't want to do multiple queries for the same context if multiple Big Segments are referenced in the same
|
34
119
|
# evaluation.
|
35
120
|
EvalResult = Struct.new(
|
36
121
|
:detail, # the EvaluationDetail representing the evaluation result
|
37
|
-
:
|
122
|
+
:prereq_evals, # an array of PrerequisiteEvalRecord instances, or nil
|
38
123
|
:big_segments_status,
|
39
124
|
:big_segments_membership
|
40
125
|
)
|
@@ -48,235 +133,394 @@ module LaunchDarkly
|
|
48
133
|
# any events that were generated for prerequisite flags; its `value` will be `nil` if the flag returns the
|
49
134
|
# default value. Error conditions produce a result with a nil value and an error reason, not an exception.
|
50
135
|
#
|
51
|
-
# @param flag [
|
52
|
-
# @param
|
53
|
-
# @
|
54
|
-
|
55
|
-
|
56
|
-
|
136
|
+
# @param flag [LaunchDarkly::Impl::Model::FeatureFlag] the flag
|
137
|
+
# @param context [LaunchDarkly::LDContext] the evaluation context
|
138
|
+
# @return [EvalResult] the evaluation result
|
139
|
+
def evaluate(flag, context)
|
140
|
+
state = EvaluatorState.new(flag)
|
141
|
+
|
57
142
|
result = EvalResult.new
|
58
|
-
|
59
|
-
|
143
|
+
begin
|
144
|
+
detail = eval_internal(flag, context, result, state)
|
145
|
+
rescue EvaluationException => exn
|
146
|
+
LaunchDarkly::Util.log_exception(@logger, "Unexpected error when evaluating flag #{flag.key}", exn)
|
147
|
+
result.detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(exn.error_kind))
|
148
|
+
return result
|
149
|
+
rescue => exn
|
150
|
+
LaunchDarkly::Util.log_exception(@logger, "Unexpected error when evaluating flag #{flag.key}", exn)
|
151
|
+
result.detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION))
|
60
152
|
return result
|
61
153
|
end
|
62
|
-
|
63
|
-
|
64
|
-
if !result.big_segments_status.nil?
|
154
|
+
|
155
|
+
unless result.big_segments_status.nil?
|
65
156
|
# If big_segments_status is non-nil at the end of the evaluation, it means a query was done at
|
66
157
|
# some point and we will want to include the status in the evaluation reason.
|
67
158
|
detail = EvaluationDetail.new(detail.value, detail.variation_index,
|
68
159
|
detail.reason.with_big_segments_status(result.big_segments_status))
|
69
160
|
end
|
70
161
|
result.detail = detail
|
71
|
-
|
162
|
+
result
|
72
163
|
end
|
73
164
|
|
165
|
+
# @param segment [LaunchDarkly::Impl::Model::Segment]
|
74
166
|
def self.make_big_segment_ref(segment) # method is visible for testing
|
75
167
|
# The format of Big Segment references is independent of what store implementation is being
|
76
168
|
# used; the store implementation receives only this string and does not know the details of
|
77
169
|
# the data model. The Relay Proxy will use the same format when writing to the store.
|
78
|
-
"#{segment
|
170
|
+
"#{segment.key}.g#{segment.generation}"
|
79
171
|
end
|
80
172
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
173
|
+
# @param flag [LaunchDarkly::Impl::Model::FeatureFlag] the flag
|
174
|
+
# @param context [LaunchDarkly::LDContext] the evaluation context
|
175
|
+
# @param eval_result [EvalResult]
|
176
|
+
# @param state [EvaluatorState]
|
177
|
+
# @raise [EvaluationException]
|
178
|
+
private def eval_internal(flag, context, eval_result, state)
|
179
|
+
unless flag.on
|
180
|
+
return flag.off_result
|
86
181
|
end
|
87
182
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
183
|
+
prereq_failure_result = check_prerequisites(flag, context, eval_result, state)
|
184
|
+
return prereq_failure_result unless prereq_failure_result.nil?
|
185
|
+
|
186
|
+
# Check context target matches
|
187
|
+
target_result = check_targets(context, flag)
|
188
|
+
return target_result unless target_result.nil?
|
92
189
|
|
93
|
-
# Check user target matches
|
94
|
-
(flag[:targets] || []).each do |target|
|
95
|
-
(target[:values] || []).each do |value|
|
96
|
-
if value == user[:key]
|
97
|
-
return get_variation(flag, target[:variation], EvaluationReason::target_match)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
190
|
# Check custom rules
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
if rule_match_user(rule, user, state)
|
107
|
-
reason = rule[:_reason] # try to use cached reason for this rule
|
108
|
-
reason = EvaluationReason::rule_match(i, rule[:id]) if reason.nil?
|
109
|
-
return get_value_for_variation_or_rollout(flag, rule, user, reason)
|
191
|
+
flag.rules.each do |rule|
|
192
|
+
if rule_match_context(rule, context, eval_result, state)
|
193
|
+
return get_value_for_variation_or_rollout(flag, rule.variation_or_rollout, context, rule.match_results)
|
110
194
|
end
|
111
195
|
end
|
112
196
|
|
113
197
|
# Check the fallthrough rule
|
114
|
-
|
115
|
-
return get_value_for_variation_or_rollout(flag, flag
|
198
|
+
unless flag.fallthrough.nil?
|
199
|
+
return get_value_for_variation_or_rollout(flag, flag.fallthrough, context, flag.fallthrough_results)
|
116
200
|
end
|
117
201
|
|
118
|
-
|
202
|
+
EvaluationDetail.new(nil, nil, EvaluationReason::fallthrough)
|
119
203
|
end
|
120
204
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
205
|
+
# @param flag [LaunchDarkly::Impl::Model::FeatureFlag] the flag
|
206
|
+
# @param context [LaunchDarkly::LDContext] the evaluation context
|
207
|
+
# @param eval_result [EvalResult]
|
208
|
+
# @param state [EvaluatorState]
|
209
|
+
# @raise [EvaluationException] if a flag prereq cycle is detected
|
210
|
+
private def check_prerequisites(flag, context, eval_result, state)
|
211
|
+
return if flag.prerequisites.empty?
|
126
212
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
213
|
+
state.prereq_stack.push(flag.key)
|
214
|
+
|
215
|
+
begin
|
216
|
+
flag.prerequisites.each do |prerequisite|
|
217
|
+
prereq_ok = true
|
218
|
+
prereq_key = prerequisite.key
|
219
|
+
|
220
|
+
if state.prereq_stack.include?(prereq_key)
|
221
|
+
raise LaunchDarkly::Impl::EvaluationException.new(
|
222
|
+
"prerequisite relationship to \"#{prereq_key}\" caused a circular reference; this is probably a temporary condition due to an incomplete update"
|
223
|
+
)
|
224
|
+
end
|
225
|
+
|
226
|
+
prereq_flag = @get_flag.call(prereq_key)
|
227
|
+
|
228
|
+
if prereq_flag.nil?
|
229
|
+
@logger.error { "[LDClient] Could not retrieve prerequisite flag \"#{prereq_key}\" when evaluating \"#{flag.key}\"" }
|
230
|
+
prereq_ok = false
|
231
|
+
else
|
232
|
+
prereq_res = eval_internal(prereq_flag, context, eval_result, state)
|
133
233
|
# Note that if the prerequisite flag is off, we don't consider it a match no matter what its
|
134
234
|
# off variation was. But we still need to evaluate it in order to generate an event.
|
135
|
-
if !prereq_flag
|
235
|
+
if !prereq_flag.on || prereq_res.variation_index != prerequisite.variation
|
136
236
|
prereq_ok = false
|
137
237
|
end
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
238
|
+
prereq_eval = PrerequisiteEvalRecord.new(prereq_flag, flag, prereq_res)
|
239
|
+
eval_result.prereq_evals = [] if eval_result.prereq_evals.nil?
|
240
|
+
eval_result.prereq_evals.push(prereq_eval)
|
241
|
+
end
|
242
|
+
|
243
|
+
unless prereq_ok
|
244
|
+
return prerequisite.failure_result
|
144
245
|
end
|
145
246
|
end
|
146
|
-
|
147
|
-
|
148
|
-
return reason.nil? ? EvaluationReason::prerequisite_failed(prereq_key) : reason
|
149
|
-
end
|
247
|
+
ensure
|
248
|
+
state.prereq_stack.pop
|
150
249
|
end
|
250
|
+
|
151
251
|
nil
|
152
252
|
end
|
153
253
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
254
|
+
# @param rule [LaunchDarkly::Impl::Model::FlagRule]
|
255
|
+
# @param context [LaunchDarkly::LDContext]
|
256
|
+
# @param eval_result [EvalResult]
|
257
|
+
# @param state [EvaluatorState]
|
258
|
+
# @raise [InvalidReferenceException]
|
259
|
+
private def rule_match_context(rule, context, eval_result, state)
|
260
|
+
rule.clauses.each do |clause|
|
261
|
+
return false unless clause_match_context(clause, context, eval_result, state)
|
159
262
|
end
|
160
263
|
|
161
|
-
|
264
|
+
true
|
162
265
|
end
|
163
266
|
|
164
|
-
|
165
|
-
|
267
|
+
# @param clause [LaunchDarkly::Impl::Model::Clause]
|
268
|
+
# @param context [LaunchDarkly::LDContext]
|
269
|
+
# @param eval_result [EvalResult]
|
270
|
+
# @param state [EvaluatorState]
|
271
|
+
# @raise [InvalidReferenceException]
|
272
|
+
private def clause_match_context(clause, context, eval_result, state)
|
273
|
+
# In the case of a segment match operator, we check if the context is in any of the segments,
|
166
274
|
# and possibly negate
|
167
|
-
if clause
|
168
|
-
result =
|
275
|
+
if clause.op == :segmentMatch
|
276
|
+
result = clause.values.any? { |v|
|
277
|
+
if state.segment_stack.include?(v)
|
278
|
+
raise LaunchDarkly::Impl::EvaluationException.new(
|
279
|
+
"segment rule referencing segment \"#{v}\" caused a circular reference; this is probably a temporary condition due to an incomplete update"
|
280
|
+
)
|
281
|
+
end
|
282
|
+
|
169
283
|
segment = @get_segment.call(v)
|
170
|
-
!segment.nil? &&
|
284
|
+
!segment.nil? && segment_match_context(segment, context, eval_result, state)
|
171
285
|
}
|
172
|
-
clause
|
286
|
+
clause.negate ? !result : result
|
173
287
|
else
|
174
|
-
|
288
|
+
clause_match_context_no_segments(clause, context)
|
175
289
|
end
|
176
290
|
end
|
177
291
|
|
178
|
-
|
179
|
-
|
180
|
-
|
292
|
+
# @param clause [LaunchDarkly::Impl::Model::Clause]
|
293
|
+
# @param context_value [any]
|
294
|
+
# @return [Boolean]
|
295
|
+
private def match_any_clause_value(clause, context_value)
|
296
|
+
op = clause.op
|
297
|
+
clause.values.any? { |cv| EvaluatorOperators.apply(op, context_value, cv) }
|
298
|
+
end
|
299
|
+
|
300
|
+
# @param clause [LaunchDarkly::Impl::Model::Clause]
|
301
|
+
# @param context [LaunchDarkly::LDContext]
|
302
|
+
# @return [Boolean]
|
303
|
+
private def clause_match_by_kind(clause, context)
|
304
|
+
# If attribute is "kind", then we treat operator and values as a match
|
305
|
+
# expression against a list of all individual kinds in the context.
|
306
|
+
# That is, for a multi-kind context with kinds of "org" and "user", it
|
307
|
+
# is a match if either of those strings is a match with Operator and
|
308
|
+
# Values.
|
181
309
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
310
|
+
(0...context.individual_context_count).each do |i|
|
311
|
+
c = context.individual_context(i)
|
312
|
+
if !c.nil? && match_any_clause_value(clause, c.kind)
|
313
|
+
return true
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
false
|
318
|
+
end
|
319
|
+
|
320
|
+
# @param clause [LaunchDarkly::Impl::Model::Clause]
|
321
|
+
# @param context [LaunchDarkly::LDContext]
|
322
|
+
# @return [Boolean]
|
323
|
+
# @raise [InvalidReferenceException] Raised if the clause.attribute is an invalid reference
|
324
|
+
private def clause_match_context_no_segments(clause, context)
|
325
|
+
raise InvalidReferenceException.new(clause.attribute.error) unless clause.attribute.error.nil?
|
326
|
+
|
327
|
+
if clause.attribute.depth == 1 && clause.attribute.component(0) == :kind
|
328
|
+
result = clause_match_by_kind(clause, context)
|
329
|
+
return clause.negate ? !result : result
|
330
|
+
end
|
331
|
+
|
332
|
+
matched_context = context.individual_context(clause.context_kind || LaunchDarkly::LDContext::KIND_DEFAULT)
|
333
|
+
return false if matched_context.nil?
|
334
|
+
|
335
|
+
context_val = matched_context.get_value_for_reference(clause.attribute)
|
336
|
+
return false if context_val.nil?
|
337
|
+
|
338
|
+
result = if context_val.is_a? Enumerable
|
339
|
+
context_val.any? { |uv| match_any_clause_value(clause, uv) }
|
186
340
|
else
|
187
|
-
|
341
|
+
match_any_clause_value(clause, context_val)
|
188
342
|
end
|
189
|
-
clause
|
343
|
+
clause.negate ? !result : result
|
190
344
|
end
|
191
345
|
|
192
|
-
|
193
|
-
|
194
|
-
|
346
|
+
# @param segment [LaunchDarkly::Impl::Model::Segment]
|
347
|
+
# @param context [LaunchDarkly::LDContext]
|
348
|
+
# @param eval_result [EvalResult]
|
349
|
+
# @param state [EvaluatorState]
|
350
|
+
# @return [Boolean]
|
351
|
+
private def segment_match_context(segment, context, eval_result, state)
|
352
|
+
return big_segment_match_context(segment, context, eval_result, state) if segment.unbounded
|
353
|
+
|
354
|
+
simple_segment_match_context(segment, context, true, eval_result, state)
|
195
355
|
end
|
196
356
|
|
197
|
-
|
198
|
-
|
357
|
+
# @param segment [LaunchDarkly::Impl::Model::Segment]
|
358
|
+
# @param context [LaunchDarkly::LDContext]
|
359
|
+
# @param eval_result [EvalResult]
|
360
|
+
# @param state [EvaluatorState]
|
361
|
+
# @return [Boolean]
|
362
|
+
private def big_segment_match_context(segment, context, eval_result, state)
|
363
|
+
unless segment.generation
|
199
364
|
# Big segment queries can only be done if the generation is known. If it's unset,
|
200
365
|
# that probably means the data store was populated by an older SDK that doesn't know
|
201
366
|
# about the generation property and therefore dropped it from the JSON data. We'll treat
|
202
367
|
# that as a "not configured" condition.
|
203
|
-
|
368
|
+
eval_result.big_segments_status = BigSegmentsStatus::NOT_CONFIGURED
|
204
369
|
return false
|
205
370
|
end
|
206
|
-
|
207
|
-
|
371
|
+
|
372
|
+
matched_context = context.individual_context(segment.unbounded_context_kind)
|
373
|
+
return false if matched_context.nil?
|
374
|
+
|
375
|
+
membership = eval_result.big_segments_membership.nil? ? nil : eval_result.big_segments_membership[matched_context.key]
|
376
|
+
|
377
|
+
if membership.nil?
|
378
|
+
# Note that this query is just by key; the context kind doesn't matter because any given
|
379
|
+
# Big Segment can only reference one context kind. So if segment A for the "user" kind
|
380
|
+
# includes a "user" context with key X, and segment B for the "org" kind includes an "org"
|
381
|
+
# context with the same key X, it is fine to say that the membership for key X is
|
382
|
+
# segment A and segment B-- there is no ambiguity.
|
383
|
+
result = @get_big_segments_membership.nil? ? nil : @get_big_segments_membership.call(matched_context.key)
|
208
384
|
if result
|
209
|
-
|
210
|
-
|
385
|
+
eval_result.big_segments_status = result.status
|
386
|
+
|
387
|
+
membership = result.membership
|
388
|
+
eval_result.big_segments_membership = {} if eval_result.big_segments_membership.nil?
|
389
|
+
eval_result.big_segments_membership[matched_context.key] = membership
|
211
390
|
else
|
212
|
-
|
213
|
-
state.big_segments_status = BigSegmentsStatus::NOT_CONFIGURED
|
391
|
+
eval_result.big_segments_status = BigSegmentsStatus::NOT_CONFIGURED
|
214
392
|
end
|
215
393
|
end
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
394
|
+
|
395
|
+
membership_result = nil
|
396
|
+
unless membership.nil?
|
397
|
+
segment_ref = Evaluator.make_big_segment_ref(segment)
|
398
|
+
membership_result = membership.nil? ? nil : membership[segment_ref]
|
399
|
+
end
|
400
|
+
|
401
|
+
return membership_result unless membership_result.nil?
|
402
|
+
simple_segment_match_context(segment, context, false, eval_result, state)
|
221
403
|
end
|
222
404
|
|
223
|
-
|
405
|
+
# @param segment [LaunchDarkly::Impl::Model::Segment]
|
406
|
+
# @param context [LaunchDarkly::LDContext]
|
407
|
+
# @param use_includes_and_excludes [Boolean]
|
408
|
+
# @param state [EvaluatorState]
|
409
|
+
# @return [Boolean]
|
410
|
+
private def simple_segment_match_context(segment, context, use_includes_and_excludes, eval_result, state)
|
224
411
|
if use_includes_and_excludes
|
225
|
-
|
226
|
-
|
412
|
+
if EvaluatorHelpers.context_key_in_target_list(context, nil, segment.included)
|
413
|
+
return true
|
414
|
+
end
|
415
|
+
|
416
|
+
segment.included_contexts.each do |target|
|
417
|
+
if EvaluatorHelpers.context_key_in_target_list(context, target.context_kind, target.values)
|
418
|
+
return true
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
if EvaluatorHelpers.context_key_in_target_list(context, nil, segment.excluded)
|
423
|
+
return false
|
424
|
+
end
|
425
|
+
|
426
|
+
segment.excluded_contexts.each do |target|
|
427
|
+
if EvaluatorHelpers.context_key_in_target_list(context, target.context_kind, target.values)
|
428
|
+
return false
|
429
|
+
end
|
430
|
+
end
|
227
431
|
end
|
228
432
|
|
229
|
-
|
230
|
-
|
433
|
+
rules = segment.rules
|
434
|
+
state.segment_stack.push(segment.key) unless rules.empty?
|
435
|
+
|
436
|
+
begin
|
437
|
+
rules.each do |r|
|
438
|
+
return true if segment_rule_match_context(r, context, segment.key, segment.salt, eval_result, state)
|
439
|
+
end
|
440
|
+
ensure
|
441
|
+
state.segment_stack.pop
|
231
442
|
end
|
232
443
|
|
233
|
-
|
444
|
+
false
|
234
445
|
end
|
235
446
|
|
236
|
-
|
237
|
-
|
238
|
-
|
447
|
+
# @param rule [LaunchDarkly::Impl::Model::SegmentRule]
|
448
|
+
# @param context [LaunchDarkly::LDContext]
|
449
|
+
# @param segment_key [String]
|
450
|
+
# @param salt [String]
|
451
|
+
# @return [Boolean]
|
452
|
+
# @raise [InvalidReferenceException]
|
453
|
+
private def segment_rule_match_context(rule, context, segment_key, salt, eval_result, state)
|
454
|
+
rule.clauses.each do |c|
|
455
|
+
return false unless clause_match_context(c, context, eval_result, state)
|
239
456
|
end
|
240
457
|
|
241
458
|
# If the weight is absent, this rule matches
|
242
|
-
return true
|
243
|
-
|
459
|
+
return true unless rule.weight
|
460
|
+
|
244
461
|
# All of the clauses are met. See if the user buckets in
|
245
|
-
|
246
|
-
|
247
|
-
|
462
|
+
begin
|
463
|
+
bucket = EvaluatorBucketing.bucket_context(context, rule.rollout_context_kind, segment_key, rule.bucket_by || "key", salt, nil)
|
464
|
+
rescue InvalidReferenceException
|
465
|
+
return false
|
466
|
+
end
|
467
|
+
|
468
|
+
weight = rule.weight.to_f / 100000.0
|
469
|
+
bucket.nil? || bucket < weight
|
248
470
|
end
|
249
471
|
|
250
|
-
private
|
472
|
+
private def get_value_for_variation_or_rollout(flag, vr, context, precomputed_results)
|
473
|
+
index, in_experiment = EvaluatorBucketing.variation_index_for_context(flag, vr, context)
|
251
474
|
|
252
|
-
|
253
|
-
|
254
|
-
@logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": invalid variation index")
|
475
|
+
if index.nil?
|
476
|
+
@logger.error("[LDClient] Data inconsistency in feature flag \"#{flag.key}\": variation/rollout object with no variation or rollout")
|
255
477
|
return Evaluator.error_result(EvaluationReason::ERROR_MALFORMED_FLAG)
|
256
478
|
end
|
257
|
-
|
479
|
+
precomputed_results.for_variation(index, in_experiment)
|
258
480
|
end
|
259
481
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
482
|
+
# @param [LaunchDarkly::LDContext] context
|
483
|
+
# @param [LaunchDarkly::Impl::Model::FeatureFlag] flag
|
484
|
+
# @return [LaunchDarkly::EvaluationDetail, nil]
|
485
|
+
private def check_targets(context, flag)
|
486
|
+
targets = flag.targets
|
487
|
+
context_targets = flag.context_targets
|
488
|
+
|
489
|
+
if context_targets.empty?
|
490
|
+
unless targets.empty?
|
491
|
+
user_context = context.individual_context(LDContext::KIND_DEFAULT)
|
492
|
+
return nil if user_context.nil?
|
266
493
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
494
|
+
targets.each do |target|
|
495
|
+
if target.values.include?(user_context.key) # rubocop:disable Performance/InefficientHashSearch
|
496
|
+
return target.match_result
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
return nil
|
274
502
|
end
|
275
|
-
|
276
|
-
|
277
|
-
|
503
|
+
|
504
|
+
context_targets.each do |target|
|
505
|
+
if target.kind == LDContext::KIND_DEFAULT
|
506
|
+
user_context = context.individual_context(LDContext::KIND_DEFAULT)
|
507
|
+
next if user_context.nil?
|
508
|
+
|
509
|
+
user_key = user_context.key
|
510
|
+
targets.each do |user_target|
|
511
|
+
if user_target.variation == target.variation
|
512
|
+
if user_target.values.include?(user_key) # rubocop:disable Performance/InefficientHashSearch
|
513
|
+
return target.match_result
|
514
|
+
end
|
515
|
+
break
|
516
|
+
end
|
517
|
+
end
|
518
|
+
elsif EvaluatorHelpers.context_key_in_target_list(context, target.kind, target.values)
|
519
|
+
return target.match_result
|
520
|
+
end
|
278
521
|
end
|
279
|
-
|
522
|
+
|
523
|
+
nil
|
280
524
|
end
|
281
525
|
end
|
282
526
|
end
|