launchdarkly-server-sdk 6.4.0 → 7.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/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 +28 -31
- 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 +39 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +213 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +8 -121
- data/lib/ldclient-rb/impl/model/segment.rb +126 -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
|
|