launchdarkly-server-sdk 8.8.3-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +13 -0
- data/README.md +61 -0
- data/lib/launchdarkly-server-sdk.rb +1 -0
- data/lib/ldclient-rb/cache_store.rb +45 -0
- data/lib/ldclient-rb/config.rb +658 -0
- data/lib/ldclient-rb/context.rb +565 -0
- data/lib/ldclient-rb/evaluation_detail.rb +387 -0
- data/lib/ldclient-rb/events.rb +642 -0
- data/lib/ldclient-rb/expiring_cache.rb +77 -0
- data/lib/ldclient-rb/flags_state.rb +88 -0
- data/lib/ldclient-rb/impl/big_segments.rb +117 -0
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +166 -0
- data/lib/ldclient-rb/impl/data_source.rb +188 -0
- data/lib/ldclient-rb/impl/data_store.rb +109 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +129 -0
- data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
- data/lib/ldclient-rb/impl/evaluator.rb +539 -0
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +86 -0
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +131 -0
- data/lib/ldclient-rb/impl/event_sender.rb +100 -0
- data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
- data/lib/ldclient-rb/impl/event_types.rb +136 -0
- data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +170 -0
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +300 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +229 -0
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +306 -0
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
- data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
- data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
- data/lib/ldclient-rb/impl/model/clause.rb +45 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +254 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
- data/lib/ldclient-rb/impl/model/segment.rb +132 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +72 -0
- data/lib/ldclient-rb/impl/repeating_task.rb +46 -0
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +141 -0
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +55 -0
- data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
- data/lib/ldclient-rb/impl/util.rb +95 -0
- data/lib/ldclient-rb/impl.rb +13 -0
- data/lib/ldclient-rb/in_memory_store.rb +100 -0
- data/lib/ldclient-rb/integrations/consul.rb +45 -0
- data/lib/ldclient-rb/integrations/dynamodb.rb +92 -0
- data/lib/ldclient-rb/integrations/file_data.rb +108 -0
- data/lib/ldclient-rb/integrations/redis.rb +98 -0
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +663 -0
- data/lib/ldclient-rb/integrations/test_data.rb +213 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +246 -0
- data/lib/ldclient-rb/integrations.rb +6 -0
- data/lib/ldclient-rb/interfaces.rb +974 -0
- data/lib/ldclient-rb/ldclient.rb +822 -0
- data/lib/ldclient-rb/memoized_value.rb +32 -0
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +46 -0
- data/lib/ldclient-rb/polling.rb +102 -0
- data/lib/ldclient-rb/reference.rb +295 -0
- data/lib/ldclient-rb/requestor.rb +102 -0
- data/lib/ldclient-rb/simple_lru_cache.rb +25 -0
- data/lib/ldclient-rb/stream.rb +196 -0
- data/lib/ldclient-rb/util.rb +132 -0
- data/lib/ldclient-rb/version.rb +3 -0
- data/lib/ldclient-rb.rb +27 -0
- metadata +400 -0
@@ -0,0 +1,565 @@
|
|
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
|
+
#
|
51
|
+
# @private
|
52
|
+
# @param key [String, nil]
|
53
|
+
# @param fully_qualified_key [String, nil]
|
54
|
+
# @param kind [String, nil]
|
55
|
+
# @param name [String, nil]
|
56
|
+
# @param anonymous [Boolean, nil]
|
57
|
+
# @param attributes [Hash, nil]
|
58
|
+
# @param private_attributes [Array<String>, nil]
|
59
|
+
# @param error [String, nil]
|
60
|
+
# @param contexts [Array<LDContext>, nil]
|
61
|
+
#
|
62
|
+
def initialize(key, fully_qualified_key, kind, name = nil, anonymous = nil, attributes = nil, private_attributes = nil, error = nil, contexts = nil)
|
63
|
+
@key = key
|
64
|
+
@fully_qualified_key = fully_qualified_key
|
65
|
+
@kind = kind
|
66
|
+
@name = name
|
67
|
+
@anonymous = anonymous || false
|
68
|
+
@attributes = attributes
|
69
|
+
@private_attributes = Set.new
|
70
|
+
(private_attributes || []).each do |attribute|
|
71
|
+
reference = Reference.create(attribute)
|
72
|
+
@private_attributes.add(reference) if reference.error.nil?
|
73
|
+
end
|
74
|
+
@error = error
|
75
|
+
@contexts = contexts
|
76
|
+
@is_multi = !contexts.nil?
|
77
|
+
end
|
78
|
+
private_class_method :new
|
79
|
+
|
80
|
+
protected attr_reader :name, :anonymous, :attributes
|
81
|
+
|
82
|
+
#
|
83
|
+
# @return [Array<Reference>] Returns the private attributes associated with this LDContext
|
84
|
+
#
|
85
|
+
def private_attributes
|
86
|
+
# TODO(sc-227265): Return a set instead of an array.
|
87
|
+
@private_attributes.to_a
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# @return [Boolean] Is this LDContext a multi-kind context?
|
92
|
+
#
|
93
|
+
def multi_kind?
|
94
|
+
@is_multi
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# @return [Boolean] Determine if this LDContext is considered valid
|
99
|
+
#
|
100
|
+
def valid?
|
101
|
+
@error.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# For a multi-kind context:
|
106
|
+
#
|
107
|
+
# A multi-kind context is made up of two or more single-kind contexts. This method will first discard any
|
108
|
+
# single-kind contexts which are anonymous. It will then create a new multi-kind context from the remaining
|
109
|
+
# single-kind contexts. This may result in an invalid context (e.g. all single-kind contexts are anonymous).
|
110
|
+
#
|
111
|
+
# For a single-kind context:
|
112
|
+
#
|
113
|
+
# If the context is not anonymous, this method will return the current context as is and unmodified.
|
114
|
+
#
|
115
|
+
# If the context is anonymous, this method will return an invalid context.
|
116
|
+
#
|
117
|
+
def without_anonymous_contexts
|
118
|
+
contexts = multi_kind? ? @contexts : [self]
|
119
|
+
contexts = contexts.reject { |c| c.anonymous }
|
120
|
+
|
121
|
+
LDContext.create_multi(contexts)
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Returns a hash mapping each context's kind to its key.
|
126
|
+
#
|
127
|
+
# @return [Hash<Symbol, String>]
|
128
|
+
#
|
129
|
+
def keys
|
130
|
+
return {} unless valid?
|
131
|
+
return Hash[kind, key] unless multi_kind?
|
132
|
+
|
133
|
+
@contexts.map { |c| [c.kind, c.key] }.to_h
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Returns an array of context kinds.
|
138
|
+
#
|
139
|
+
# @return [Array<String>]
|
140
|
+
#
|
141
|
+
def kinds
|
142
|
+
return [] unless valid?
|
143
|
+
return [kind] unless multi_kind?
|
144
|
+
|
145
|
+
@contexts.map { |c| c.kind }
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Return an array of top level attribute keys (excluding built-in attributes)
|
150
|
+
#
|
151
|
+
# @return [Array<Symbol>]
|
152
|
+
#
|
153
|
+
def get_custom_attribute_names
|
154
|
+
return [] if @attributes.nil?
|
155
|
+
|
156
|
+
@attributes.keys
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# get_value looks up the value of any attribute of the Context by name.
|
161
|
+
# This includes only attributes that are addressable in evaluations-- not
|
162
|
+
# metadata such as private attributes.
|
163
|
+
#
|
164
|
+
# For a single-kind context, the attribute name can be any custom attribute.
|
165
|
+
# It can also be one of the built-in ones like "kind", "key", or "name".
|
166
|
+
#
|
167
|
+
# For a multi-kind context, the only supported attribute name is "kind".
|
168
|
+
# Use {#individual_context} to inspect a Context for a particular kind and
|
169
|
+
# then get its attributes.
|
170
|
+
#
|
171
|
+
# This method does not support complex expressions for getting individual
|
172
|
+
# values out of JSON objects or arrays, such as "/address/street". Use
|
173
|
+
# {#get_value_for_reference} for that purpose.
|
174
|
+
#
|
175
|
+
# If the value is found, the return value is the attribute value;
|
176
|
+
# otherwise, it is nil.
|
177
|
+
#
|
178
|
+
# @param attribute [String, Symbol]
|
179
|
+
# @return [any]
|
180
|
+
#
|
181
|
+
def get_value(attribute)
|
182
|
+
reference = Reference.create_literal(attribute)
|
183
|
+
get_value_for_reference(reference)
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# get_value_for_reference looks up the value of any attribute of the
|
188
|
+
# Context, or a value contained within an attribute, based on a {Reference}
|
189
|
+
# instance. This includes only attributes that are addressable in
|
190
|
+
# evaluations-- not metadata such as private attributes.
|
191
|
+
#
|
192
|
+
# This implements the same behavior that the SDK uses to resolve attribute
|
193
|
+
# references during a flag evaluation. In a single-kind context, the
|
194
|
+
# {Reference} can represent a simple attribute name-- either a built-in one
|
195
|
+
# like "name" or "key", or a custom attribute -- or, it can be a
|
196
|
+
# slash-delimited path using a JSON-Pointer-like syntax. See {Reference}
|
197
|
+
# for more details.
|
198
|
+
#
|
199
|
+
# For a multi-kind context, the only supported attribute name is "kind".
|
200
|
+
# Use {#individual_context} to inspect a Context for a particular kind and
|
201
|
+
# then get its attributes.
|
202
|
+
#
|
203
|
+
# If the value is found, the return value is the attribute value;
|
204
|
+
# otherwise, it is nil.
|
205
|
+
#
|
206
|
+
# @param reference [Reference]
|
207
|
+
# @return [any]
|
208
|
+
#
|
209
|
+
def get_value_for_reference(reference)
|
210
|
+
return nil unless valid?
|
211
|
+
return nil unless reference.is_a?(Reference)
|
212
|
+
return nil unless reference.error.nil?
|
213
|
+
|
214
|
+
first_component = reference.component(0)
|
215
|
+
return nil if first_component.nil?
|
216
|
+
|
217
|
+
if multi_kind?
|
218
|
+
if reference.depth == 1 && first_component == :kind
|
219
|
+
return kind
|
220
|
+
end
|
221
|
+
|
222
|
+
# Multi-kind contexts have no other addressable attributes
|
223
|
+
return nil
|
224
|
+
end
|
225
|
+
|
226
|
+
value = get_top_level_addressable_attribute_single_kind(first_component)
|
227
|
+
return nil if value.nil?
|
228
|
+
|
229
|
+
(1...reference.depth).each do |i|
|
230
|
+
name = reference.component(i)
|
231
|
+
|
232
|
+
return nil unless value.is_a?(Hash)
|
233
|
+
return nil unless value.has_key?(name)
|
234
|
+
|
235
|
+
value = value[name]
|
236
|
+
end
|
237
|
+
|
238
|
+
value
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
# Returns the number of context kinds in this context.
|
243
|
+
#
|
244
|
+
# For a valid individual context, this returns 1. For a multi-context, it
|
245
|
+
# returns the number of context kinds. For an invalid context, it returns
|
246
|
+
# zero.
|
247
|
+
#
|
248
|
+
# @return [Integer] the number of context kinds
|
249
|
+
#
|
250
|
+
def individual_context_count
|
251
|
+
return 0 unless valid?
|
252
|
+
return 1 if @contexts.nil?
|
253
|
+
@contexts.count
|
254
|
+
end
|
255
|
+
|
256
|
+
#
|
257
|
+
# Returns the single-kind LDContext corresponding to one of the kinds in
|
258
|
+
# this context.
|
259
|
+
#
|
260
|
+
# The `kind` parameter can be either a number representing a zero-based
|
261
|
+
# index, or a string representing a context kind.
|
262
|
+
#
|
263
|
+
# If this method is called on a single-kind LDContext, then the only
|
264
|
+
# allowable value for `kind` is either zero or the same value as {#kind},
|
265
|
+
# and the return value on success is the same LDContext.
|
266
|
+
#
|
267
|
+
# If the method is called on a multi-context, and `kind` is a number, it
|
268
|
+
# must be a non-negative index that is less than the number of kinds (that
|
269
|
+
# is, less than the return value of {#individual_context_count}, and the
|
270
|
+
# return value on success is one of the individual LDContexts within. Or,
|
271
|
+
# if `kind` is a string, it must match the context kind of one of the
|
272
|
+
# individual contexts.
|
273
|
+
#
|
274
|
+
# If there is no context corresponding to `kind`, the method returns nil.
|
275
|
+
#
|
276
|
+
# @param kind [Integer, String] the index or string value of a context kind
|
277
|
+
# @return [LDContext, nil] the context corresponding to that index or kind,
|
278
|
+
# or null if none.
|
279
|
+
#
|
280
|
+
def individual_context(kind)
|
281
|
+
return nil unless valid?
|
282
|
+
|
283
|
+
if kind.is_a?(Integer)
|
284
|
+
unless multi_kind?
|
285
|
+
return kind == 0 ? self : nil
|
286
|
+
end
|
287
|
+
|
288
|
+
return kind >= 0 && kind < @contexts.count ? @contexts[kind] : nil
|
289
|
+
end
|
290
|
+
|
291
|
+
return nil unless kind.is_a?(String)
|
292
|
+
|
293
|
+
unless multi_kind?
|
294
|
+
return self.kind == kind ? self : nil
|
295
|
+
end
|
296
|
+
|
297
|
+
@contexts.each do |context|
|
298
|
+
return context if context.kind == kind
|
299
|
+
end
|
300
|
+
|
301
|
+
nil
|
302
|
+
end
|
303
|
+
|
304
|
+
#
|
305
|
+
# An LDContext can be compared to other LDContexts or to a hash object. If
|
306
|
+
# a hash is provided, it is first converted to an LDContext using the
|
307
|
+
# `LDContext.create` method.
|
308
|
+
#
|
309
|
+
# @param other [LDContext, Hash]
|
310
|
+
# @return [Boolean]
|
311
|
+
#
|
312
|
+
def ==(other)
|
313
|
+
other = LDContext.create(other) if other.is_a? Hash
|
314
|
+
return false unless other.is_a? LDContext
|
315
|
+
|
316
|
+
return false unless self.kind == other.kind
|
317
|
+
return false unless self.valid? == other.valid?
|
318
|
+
return false unless self.error == other.error
|
319
|
+
|
320
|
+
return false unless self.individual_context_count == other.individual_context_count
|
321
|
+
|
322
|
+
if self.multi_kind?
|
323
|
+
self.kinds.each do |kind|
|
324
|
+
return false unless self.individual_context(kind) == other.individual_context(kind)
|
325
|
+
end
|
326
|
+
|
327
|
+
return true
|
328
|
+
end
|
329
|
+
|
330
|
+
return false unless self.key == other.key
|
331
|
+
return false unless self.name == other.name
|
332
|
+
return false unless self.anonymous == other.anonymous
|
333
|
+
return false unless self.attributes == other.attributes
|
334
|
+
|
335
|
+
# TODO(sc-227265): Calling .to_set is unnecessary once private_attributes are sets.
|
336
|
+
return false unless self.private_attributes.to_set == other.private_attributes.to_set
|
337
|
+
|
338
|
+
true
|
339
|
+
end
|
340
|
+
alias eql? ==
|
341
|
+
|
342
|
+
#
|
343
|
+
# For a single-kind context, the provided key will return the attribute value specified. This is the same as calling
|
344
|
+
# `LDCotnext.get_value`.
|
345
|
+
#
|
346
|
+
# For multi-kind contexts, the key will be interpreted as a context kind. If the multi-kind context has an
|
347
|
+
# individual context of that kind, it will be returned. Otherwise, this method will return nil. This behaves the
|
348
|
+
# same as calling `LDContext.individual_context`.
|
349
|
+
#
|
350
|
+
# @param key [Symbol, String]
|
351
|
+
#
|
352
|
+
def [](key)
|
353
|
+
return nil unless key.is_a? Symbol or key.is_a? String
|
354
|
+
multi_kind? ? individual_context(key.to_s) : get_value(key)
|
355
|
+
end
|
356
|
+
|
357
|
+
#
|
358
|
+
# Convert the LDContext to a JSON string.
|
359
|
+
#
|
360
|
+
# @param args [Array]
|
361
|
+
# @return [String]
|
362
|
+
#
|
363
|
+
def to_json(*args)
|
364
|
+
JSON.generate(to_h, *args)
|
365
|
+
end
|
366
|
+
|
367
|
+
#
|
368
|
+
# Convert the LDContext to a hash. If the LDContext is invalid, the hash will contain an error key with the error
|
369
|
+
# message.
|
370
|
+
#
|
371
|
+
# @return [Hash]
|
372
|
+
#
|
373
|
+
def to_h
|
374
|
+
return {error: error} unless valid?
|
375
|
+
return hash_single_kind unless multi_kind?
|
376
|
+
|
377
|
+
hash = {kind: 'multi'}
|
378
|
+
@contexts.each do |context|
|
379
|
+
single_kind_hash = context.to_h
|
380
|
+
kind = single_kind_hash.delete(:kind)
|
381
|
+
hash[kind] = single_kind_hash
|
382
|
+
end
|
383
|
+
|
384
|
+
hash
|
385
|
+
end
|
386
|
+
|
387
|
+
protected def hash_single_kind
|
388
|
+
hash = attributes.nil? ? {} : attributes.clone
|
389
|
+
|
390
|
+
hash[:kind] = kind
|
391
|
+
hash[:key] = key
|
392
|
+
|
393
|
+
hash[:name] = name unless name.nil?
|
394
|
+
hash[:anonymous] = anonymous if anonymous
|
395
|
+
hash[:_meta] = {privateAttributes: private_attributes} unless private_attributes.empty?
|
396
|
+
|
397
|
+
hash
|
398
|
+
end
|
399
|
+
|
400
|
+
#
|
401
|
+
# Retrieve the value of any top level, addressable attribute.
|
402
|
+
#
|
403
|
+
# This method returns an array of two values. The first element is the
|
404
|
+
# value of the requested attribute or nil if it does not exist. The second
|
405
|
+
# value will be true if the attribute exists; otherwise, it will be false.
|
406
|
+
#
|
407
|
+
# @param name [Symbol]
|
408
|
+
# @return [any]
|
409
|
+
#
|
410
|
+
private def get_top_level_addressable_attribute_single_kind(name)
|
411
|
+
case name
|
412
|
+
when :kind
|
413
|
+
kind
|
414
|
+
when :key
|
415
|
+
key
|
416
|
+
when :name
|
417
|
+
@name
|
418
|
+
when :anonymous
|
419
|
+
@anonymous
|
420
|
+
else
|
421
|
+
@attributes&.fetch(name, nil)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
#
|
426
|
+
# Convenience method to create a simple single kind context providing only
|
427
|
+
# a key and kind type.
|
428
|
+
#
|
429
|
+
# @param key [String]
|
430
|
+
# @param kind [String]
|
431
|
+
#
|
432
|
+
def self.with_key(key, kind = KIND_DEFAULT)
|
433
|
+
create({key: key, kind: kind})
|
434
|
+
end
|
435
|
+
|
436
|
+
#
|
437
|
+
# Create a single kind context from the provided hash.
|
438
|
+
#
|
439
|
+
# The provided hash must match the format as outlined in the
|
440
|
+
# {https://docs.launchdarkly.com/sdk/features/user-config SDK
|
441
|
+
# documentation}.
|
442
|
+
#
|
443
|
+
# @param data [Hash]
|
444
|
+
# @return [LDContext]
|
445
|
+
#
|
446
|
+
def self.create(data)
|
447
|
+
return create_invalid_context(ERR_NOT_HASH) unless data.is_a?(Hash)
|
448
|
+
|
449
|
+
kind = data[:kind]
|
450
|
+
if kind == KIND_MULTI
|
451
|
+
contexts = []
|
452
|
+
data.each do |key, value|
|
453
|
+
next if key == :kind
|
454
|
+
contexts << create_single_context(value, key.to_s)
|
455
|
+
end
|
456
|
+
|
457
|
+
return create_multi(contexts)
|
458
|
+
end
|
459
|
+
|
460
|
+
create_single_context(data, kind)
|
461
|
+
end
|
462
|
+
|
463
|
+
#
|
464
|
+
# Create a multi-kind context from the array of LDContexts provided.
|
465
|
+
#
|
466
|
+
# A multi-kind context is comprised of two or more single kind contexts.
|
467
|
+
# You cannot include a multi-kind context instead another multi-kind
|
468
|
+
# context.
|
469
|
+
#
|
470
|
+
# Additionally, the kind of each single-kind context must be unique. For
|
471
|
+
# instance, you cannot create a multi-kind context that includes two user
|
472
|
+
# kind contexts.
|
473
|
+
#
|
474
|
+
# If you attempt to create a multi-kind context from one single-kind
|
475
|
+
# context, this method will return the single-kind context instead of a new
|
476
|
+
# multi-kind context wrapping that one single-kind.
|
477
|
+
#
|
478
|
+
# @param contexts [Array<LDContext>]
|
479
|
+
# @return [LDContext]
|
480
|
+
#
|
481
|
+
def self.create_multi(contexts)
|
482
|
+
return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY) unless contexts.is_a?(Array)
|
483
|
+
return create_invalid_context(ERR_KIND_MULTI_WITH_NO_KINDS) if contexts.empty?
|
484
|
+
|
485
|
+
kinds = Set.new
|
486
|
+
contexts.each do |context|
|
487
|
+
if !context.is_a?(LDContext)
|
488
|
+
return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY)
|
489
|
+
elsif !context.valid?
|
490
|
+
return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY)
|
491
|
+
elsif context.multi_kind?
|
492
|
+
return create_invalid_context(ERR_KIND_MULTI_CANNOT_CONTAIN_MULTI)
|
493
|
+
elsif kinds.include? context.kind
|
494
|
+
return create_invalid_context(ERR_KIND_MULTI_DUPLICATES)
|
495
|
+
end
|
496
|
+
|
497
|
+
kinds.add(context.kind)
|
498
|
+
end
|
499
|
+
|
500
|
+
return contexts[0] if contexts.length == 1
|
501
|
+
|
502
|
+
full_key = contexts.sort_by(&:kind)
|
503
|
+
.map { |c| LaunchDarkly::Impl::Context::canonicalize_key_for_kind(c.kind, c.key) }
|
504
|
+
.join(":")
|
505
|
+
|
506
|
+
new(nil, full_key, "multi", nil, false, nil, nil, nil, contexts)
|
507
|
+
end
|
508
|
+
|
509
|
+
#
|
510
|
+
# @param error [String]
|
511
|
+
# @return [LDContext]
|
512
|
+
#
|
513
|
+
private_class_method def self.create_invalid_context(error)
|
514
|
+
new(nil, nil, nil, nil, false, nil, nil, error)
|
515
|
+
end
|
516
|
+
|
517
|
+
#
|
518
|
+
# @param data [Hash]
|
519
|
+
# @param kind [String]
|
520
|
+
# @return [LaunchDarkly::LDContext]
|
521
|
+
#
|
522
|
+
private_class_method def self.create_single_context(data, kind)
|
523
|
+
unless data.is_a?(Hash)
|
524
|
+
return create_invalid_context(ERR_NOT_HASH)
|
525
|
+
end
|
526
|
+
|
527
|
+
kind_error = LaunchDarkly::Impl::Context.validate_kind(kind)
|
528
|
+
return create_invalid_context(kind_error) unless kind_error.nil?
|
529
|
+
|
530
|
+
key = data[:key]
|
531
|
+
key_error = LaunchDarkly::Impl::Context.validate_key(key)
|
532
|
+
return create_invalid_context(key_error) unless key_error.nil?
|
533
|
+
|
534
|
+
name = data[:name]
|
535
|
+
name_error = LaunchDarkly::Impl::Context.validate_name(name)
|
536
|
+
return create_invalid_context(name_error) unless name_error.nil?
|
537
|
+
|
538
|
+
anonymous = data.fetch(:anonymous, false)
|
539
|
+
anonymous_error = LaunchDarkly::Impl::Context.validate_anonymous(anonymous, false)
|
540
|
+
return create_invalid_context(anonymous_error) unless anonymous_error.nil?
|
541
|
+
|
542
|
+
meta = data.fetch(:_meta, {})
|
543
|
+
private_attributes = meta[:privateAttributes]
|
544
|
+
if private_attributes && !private_attributes.is_a?(Array)
|
545
|
+
return create_invalid_context(ERR_PRIVATE_NON_ARRAY)
|
546
|
+
end
|
547
|
+
|
548
|
+
# We only need to create an attribute hash if there are keys set outside
|
549
|
+
# of the ones we store in dedicated instance variables.
|
550
|
+
attributes = nil
|
551
|
+
data.each do |k, v|
|
552
|
+
case k
|
553
|
+
when :kind, :key, :name, :anonymous, :_meta
|
554
|
+
next
|
555
|
+
else
|
556
|
+
attributes ||= {}
|
557
|
+
attributes[k] = v.clone
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
full_key = kind == LDContext::KIND_DEFAULT ? key.to_s : LaunchDarkly::Impl::Context::canonicalize_key_for_kind(kind, key.to_s)
|
562
|
+
new(key.to_s, full_key, kind, name, anonymous, attributes, private_attributes)
|
563
|
+
end
|
564
|
+
end
|
565
|
+
end
|