launchdarkly-server-sdk 6.4.0 → 7.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ldclient-rb/config.rb +102 -56
- data/lib/ldclient-rb/context.rb +487 -0
- data/lib/ldclient-rb/evaluation_detail.rb +20 -20
- data/lib/ldclient-rb/events.rb +77 -132
- data/lib/ldclient-rb/flags_state.rb +4 -4
- data/lib/ldclient-rb/impl/big_segments.rb +17 -17
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +145 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
- data/lib/ldclient-rb/impl/evaluator.rb +379 -131
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +31 -34
- data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
- data/lib/ldclient-rb/impl/event_sender.rb +6 -6
- data/lib/ldclient-rb/impl/event_summarizer.rb +12 -7
- data/lib/ldclient-rb/impl/event_types.rb +18 -30
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +29 -29
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +92 -12
- data/lib/ldclient-rb/impl/model/clause.rb +45 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +232 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +8 -121
- data/lib/ldclient-rb/impl/model/segment.rb +132 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +52 -12
- data/lib/ldclient-rb/impl/repeating_task.rb +1 -1
- 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 +2 -2
- data/lib/ldclient-rb/in_memory_store.rb +2 -2
- data/lib/ldclient-rb/integrations/consul.rb +1 -1
- data/lib/ldclient-rb/integrations/dynamodb.rb +1 -1
- data/lib/ldclient-rb/integrations/file_data.rb +3 -3
- data/lib/ldclient-rb/integrations/redis.rb +4 -4
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +218 -62
- data/lib/ldclient-rb/integrations/test_data.rb +16 -12
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +9 -9
- data/lib/ldclient-rb/interfaces.rb +14 -14
- data/lib/ldclient-rb/ldclient.rb +94 -144
- data/lib/ldclient-rb/memoized_value.rb +1 -1
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
- data/lib/ldclient-rb/polling.rb +2 -2
- data/lib/ldclient-rb/reference.rb +274 -0
- data/lib/ldclient-rb/requestor.rb +5 -5
- data/lib/ldclient-rb/stream.rb +7 -8
- data/lib/ldclient-rb/util.rb +4 -19
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +2 -3
- metadata +34 -17
- data/lib/ldclient-rb/file_data_source.rb +0 -23
- 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
@@ -0,0 +1,487 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'ldclient-rb/impl/context'
|
3
|
+
require 'ldclient-rb/reference'
|
4
|
+
|
5
|
+
module LaunchDarkly
|
6
|
+
# LDContext is a collection of attributes that can be referenced in flag
|
7
|
+
# evaluations and analytics events.
|
8
|
+
#
|
9
|
+
# To create an LDContext of a single kind, such as a user, you may use
|
10
|
+
# {LDContext#create} or {LDContext#with_key}.
|
11
|
+
#
|
12
|
+
# To create an LDContext with multiple kinds, use {LDContext#create_multi}.
|
13
|
+
#
|
14
|
+
# Each factory method will always return an LDContext. However, that
|
15
|
+
# LDContext may be invalid. You can check the validity of the resulting
|
16
|
+
# context, and the associated errors by calling {LDContext#valid?} and
|
17
|
+
# {LDContext#error}
|
18
|
+
class LDContext
|
19
|
+
KIND_DEFAULT = "user"
|
20
|
+
KIND_MULTI = "multi"
|
21
|
+
|
22
|
+
ERR_NOT_HASH = 'context data is not a hash'
|
23
|
+
private_constant :ERR_NOT_HASH
|
24
|
+
ERR_KEY_EMPTY = 'context key must not be null or empty'
|
25
|
+
private_constant :ERR_KEY_EMPTY
|
26
|
+
ERR_KIND_MULTI_NON_CONTEXT_ARRAY = 'context data must be an array of valid LDContexts'
|
27
|
+
private_constant :ERR_KIND_MULTI_NON_CONTEXT_ARRAY
|
28
|
+
ERR_KIND_MULTI_CANNOT_CONTAIN_MULTI = 'multi-kind context cannot contain another multi-kind context'
|
29
|
+
private_constant :ERR_KIND_MULTI_CANNOT_CONTAIN_MULTI
|
30
|
+
ERR_KIND_MULTI_WITH_NO_KINDS = 'multi-context must contain at least one kind'
|
31
|
+
private_constant :ERR_KIND_MULTI_WITH_NO_KINDS
|
32
|
+
ERR_KIND_MULTI_DUPLICATES = 'multi-kind context cannot have same kind more than once'
|
33
|
+
private_constant :ERR_KIND_MULTI_DUPLICATES
|
34
|
+
ERR_CUSTOM_NON_HASH = 'context custom must be a hash'
|
35
|
+
private_constant :ERR_CUSTOM_NON_HASH
|
36
|
+
ERR_PRIVATE_NON_ARRAY = 'context private attributes must be an array'
|
37
|
+
|
38
|
+
# @return [String, nil] Returns the key for this context
|
39
|
+
attr_reader :key
|
40
|
+
|
41
|
+
# @return [String, nil] Returns the fully qualified key for this context
|
42
|
+
attr_reader :fully_qualified_key
|
43
|
+
|
44
|
+
# @return [String, nil] Returns the kind for this context
|
45
|
+
attr_reader :kind
|
46
|
+
|
47
|
+
# @return [String, nil] Returns the error associated with this LDContext if invalid
|
48
|
+
attr_reader :error
|
49
|
+
|
50
|
+
# @return [Array<Reference>] Returns the private attributes associated with this LDContext
|
51
|
+
attr_reader :private_attributes
|
52
|
+
|
53
|
+
#
|
54
|
+
# @private
|
55
|
+
# @param key [String, nil]
|
56
|
+
# @param fully_qualified_key [String, nil]
|
57
|
+
# @param kind [String, nil]
|
58
|
+
# @param name [String, nil]
|
59
|
+
# @param anonymous [Boolean, nil]
|
60
|
+
# @param attributes [Hash, nil]
|
61
|
+
# @param private_attributes [Array<String>, nil]
|
62
|
+
# @param error [String, nil]
|
63
|
+
# @param contexts [Array<LDContext>, nil]
|
64
|
+
#
|
65
|
+
def initialize(key, fully_qualified_key, kind, name = nil, anonymous = nil, attributes = nil, private_attributes = nil, error = nil, contexts = nil)
|
66
|
+
@key = key
|
67
|
+
@fully_qualified_key = fully_qualified_key
|
68
|
+
@kind = kind
|
69
|
+
@name = name
|
70
|
+
@anonymous = anonymous || false
|
71
|
+
@attributes = attributes
|
72
|
+
@private_attributes = []
|
73
|
+
(private_attributes || []).each do |attribute|
|
74
|
+
reference = Reference.create(attribute)
|
75
|
+
@private_attributes << reference if reference.error.nil?
|
76
|
+
end
|
77
|
+
@error = error
|
78
|
+
@contexts = contexts
|
79
|
+
@is_multi = !contexts.nil?
|
80
|
+
end
|
81
|
+
private_class_method :new
|
82
|
+
|
83
|
+
#
|
84
|
+
# @return [Boolean] Is this LDContext a multi-kind context?
|
85
|
+
#
|
86
|
+
def multi_kind?
|
87
|
+
@is_multi
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# @return [Boolean] Determine if this LDContext is considered valid
|
92
|
+
#
|
93
|
+
def valid?
|
94
|
+
@error.nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Returns a hash mapping each context's kind to its key.
|
99
|
+
#
|
100
|
+
# @return [Hash<Symbol, String>]
|
101
|
+
#
|
102
|
+
def keys
|
103
|
+
return {} unless valid?
|
104
|
+
return Hash[kind, key] unless multi_kind?
|
105
|
+
|
106
|
+
@contexts.map { |c| [c.kind, c.key] }.to_h
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Returns an array of context kinds.
|
111
|
+
#
|
112
|
+
# @return [Array<String>]
|
113
|
+
#
|
114
|
+
def kinds
|
115
|
+
return [] unless valid?
|
116
|
+
return [kind] unless multi_kind?
|
117
|
+
|
118
|
+
@contexts.map { |c| c.kind }
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Return an array of top level attribute keys (excluding built-in attributes)
|
123
|
+
#
|
124
|
+
# @return [Array<Symbol>]
|
125
|
+
#
|
126
|
+
def get_custom_attribute_names
|
127
|
+
return [] if @attributes.nil?
|
128
|
+
|
129
|
+
@attributes.keys
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# get_value looks up the value of any attribute of the Context by name.
|
134
|
+
# This includes only attributes that are addressable in evaluations-- not
|
135
|
+
# metadata such as private attributes.
|
136
|
+
#
|
137
|
+
# For a single-kind context, the attribute name can be any custom attribute.
|
138
|
+
# It can also be one of the built-in ones like "kind", "key", or "name".
|
139
|
+
#
|
140
|
+
# For a multi-kind context, the only supported attribute name is "kind".
|
141
|
+
# Use {#individual_context} to inspect a Context for a particular kind and
|
142
|
+
# then get its attributes.
|
143
|
+
#
|
144
|
+
# This method does not support complex expressions for getting individual
|
145
|
+
# values out of JSON objects or arrays, such as "/address/street". Use
|
146
|
+
# {#get_value_for_reference} for that purpose.
|
147
|
+
#
|
148
|
+
# If the value is found, the return value is the attribute value;
|
149
|
+
# otherwise, it is nil.
|
150
|
+
#
|
151
|
+
# @param attribute [String, Symbol]
|
152
|
+
# @return [any]
|
153
|
+
#
|
154
|
+
def get_value(attribute)
|
155
|
+
reference = Reference.create_literal(attribute)
|
156
|
+
get_value_for_reference(reference)
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# get_value_for_reference looks up the value of any attribute of the
|
161
|
+
# Context, or a value contained within an attribute, based on a {Reference}
|
162
|
+
# instance. This includes only attributes that are addressable in
|
163
|
+
# evaluations-- not metadata such as private attributes.
|
164
|
+
#
|
165
|
+
# This implements the same behavior that the SDK uses to resolve attribute
|
166
|
+
# references during a flag evaluation. In a single-kind context, the
|
167
|
+
# {Reference} can represent a simple attribute name-- either a built-in one
|
168
|
+
# like "name" or "key", or a custom attribute -- or, it can be a
|
169
|
+
# slash-delimited path using a JSON-Pointer-like syntax. See {Reference}
|
170
|
+
# for more details.
|
171
|
+
#
|
172
|
+
# For a multi-kind context, the only supported attribute name is "kind".
|
173
|
+
# Use {#individual_context} to inspect a Context for a particular kind and
|
174
|
+
# then get its attributes.
|
175
|
+
#
|
176
|
+
# If the value is found, the return value is the attribute value;
|
177
|
+
# otherwise, it is nil.
|
178
|
+
#
|
179
|
+
# @param reference [Reference]
|
180
|
+
# @return [any]
|
181
|
+
#
|
182
|
+
def get_value_for_reference(reference)
|
183
|
+
return nil unless valid?
|
184
|
+
return nil unless reference.is_a?(Reference)
|
185
|
+
return nil unless reference.error.nil?
|
186
|
+
|
187
|
+
first_component = reference.component(0)
|
188
|
+
return nil if first_component.nil?
|
189
|
+
|
190
|
+
if multi_kind?
|
191
|
+
if reference.depth == 1 && first_component == :kind
|
192
|
+
return kind
|
193
|
+
end
|
194
|
+
|
195
|
+
# Multi-kind contexts have no other addressable attributes
|
196
|
+
return nil
|
197
|
+
end
|
198
|
+
|
199
|
+
value = get_top_level_addressable_attribute_single_kind(first_component)
|
200
|
+
return nil if value.nil?
|
201
|
+
|
202
|
+
(1...reference.depth).each do |i|
|
203
|
+
name = reference.component(i)
|
204
|
+
|
205
|
+
return nil unless value.is_a?(Hash)
|
206
|
+
return nil unless value.has_key?(name)
|
207
|
+
|
208
|
+
value = value[name]
|
209
|
+
end
|
210
|
+
|
211
|
+
value
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# Returns the number of context kinds in this context.
|
216
|
+
#
|
217
|
+
# For a valid individual context, this returns 1. For a multi-context, it
|
218
|
+
# returns the number of context kinds. For an invalid context, it returns
|
219
|
+
# zero.
|
220
|
+
#
|
221
|
+
# @return [Integer] the number of context kinds
|
222
|
+
#
|
223
|
+
def individual_context_count
|
224
|
+
return 0 unless valid?
|
225
|
+
return 1 if @contexts.nil?
|
226
|
+
@contexts.count
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# Returns the single-kind LDContext corresponding to one of the kinds in
|
231
|
+
# this context.
|
232
|
+
#
|
233
|
+
# The `kind` parameter can be either a number representing a zero-based
|
234
|
+
# index, or a string representing a context kind.
|
235
|
+
#
|
236
|
+
# If this method is called on a single-kind LDContext, then the only
|
237
|
+
# allowable value for `kind` is either zero or the same value as {#kind},
|
238
|
+
# and the return value on success is the same LDContext.
|
239
|
+
#
|
240
|
+
# If the method is called on a multi-context, and `kind` is a number, it
|
241
|
+
# must be a non-negative index that is less than the number of kinds (that
|
242
|
+
# is, less than the return value of {#individual_context_count}, and the
|
243
|
+
# return value on success is one of the individual LDContexts within. Or,
|
244
|
+
# if `kind` is a string, it must match the context kind of one of the
|
245
|
+
# individual contexts.
|
246
|
+
#
|
247
|
+
# If there is no context corresponding to `kind`, the method returns nil.
|
248
|
+
#
|
249
|
+
# @param kind [Integer, String] the index or string value of a context kind
|
250
|
+
# @return [LDContext, nil] the context corresponding to that index or kind,
|
251
|
+
# or null if none.
|
252
|
+
#
|
253
|
+
def individual_context(kind)
|
254
|
+
return nil unless valid?
|
255
|
+
|
256
|
+
if kind.is_a?(Integer)
|
257
|
+
unless multi_kind?
|
258
|
+
return kind == 0 ? self : nil
|
259
|
+
end
|
260
|
+
|
261
|
+
return kind >= 0 && kind < @contexts.count ? @contexts[kind] : nil
|
262
|
+
end
|
263
|
+
|
264
|
+
return nil unless kind.is_a?(String)
|
265
|
+
|
266
|
+
unless multi_kind?
|
267
|
+
return self.kind == kind ? self : nil
|
268
|
+
end
|
269
|
+
|
270
|
+
@contexts.each do |context|
|
271
|
+
return context if context.kind == kind
|
272
|
+
end
|
273
|
+
|
274
|
+
nil
|
275
|
+
end
|
276
|
+
|
277
|
+
#
|
278
|
+
# Retrieve the value of any top level, addressable attribute.
|
279
|
+
#
|
280
|
+
# This method returns an array of two values. The first element is the
|
281
|
+
# value of the requested attribute or nil if it does not exist. The second
|
282
|
+
# value will be true if the attribute exists; otherwise, it will be false.
|
283
|
+
#
|
284
|
+
# @param name [Symbol]
|
285
|
+
# @return [any]
|
286
|
+
#
|
287
|
+
private def get_top_level_addressable_attribute_single_kind(name)
|
288
|
+
case name
|
289
|
+
when :kind
|
290
|
+
kind
|
291
|
+
when :key
|
292
|
+
key
|
293
|
+
when :name
|
294
|
+
@name
|
295
|
+
when :anonymous
|
296
|
+
@anonymous
|
297
|
+
else
|
298
|
+
@attributes&.fetch(name, nil)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
#
|
303
|
+
# Convenience method to create a simple single kind context providing only
|
304
|
+
# a key and kind type.
|
305
|
+
#
|
306
|
+
# @param key [String]
|
307
|
+
# @param kind [String]
|
308
|
+
#
|
309
|
+
def self.with_key(key, kind = KIND_DEFAULT)
|
310
|
+
create({key: key, kind: kind})
|
311
|
+
end
|
312
|
+
|
313
|
+
#
|
314
|
+
# Create a single kind context from the provided hash.
|
315
|
+
#
|
316
|
+
# The provided hash must match the format as outlined in the
|
317
|
+
# {https://docs.launchdarkly.com/sdk/features/user-config SDK
|
318
|
+
# documentation}.
|
319
|
+
#
|
320
|
+
# @param data [Hash]
|
321
|
+
# @return [LDContext]
|
322
|
+
#
|
323
|
+
def self.create(data)
|
324
|
+
return create_invalid_context(ERR_NOT_HASH) unless data.is_a?(Hash)
|
325
|
+
return create_legacy_context(data) unless data.has_key?(:kind)
|
326
|
+
|
327
|
+
kind = data[:kind]
|
328
|
+
if kind == KIND_MULTI
|
329
|
+
contexts = []
|
330
|
+
data.each do |key, value|
|
331
|
+
next if key == :kind
|
332
|
+
contexts << create_single_context(value, key.to_s)
|
333
|
+
end
|
334
|
+
|
335
|
+
return create_multi(contexts)
|
336
|
+
end
|
337
|
+
|
338
|
+
create_single_context(data, kind)
|
339
|
+
end
|
340
|
+
|
341
|
+
#
|
342
|
+
# Create a multi-kind context from the array of LDContexts provided.
|
343
|
+
#
|
344
|
+
# A multi-kind context is comprised of two or more single kind contexts.
|
345
|
+
# You cannot include a multi-kind context instead another multi-kind
|
346
|
+
# context.
|
347
|
+
#
|
348
|
+
# Additionally, the kind of each single-kind context must be unique. For
|
349
|
+
# instance, you cannot create a multi-kind context that includes two user
|
350
|
+
# kind contexts.
|
351
|
+
#
|
352
|
+
# If you attempt to create a multi-kind context from one single-kind
|
353
|
+
# context, this method will return the single-kind context instead of a new
|
354
|
+
# multi-kind context wrapping that one single-kind.
|
355
|
+
#
|
356
|
+
# @param contexts [Array<LDContext>]
|
357
|
+
# @return [LDContext]
|
358
|
+
#
|
359
|
+
def self.create_multi(contexts)
|
360
|
+
return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY) unless contexts.is_a?(Array)
|
361
|
+
return create_invalid_context(ERR_KIND_MULTI_WITH_NO_KINDS) if contexts.empty?
|
362
|
+
|
363
|
+
kinds = Set.new
|
364
|
+
contexts.each do |context|
|
365
|
+
if !context.is_a?(LDContext)
|
366
|
+
return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY)
|
367
|
+
elsif !context.valid?
|
368
|
+
return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY)
|
369
|
+
elsif context.multi_kind?
|
370
|
+
return create_invalid_context(ERR_KIND_MULTI_CANNOT_CONTAIN_MULTI)
|
371
|
+
elsif kinds.include? context.kind
|
372
|
+
return create_invalid_context(ERR_KIND_MULTI_DUPLICATES)
|
373
|
+
end
|
374
|
+
|
375
|
+
kinds.add(context.kind)
|
376
|
+
end
|
377
|
+
|
378
|
+
return contexts[0] if contexts.length == 1
|
379
|
+
|
380
|
+
full_key = contexts.sort_by(&:kind)
|
381
|
+
.map { |c| LaunchDarkly::Impl::Context::canonicalize_key_for_kind(c.kind, c.key) }
|
382
|
+
.join(":")
|
383
|
+
|
384
|
+
new(nil, full_key, "multi", nil, false, nil, nil, nil, contexts)
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
# @param error [String]
|
389
|
+
# @return [LDContext]
|
390
|
+
#
|
391
|
+
private_class_method def self.create_invalid_context(error)
|
392
|
+
new(nil, nil, nil, nil, false, nil, nil, error)
|
393
|
+
end
|
394
|
+
|
395
|
+
#
|
396
|
+
# @param data [Hash]
|
397
|
+
# @return [LDContext]
|
398
|
+
#
|
399
|
+
private_class_method def self.create_legacy_context(data)
|
400
|
+
key = data[:key]
|
401
|
+
|
402
|
+
# Legacy users are allowed to have "" as a key but they cannot have nil as a key.
|
403
|
+
return create_invalid_context(ERR_KEY_EMPTY) if key.nil?
|
404
|
+
|
405
|
+
name = data[:name]
|
406
|
+
name_error = LaunchDarkly::Impl::Context.validate_name(name)
|
407
|
+
return create_invalid_context(name_error) unless name_error.nil?
|
408
|
+
|
409
|
+
anonymous = data[:anonymous]
|
410
|
+
anonymous_error = LaunchDarkly::Impl::Context.validate_anonymous(anonymous, true)
|
411
|
+
return create_invalid_context(anonymous_error) unless anonymous_error.nil?
|
412
|
+
|
413
|
+
custom = data[:custom]
|
414
|
+
unless custom.nil? || custom.is_a?(Hash)
|
415
|
+
return create_invalid_context(ERR_CUSTOM_NON_HASH)
|
416
|
+
end
|
417
|
+
|
418
|
+
# We only need to create an attribute hash if one of these keys exist.
|
419
|
+
# Everything else is stored in dedicated instance variables.
|
420
|
+
attributes = custom.clone
|
421
|
+
data.each do |k, v|
|
422
|
+
case k
|
423
|
+
when :ip, :email, :avatar, :firstName, :lastName, :country
|
424
|
+
attributes ||= {}
|
425
|
+
attributes[k] = v.clone
|
426
|
+
else
|
427
|
+
next
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
private_attributes = data[:privateAttributeNames]
|
432
|
+
if private_attributes && !private_attributes.is_a?(Array)
|
433
|
+
return create_invalid_context(ERR_PRIVATE_NON_ARRAY)
|
434
|
+
end
|
435
|
+
|
436
|
+
new(key.to_s, key.to_s, KIND_DEFAULT, name, anonymous, attributes, private_attributes)
|
437
|
+
end
|
438
|
+
|
439
|
+
#
|
440
|
+
# @param data [Hash]
|
441
|
+
# @param kind [String]
|
442
|
+
# @return [LaunchDarkly::LDContext]
|
443
|
+
#
|
444
|
+
private_class_method def self.create_single_context(data, kind)
|
445
|
+
unless data.is_a?(Hash)
|
446
|
+
return create_invalid_context(ERR_NOT_HASH)
|
447
|
+
end
|
448
|
+
|
449
|
+
kind_error = LaunchDarkly::Impl::Context.validate_kind(kind)
|
450
|
+
return create_invalid_context(kind_error) unless kind_error.nil?
|
451
|
+
|
452
|
+
key = data[:key]
|
453
|
+
key_error = LaunchDarkly::Impl::Context.validate_key(key)
|
454
|
+
return create_invalid_context(key_error) unless key_error.nil?
|
455
|
+
|
456
|
+
name = data[:name]
|
457
|
+
name_error = LaunchDarkly::Impl::Context.validate_name(name)
|
458
|
+
return create_invalid_context(name_error) unless name_error.nil?
|
459
|
+
|
460
|
+
anonymous = data.fetch(:anonymous, false)
|
461
|
+
anonymous_error = LaunchDarkly::Impl::Context.validate_anonymous(anonymous, false)
|
462
|
+
return create_invalid_context(anonymous_error) unless anonymous_error.nil?
|
463
|
+
|
464
|
+
meta = data.fetch(:_meta, {})
|
465
|
+
private_attributes = meta[:privateAttributes]
|
466
|
+
if private_attributes && !private_attributes.is_a?(Array)
|
467
|
+
return create_invalid_context(ERR_PRIVATE_NON_ARRAY)
|
468
|
+
end
|
469
|
+
|
470
|
+
# We only need to create an attribute hash if there are keys set outside
|
471
|
+
# of the ones we store in dedicated instance variables.
|
472
|
+
attributes = nil
|
473
|
+
data.each do |k, v|
|
474
|
+
case k
|
475
|
+
when :kind, :key, :name, :anonymous, :_meta
|
476
|
+
next
|
477
|
+
else
|
478
|
+
attributes ||= {}
|
479
|
+
attributes[k] = v.clone
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
full_key = kind == LDContext::KIND_DEFAULT ? key.to_s : LaunchDarkly::Impl::Context::canonicalize_key_for_kind(kind, key.to_s)
|
484
|
+
new(key.to_s, full_key, kind, name, anonymous, attributes, private_attributes)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
@@ -12,7 +12,7 @@ module LaunchDarkly
|
|
12
12
|
# @raise [ArgumentError] if `variation_index` or `reason` is not of the correct type
|
13
13
|
def initialize(value, variation_index, reason)
|
14
14
|
raise ArgumentError.new("variation_index must be a number") if !variation_index.nil? && !(variation_index.is_a? Numeric)
|
15
|
-
raise ArgumentError.new("reason must be an EvaluationReason")
|
15
|
+
raise ArgumentError.new("reason must be an EvaluationReason") unless reason.is_a? EvaluationReason
|
16
16
|
@value = value
|
17
17
|
@variation_index = variation_index
|
18
18
|
@reason = reason
|
@@ -70,20 +70,20 @@ module LaunchDarkly
|
|
70
70
|
class EvaluationReason
|
71
71
|
# Value for {#kind} indicating that the flag was off and therefore returned its configured off value.
|
72
72
|
OFF = :OFF
|
73
|
-
|
74
|
-
# Value for {#kind} indicating that the flag was on but the
|
73
|
+
|
74
|
+
# Value for {#kind} indicating that the flag was on but the context did not match any targets or rules.
|
75
75
|
FALLTHROUGH = :FALLTHROUGH
|
76
|
-
|
77
|
-
# Value for {#kind} indicating that the
|
76
|
+
|
77
|
+
# Value for {#kind} indicating that the context key was specifically targeted for this flag.
|
78
78
|
TARGET_MATCH = :TARGET_MATCH
|
79
|
-
|
80
|
-
# Value for {#kind} indicating that the
|
79
|
+
|
80
|
+
# Value for {#kind} indicating that the context matched one of the flag's rules.
|
81
81
|
RULE_MATCH = :RULE_MATCH
|
82
|
-
|
82
|
+
|
83
83
|
# Value for {#kind} indicating that the flag was considered off because it had at least one
|
84
84
|
# prerequisite flag that either was off or did not return the desired variation.
|
85
85
|
PREREQUISITE_FAILED = :PREREQUISITE_FAILED
|
86
|
-
|
86
|
+
|
87
87
|
# Value for {#kind} indicating that the flag could not be evaluated, e.g. because it does not exist
|
88
88
|
# or due to an unexpected error. In this case the result value will be the application default value
|
89
89
|
# that the caller passed to the client. Check {#error_kind} for more details on the problem.
|
@@ -100,8 +100,8 @@ module LaunchDarkly
|
|
100
100
|
# a rule specified a nonexistent variation. An error message will always be logged in this case.
|
101
101
|
ERROR_MALFORMED_FLAG = :MALFORMED_FLAG
|
102
102
|
|
103
|
-
# Value for {#error_kind} indicating that the caller passed `nil` for the
|
104
|
-
#
|
103
|
+
# Value for {#error_kind} indicating that the caller passed `nil` for the context parameter, or the
|
104
|
+
# context was invalid.
|
105
105
|
ERROR_USER_NOT_SPECIFIED = :USER_NOT_SPECIFIED
|
106
106
|
|
107
107
|
# Value for {#error_kind} indicating that an unexpected exception stopped flag evaluation. An error
|
@@ -141,7 +141,7 @@ module LaunchDarkly
|
|
141
141
|
# querying at least one Big Segment. Otherwise it returns `nil`. Possible values are defined by
|
142
142
|
# {BigSegmentsStatus}.
|
143
143
|
#
|
144
|
-
# Big Segments are a specific kind of
|
144
|
+
# Big Segments are a specific kind of context segments. For more information, read the LaunchDarkly
|
145
145
|
# documentation: https://docs.launchdarkly.com/home/users/big-segments
|
146
146
|
# @return [Symbol]
|
147
147
|
attr_reader :big_segments_status
|
@@ -176,9 +176,9 @@ module LaunchDarkly
|
|
176
176
|
# @return [EvaluationReason]
|
177
177
|
# @raise [ArgumentError] if `rule_index` is not a number or `rule_id` is not a string
|
178
178
|
def self.rule_match(rule_index, rule_id, in_experiment=false)
|
179
|
-
raise ArgumentError.new("rule_index must be a number")
|
179
|
+
raise ArgumentError.new("rule_index must be a number") unless rule_index.is_a? Numeric
|
180
180
|
raise ArgumentError.new("rule_id must be a string") if !rule_id.nil? && !(rule_id.is_a? String) # in test data, ID could be nil
|
181
|
-
|
181
|
+
|
182
182
|
if in_experiment
|
183
183
|
er = new(:RULE_MATCH, rule_index, rule_id, nil, nil, true)
|
184
184
|
else
|
@@ -193,7 +193,7 @@ module LaunchDarkly
|
|
193
193
|
# @return [EvaluationReason]
|
194
194
|
# @raise [ArgumentError] if `prerequisite_key` is nil or not a string
|
195
195
|
def self.prerequisite_failed(prerequisite_key)
|
196
|
-
raise ArgumentError.new("prerequisite_key must be a string")
|
196
|
+
raise ArgumentError.new("prerequisite_key must be a string") unless prerequisite_key.is_a? String
|
197
197
|
new(:PREREQUISITE_FAILED, nil, nil, prerequisite_key, nil)
|
198
198
|
end
|
199
199
|
|
@@ -203,7 +203,7 @@ module LaunchDarkly
|
|
203
203
|
# @return [EvaluationReason]
|
204
204
|
# @raise [ArgumentError] if `error_kind` is not a symbol
|
205
205
|
def self.error(error_kind)
|
206
|
-
raise ArgumentError.new("error_kind must be a symbol")
|
206
|
+
raise ArgumentError.new("error_kind must be a symbol") unless error_kind.is_a? Symbol
|
207
207
|
e = @@error_instances[error_kind]
|
208
208
|
e.nil? ? make_error(error_kind) : e
|
209
209
|
end
|
@@ -279,7 +279,7 @@ module LaunchDarkly
|
|
279
279
|
else
|
280
280
|
{ kind: @kind }
|
281
281
|
end
|
282
|
-
|
282
|
+
unless @big_segments_status.nil?
|
283
283
|
ret[:bigSegmentsStatus] = @big_segments_status
|
284
284
|
end
|
285
285
|
ret
|
@@ -327,9 +327,9 @@ module LaunchDarkly
|
|
327
327
|
@kind = kind.to_sym
|
328
328
|
@rule_index = rule_index
|
329
329
|
@rule_id = rule_id
|
330
|
-
@rule_id.freeze
|
330
|
+
@rule_id.freeze unless rule_id.nil?
|
331
331
|
@prerequisite_key = prerequisite_key
|
332
|
-
@prerequisite_key.freeze
|
332
|
+
@prerequisite_key.freeze unless prerequisite_key.nil?
|
333
333
|
@error_kind = error_kind
|
334
334
|
@in_experiment = in_experiment
|
335
335
|
@big_segments_status = big_segments_status
|
@@ -348,7 +348,7 @@ module LaunchDarkly
|
|
348
348
|
ERROR_FLAG_NOT_FOUND => make_error(ERROR_FLAG_NOT_FOUND),
|
349
349
|
ERROR_MALFORMED_FLAG => make_error(ERROR_MALFORMED_FLAG),
|
350
350
|
ERROR_USER_NOT_SPECIFIED => make_error(ERROR_USER_NOT_SPECIFIED),
|
351
|
-
ERROR_EXCEPTION => make_error(ERROR_EXCEPTION)
|
351
|
+
ERROR_EXCEPTION => make_error(ERROR_EXCEPTION),
|
352
352
|
}
|
353
353
|
end
|
354
354
|
|