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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +13 -0
  3. data/README.md +61 -0
  4. data/lib/launchdarkly-server-sdk.rb +1 -0
  5. data/lib/ldclient-rb/cache_store.rb +45 -0
  6. data/lib/ldclient-rb/config.rb +658 -0
  7. data/lib/ldclient-rb/context.rb +565 -0
  8. data/lib/ldclient-rb/evaluation_detail.rb +387 -0
  9. data/lib/ldclient-rb/events.rb +642 -0
  10. data/lib/ldclient-rb/expiring_cache.rb +77 -0
  11. data/lib/ldclient-rb/flags_state.rb +88 -0
  12. data/lib/ldclient-rb/impl/big_segments.rb +117 -0
  13. data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
  14. data/lib/ldclient-rb/impl/context.rb +96 -0
  15. data/lib/ldclient-rb/impl/context_filter.rb +166 -0
  16. data/lib/ldclient-rb/impl/data_source.rb +188 -0
  17. data/lib/ldclient-rb/impl/data_store.rb +109 -0
  18. data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
  19. data/lib/ldclient-rb/impl/diagnostic_events.rb +129 -0
  20. data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
  21. data/lib/ldclient-rb/impl/evaluator.rb +539 -0
  22. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +86 -0
  23. data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
  24. data/lib/ldclient-rb/impl/evaluator_operators.rb +131 -0
  25. data/lib/ldclient-rb/impl/event_sender.rb +100 -0
  26. data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
  27. data/lib/ldclient-rb/impl/event_types.rb +136 -0
  28. data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
  29. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +170 -0
  30. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +300 -0
  31. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +229 -0
  32. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +306 -0
  33. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
  34. data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
  35. data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
  36. data/lib/ldclient-rb/impl/model/clause.rb +45 -0
  37. data/lib/ldclient-rb/impl/model/feature_flag.rb +254 -0
  38. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
  39. data/lib/ldclient-rb/impl/model/segment.rb +132 -0
  40. data/lib/ldclient-rb/impl/model/serialization.rb +72 -0
  41. data/lib/ldclient-rb/impl/repeating_task.rb +46 -0
  42. data/lib/ldclient-rb/impl/sampler.rb +25 -0
  43. data/lib/ldclient-rb/impl/store_client_wrapper.rb +141 -0
  44. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +55 -0
  45. data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
  46. data/lib/ldclient-rb/impl/util.rb +95 -0
  47. data/lib/ldclient-rb/impl.rb +13 -0
  48. data/lib/ldclient-rb/in_memory_store.rb +100 -0
  49. data/lib/ldclient-rb/integrations/consul.rb +45 -0
  50. data/lib/ldclient-rb/integrations/dynamodb.rb +92 -0
  51. data/lib/ldclient-rb/integrations/file_data.rb +108 -0
  52. data/lib/ldclient-rb/integrations/redis.rb +98 -0
  53. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +663 -0
  54. data/lib/ldclient-rb/integrations/test_data.rb +213 -0
  55. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +246 -0
  56. data/lib/ldclient-rb/integrations.rb +6 -0
  57. data/lib/ldclient-rb/interfaces.rb +974 -0
  58. data/lib/ldclient-rb/ldclient.rb +822 -0
  59. data/lib/ldclient-rb/memoized_value.rb +32 -0
  60. data/lib/ldclient-rb/migrations.rb +230 -0
  61. data/lib/ldclient-rb/non_blocking_thread_pool.rb +46 -0
  62. data/lib/ldclient-rb/polling.rb +102 -0
  63. data/lib/ldclient-rb/reference.rb +295 -0
  64. data/lib/ldclient-rb/requestor.rb +102 -0
  65. data/lib/ldclient-rb/simple_lru_cache.rb +25 -0
  66. data/lib/ldclient-rb/stream.rb +196 -0
  67. data/lib/ldclient-rb/util.rb +132 -0
  68. data/lib/ldclient-rb/version.rb +3 -0
  69. data/lib/ldclient-rb.rb +27 -0
  70. 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