launchdarkly-server-sdk 6.4.0 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ldclient-rb/config.rb +102 -56
  3. data/lib/ldclient-rb/context.rb +487 -0
  4. data/lib/ldclient-rb/evaluation_detail.rb +20 -20
  5. data/lib/ldclient-rb/events.rb +77 -132
  6. data/lib/ldclient-rb/flags_state.rb +4 -4
  7. data/lib/ldclient-rb/impl/big_segments.rb +17 -17
  8. data/lib/ldclient-rb/impl/context.rb +96 -0
  9. data/lib/ldclient-rb/impl/context_filter.rb +145 -0
  10. data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
  11. data/lib/ldclient-rb/impl/evaluator.rb +379 -131
  12. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
  13. data/lib/ldclient-rb/impl/evaluator_helpers.rb +28 -31
  14. data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
  15. data/lib/ldclient-rb/impl/event_sender.rb +6 -6
  16. data/lib/ldclient-rb/impl/event_summarizer.rb +12 -7
  17. data/lib/ldclient-rb/impl/event_types.rb +18 -30
  18. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
  19. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +29 -29
  20. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
  21. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +92 -12
  22. data/lib/ldclient-rb/impl/model/clause.rb +39 -0
  23. data/lib/ldclient-rb/impl/model/feature_flag.rb +213 -0
  24. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +8 -121
  25. data/lib/ldclient-rb/impl/model/segment.rb +126 -0
  26. data/lib/ldclient-rb/impl/model/serialization.rb +52 -12
  27. data/lib/ldclient-rb/impl/repeating_task.rb +1 -1
  28. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
  29. data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
  30. data/lib/ldclient-rb/impl/util.rb +2 -2
  31. data/lib/ldclient-rb/in_memory_store.rb +2 -2
  32. data/lib/ldclient-rb/integrations/consul.rb +1 -1
  33. data/lib/ldclient-rb/integrations/dynamodb.rb +1 -1
  34. data/lib/ldclient-rb/integrations/file_data.rb +3 -3
  35. data/lib/ldclient-rb/integrations/redis.rb +4 -4
  36. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +218 -62
  37. data/lib/ldclient-rb/integrations/test_data.rb +16 -12
  38. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +9 -9
  39. data/lib/ldclient-rb/interfaces.rb +14 -14
  40. data/lib/ldclient-rb/ldclient.rb +94 -144
  41. data/lib/ldclient-rb/memoized_value.rb +1 -1
  42. data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
  43. data/lib/ldclient-rb/polling.rb +2 -2
  44. data/lib/ldclient-rb/reference.rb +274 -0
  45. data/lib/ldclient-rb/requestor.rb +5 -5
  46. data/lib/ldclient-rb/stream.rb +7 -8
  47. data/lib/ldclient-rb/util.rb +4 -19
  48. data/lib/ldclient-rb/version.rb +1 -1
  49. data/lib/ldclient-rb.rb +2 -3
  50. metadata +34 -17
  51. data/lib/ldclient-rb/file_data_source.rb +0 -23
  52. data/lib/ldclient-rb/newrelic.rb +0 -17
  53. data/lib/ldclient-rb/redis_store.rb +0 -88
  54. data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -1,3 +1,4 @@
1
+ require "ldclient-rb/impl/context_filter"
1
2
  require "ldclient-rb/impl/diagnostic_events"
2
3
  require "ldclient-rb/impl/event_sender"
3
4
  require "ldclient-rb/impl/event_summarizer"
@@ -20,7 +21,7 @@ require "time"
20
21
  # On a separate worker thread, EventDispatcher consumes events from the inbox. These are considered
21
22
  # "input events" because they may or may not actually be sent to LaunchDarkly; most flag evaluation
22
23
  # events are not sent, but are counted and the counters become part of a single summary event.
23
- # EventDispatcher updates those counters, creates "index" events for any users that have not been seen
24
+ # EventDispatcher updates those counters, creates "index" events for any contexts that have not been seen
24
25
  # recently, and places any events that will be sent to LaunchDarkly into the "outbox" queue.
25
26
  #
26
27
  # When it is time to flush events to LaunchDarkly, the contents of the outbox are handed off to
@@ -30,7 +31,7 @@ require "time"
30
31
  module LaunchDarkly
31
32
  module EventProcessorMethods
32
33
  def record_eval_event(
33
- user,
34
+ context,
34
35
  key,
35
36
  version = nil,
36
37
  variation = nil,
@@ -43,20 +44,17 @@ module LaunchDarkly
43
44
  )
44
45
  end
45
46
 
46
- def record_identify_event(user)
47
+ def record_identify_event(context)
47
48
  end
48
49
 
49
50
  def record_custom_event(
50
- user,
51
+ context,
51
52
  key,
52
53
  data = nil,
53
54
  metric_value = nil
54
55
  )
55
56
  end
56
57
 
57
- def record_alias_event(user, previous_user)
58
- end
59
-
60
58
  def flush
61
59
  end
62
60
 
@@ -65,11 +63,7 @@ module LaunchDarkly
65
63
  end
66
64
 
67
65
  MAX_FLUSH_WORKERS = 5
68
- USER_ATTRS_TO_STRINGIFY_FOR_EVENTS = [ :key, :secondary, :ip, :country, :email, :firstName, :lastName,
69
- :avatar, :name ]
70
-
71
66
  private_constant :MAX_FLUSH_WORKERS
72
- private_constant :USER_ATTRS_TO_STRINGIFY_FOR_EVENTS
73
67
 
74
68
  # @private
75
69
  class NullEventProcessor
@@ -81,7 +75,7 @@ module LaunchDarkly
81
75
  end
82
76
 
83
77
  # @private
84
- class FlushUsersMessage
78
+ class FlushContextsMessage
85
79
  end
86
80
 
87
81
  # @private
@@ -93,7 +87,7 @@ module LaunchDarkly
93
87
  def initialize
94
88
  @reply = Concurrent::Semaphore.new(0)
95
89
  end
96
-
90
+
97
91
  def completed
98
92
  @reply.release
99
93
  end
@@ -123,10 +117,10 @@ module LaunchDarkly
123
117
  post_to_inbox(FlushMessage.new)
124
118
  end
125
119
  @flush_task.execute
126
- @users_flush_task = Concurrent::TimerTask.new(execution_interval: config.user_keys_flush_interval) do
127
- post_to_inbox(FlushUsersMessage.new)
120
+ @contexts_flush_task = Concurrent::TimerTask.new(execution_interval: config.context_keys_flush_interval) do
121
+ post_to_inbox(FlushContextsMessage.new)
128
122
  end
129
- @users_flush_task.execute
123
+ @contexts_flush_task.execute
130
124
  if !diagnostic_accumulator.nil?
131
125
  interval = test_properties && test_properties.has_key?(:diagnostic_recording_interval) ?
132
126
  test_properties[:diagnostic_recording_interval] :
@@ -142,7 +136,7 @@ module LaunchDarkly
142
136
  @inbox_full = Concurrent::AtomicBoolean.new(false)
143
137
 
144
138
  event_sender = (test_properties || {})[:event_sender] ||
145
- Impl::EventSender.new(sdk_key, config, client ? client : Util.new_http_client(config.events_uri, config))
139
+ Impl::EventSender.new(sdk_key, config, client || Util.new_http_client(config.events_uri, config))
146
140
 
147
141
  @timestamp_fn = (test_properties || {})[:timestamp_fn] || proc { Impl::Util.current_time_millis }
148
142
 
@@ -150,7 +144,7 @@ module LaunchDarkly
150
144
  end
151
145
 
152
146
  def record_eval_event(
153
- user,
147
+ context,
154
148
  key,
155
149
  version = nil,
156
150
  variation = nil,
@@ -161,26 +155,16 @@ module LaunchDarkly
161
155
  debug_until = nil,
162
156
  prereq_of = nil
163
157
  )
164
- post_to_inbox(LaunchDarkly::Impl::EvalEvent.new(timestamp, user, key, version, variation, value, reason,
158
+ post_to_inbox(LaunchDarkly::Impl::EvalEvent.new(timestamp, context, key, version, variation, value, reason,
165
159
  default, track_events, debug_until, prereq_of))
166
160
  end
167
161
 
168
- def record_identify_event(user)
169
- post_to_inbox(LaunchDarkly::Impl::IdentifyEvent.new(timestamp, user))
162
+ def record_identify_event(context)
163
+ post_to_inbox(LaunchDarkly::Impl::IdentifyEvent.new(timestamp, context))
170
164
  end
171
165
 
172
- def record_custom_event(user, key, data = nil, metric_value = nil)
173
- post_to_inbox(LaunchDarkly::Impl::CustomEvent.new(timestamp, user, key, data, metric_value))
174
- end
175
-
176
- def record_alias_event(user, previous_user)
177
- post_to_inbox(LaunchDarkly::Impl::AliasEvent.new(
178
- timestamp,
179
- user.nil? ? nil : user[:key],
180
- user_to_context_kind(user),
181
- previous_user.nil? ? nil : previous_user[:key],
182
- user_to_context_kind(previous_user)
183
- ))
166
+ def record_custom_event(context, key, data = nil, metric_value = nil)
167
+ post_to_inbox(LaunchDarkly::Impl::CustomEvent.new(timestamp, context, key, data, metric_value))
184
168
  end
185
169
 
186
170
  def flush
@@ -192,8 +176,8 @@ module LaunchDarkly
192
176
  # final shutdown, which includes a final flush, is done synchronously
193
177
  if @stopped.make_true
194
178
  @flush_task.shutdown
195
- @users_flush_task.shutdown
196
- @diagnostic_event_task.shutdown if !@diagnostic_event_task.nil?
179
+ @contexts_flush_task.shutdown
180
+ @diagnostic_event_task.shutdown unless @diagnostic_event_task.nil?
197
181
  # Note that here we are not calling post_to_inbox, because we *do* want to wait if the inbox
198
182
  # is full; an orderly shutdown can't happen unless these messages are received.
199
183
  @inbox << FlushMessage.new
@@ -227,10 +211,6 @@ module LaunchDarkly
227
211
  end
228
212
  end
229
213
  end
230
-
231
- private def user_to_context_kind(user)
232
- (user.nil? || !user[:anonymous]) ? 'user' : 'anonymousUser'
233
- end
234
214
  end
235
215
 
236
216
  # @private
@@ -241,13 +221,13 @@ module LaunchDarkly
241
221
  @diagnostic_accumulator = config.diagnostic_opt_out? ? nil : diagnostic_accumulator
242
222
  @event_sender = event_sender
243
223
 
244
- @user_keys = SimpleLRUCacheSet.new(config.user_keys_capacity)
224
+ @context_keys = SimpleLRUCacheSet.new(config.context_keys_capacity)
245
225
  @formatter = EventOutputFormatter.new(config)
246
226
  @disabled = Concurrent::AtomicBoolean.new(false)
247
227
  @last_known_past_time = Concurrent::AtomicReference.new(0)
248
- @deduplicated_users = 0
228
+ @deduplicated_contexts = 0
249
229
  @events_in_last_batch = 0
250
-
230
+
251
231
  outbox = EventBuffer.new(config.capacity, config.logger)
252
232
  flush_workers = NonBlockingThreadPool.new(MAX_FLUSH_WORKERS)
253
233
 
@@ -272,8 +252,8 @@ module LaunchDarkly
272
252
  case message
273
253
  when FlushMessage
274
254
  trigger_flush(outbox, flush_workers)
275
- when FlushUsersMessage
276
- @user_keys.clear
255
+ when FlushContextsMessage
256
+ @context_keys.clear
277
257
  when DiagnosticEventMessage
278
258
  send_and_reset_diagnostics(outbox, diagnostic_event_workers)
279
259
  when TestSyncMessage
@@ -295,7 +275,7 @@ module LaunchDarkly
295
275
  def do_shutdown(flush_workers, diagnostic_event_workers)
296
276
  flush_workers.shutdown
297
277
  flush_workers.wait_for_termination
298
- if !diagnostic_event_workers.nil?
278
+ unless diagnostic_event_workers.nil?
299
279
  diagnostic_event_workers.shutdown
300
280
  diagnostic_event_workers.wait_for_termination
301
281
  end
@@ -305,7 +285,7 @@ module LaunchDarkly
305
285
  def synchronize_for_testing(flush_workers, diagnostic_event_workers)
306
286
  # Used only by unit tests. Wait until all active flush workers have finished.
307
287
  flush_workers.wait_all
308
- diagnostic_event_workers.wait_all if !diagnostic_event_workers.nil?
288
+ diagnostic_event_workers.wait_all unless diagnostic_event_workers.nil?
309
289
  end
310
290
 
311
291
  def dispatch_event(event, outbox)
@@ -327,27 +307,26 @@ module LaunchDarkly
327
307
  will_add_full_event = true
328
308
  end
329
309
 
330
- # For each user we haven't seen before, we add an index event - unless this is already
331
- # an identify event for that user.
332
- if !(will_add_full_event && @config.inline_users_in_events)
333
- if !event.user.nil? && !notice_user(event.user) && !event.is_a?(LaunchDarkly::Impl::IdentifyEvent)
334
- outbox.add_event(LaunchDarkly::Impl::IndexEvent.new(event.timestamp, event.user))
335
- end
310
+ # For each context we haven't seen before, we add an index event - unless this is already
311
+ # an identify event for that context.
312
+ if !event.context.nil? && !notice_context(event.context) && !event.is_a?(LaunchDarkly::Impl::IdentifyEvent)
313
+ outbox.add_event(LaunchDarkly::Impl::IndexEvent.new(event.timestamp, event.context))
336
314
  end
337
315
 
338
316
  outbox.add_event(event) if will_add_full_event
339
- outbox.add_event(debug_event) if !debug_event.nil?
317
+ outbox.add_event(debug_event) unless debug_event.nil?
340
318
  end
341
319
 
342
- # Add to the set of users we've noticed, and return true if the user was already known to us.
343
- def notice_user(user)
344
- if user.nil? || !user.has_key?(:key)
345
- true
346
- else
347
- known = @user_keys.add(user[:key].to_s)
348
- @deduplicated_users += 1 if known
349
- known
350
- end
320
+ #
321
+ # Add to the set of contexts we've noticed, and return true if the context
322
+ # was already known to us.
323
+ # @param context [LaunchDarkly::LDContext]
324
+ # @return [Boolean]
325
+ #
326
+ def notice_context(context)
327
+ known = @context_keys.add(context.fully_qualified_key)
328
+ @deduplicated_contexts += 1 if known
329
+ known
351
330
  end
352
331
 
353
332
  def should_debug_event(event)
@@ -365,7 +344,7 @@ module LaunchDarkly
365
344
  return
366
345
  end
367
346
 
368
- payload = outbox.get_payload
347
+ payload = outbox.get_payload
369
348
  if !payload.events.empty? || !payload.summary.counters.empty?
370
349
  count = payload.events.length + (payload.summary.counters.empty? ? 0 : 1)
371
350
  @events_in_last_batch = count
@@ -375,7 +354,7 @@ module LaunchDarkly
375
354
  events_out = @formatter.make_output_events(payload.events, payload.summary)
376
355
  result = @event_sender.send_event_data(events_out.to_json, "#{events_out.length} events", false)
377
356
  @disabled.value = true if result.must_shutdown
378
- if !result.time_from_server.nil?
357
+ unless result.time_from_server.nil?
379
358
  @last_known_past_time.value = (result.time_from_server.to_f * 1000).to_i
380
359
  end
381
360
  rescue => e
@@ -391,8 +370,8 @@ module LaunchDarkly
391
370
  def send_and_reset_diagnostics(outbox, diagnostic_event_workers)
392
371
  return if @diagnostic_accumulator.nil?
393
372
  dropped_count = outbox.get_and_clear_dropped_count
394
- event = @diagnostic_accumulator.create_periodic_event_and_reset(dropped_count, @deduplicated_users, @events_in_last_batch)
395
- @deduplicated_users = 0
373
+ event = @diagnostic_accumulator.create_periodic_event_and_reset(dropped_count, @deduplicated_contexts, @events_in_last_batch)
374
+ @deduplicated_contexts = 0
396
375
  @events_in_last_batch = 0
397
376
  send_diagnostic_event(event, diagnostic_event_workers)
398
377
  end
@@ -430,7 +409,7 @@ module LaunchDarkly
430
409
  @capacity_exceeded = false
431
410
  else
432
411
  @dropped_events += 1
433
- if !@capacity_exceeded
412
+ unless @capacity_exceeded
434
413
  @capacity_exceeded = true
435
414
  @logger.warn { "[LDClient] Exceeded event queue capacity. Increase capacity to avoid dropping events." }
436
415
  end
@@ -442,7 +421,7 @@ module LaunchDarkly
442
421
  end
443
422
 
444
423
  def get_payload
445
- return FlushPayload.new(@events, @summarizer.snapshot)
424
+ FlushPayload.new(@events, @summarizer.snapshot)
446
425
  end
447
426
 
448
427
  def get_and_clear_dropped_count
@@ -462,21 +441,18 @@ module LaunchDarkly
462
441
  FEATURE_KIND = 'feature'
463
442
  IDENTIFY_KIND = 'identify'
464
443
  CUSTOM_KIND = 'custom'
465
- ALIAS_KIND = 'alias'
466
444
  INDEX_KIND = 'index'
467
445
  DEBUG_KIND = 'debug'
468
446
  SUMMARY_KIND = 'summary'
469
- ANONYMOUS_USER_CONTEXT_KIND = 'anonymousUser'
470
447
 
471
448
  def initialize(config)
472
- @inline_users = config.inline_users_in_events
473
- @user_filter = UserFilter.new(config)
449
+ @context_filter = LaunchDarkly::Impl::ContextFilter.new(config.all_attributes_private, config.private_attributes)
474
450
  end
475
451
 
476
452
  # Transforms events into the format used for event sending.
477
453
  def make_output_events(events, summary)
478
454
  events_out = events.map { |e| make_output_event(e) }
479
- if !summary.counters.empty?
455
+ unless summary.counters.empty?
480
456
  events_out.push(make_summary_event(summary))
481
457
  end
482
458
  events_out
@@ -484,75 +460,62 @@ module LaunchDarkly
484
460
 
485
461
  private def make_output_event(event)
486
462
  case event
487
-
463
+
488
464
  when LaunchDarkly::Impl::EvalEvent
489
465
  out = {
490
466
  kind: FEATURE_KIND,
491
467
  creationDate: event.timestamp,
492
468
  key: event.key,
493
- value: event.value
469
+ value: event.value,
494
470
  }
495
- out[:default] = event.default if !event.default.nil?
496
- out[:variation] = event.variation if !event.variation.nil?
497
- out[:version] = event.version if !event.version.nil?
498
- out[:prereqOf] = event.prereq_of if !event.prereq_of.nil?
499
- set_opt_context_kind(out, event.user)
500
- set_user_or_user_key(out, event.user)
501
- out[:reason] = event.reason if !event.reason.nil?
471
+ out[:default] = event.default unless event.default.nil?
472
+ out[:variation] = event.variation unless event.variation.nil?
473
+ out[:version] = event.version unless event.version.nil?
474
+ out[:prereqOf] = event.prereq_of unless event.prereq_of.nil?
475
+ out[:contextKeys] = event.context.keys
476
+ out[:reason] = event.reason unless event.reason.nil?
502
477
  out
503
478
 
504
479
  when LaunchDarkly::Impl::IdentifyEvent
505
480
  {
506
481
  kind: IDENTIFY_KIND,
507
482
  creationDate: event.timestamp,
508
- key: event.user[:key].to_s,
509
- user: process_user(event.user)
483
+ key: event.context.fully_qualified_key,
484
+ context: @context_filter.filter(event.context),
510
485
  }
511
-
486
+
512
487
  when LaunchDarkly::Impl::CustomEvent
513
488
  out = {
514
489
  kind: CUSTOM_KIND,
515
490
  creationDate: event.timestamp,
516
- key: event.key
491
+ key: event.key,
517
492
  }
518
- out[:data] = event.data if !event.data.nil?
519
- set_user_or_user_key(out, event.user)
520
- out[:metricValue] = event.metric_value if !event.metric_value.nil?
521
- set_opt_context_kind(out, event.user)
493
+ out[:data] = event.data unless event.data.nil?
494
+ out[:contextKeys] = event.context.keys
495
+ out[:metricValue] = event.metric_value unless event.metric_value.nil?
522
496
  out
523
497
 
524
- when LaunchDarkly::Impl::AliasEvent
525
- {
526
- kind: ALIAS_KIND,
527
- creationDate: event.timestamp,
528
- key: event.key,
529
- contextKind: event.context_kind,
530
- previousKey: event.previous_key,
531
- previousContextKind: event.previous_context_kind
532
- }
533
-
534
498
  when LaunchDarkly::Impl::IndexEvent
535
499
  {
536
500
  kind: INDEX_KIND,
537
501
  creationDate: event.timestamp,
538
- user: process_user(event.user)
502
+ context: @context_filter.filter(event.context),
539
503
  }
540
-
504
+
541
505
  when LaunchDarkly::Impl::DebugEvent
542
506
  original = event.eval_event
543
507
  out = {
544
508
  kind: DEBUG_KIND,
545
509
  creationDate: original.timestamp,
546
510
  key: original.key,
547
- user: process_user(original.user),
548
- value: original.value
511
+ context: @context_filter.filter(original.context),
512
+ value: original.value,
549
513
  }
550
- out[:default] = original.default if !original.default.nil?
551
- out[:variation] = original.variation if !original.variation.nil?
552
- out[:version] = original.version if !original.version.nil?
553
- out[:prereqOf] = original.prereq_of if !original.prereq_of.nil?
554
- set_opt_context_kind(out, original.user)
555
- out[:reason] = original.reason if !original.reason.nil?
514
+ out[:default] = original.default unless original.default.nil?
515
+ out[:variation] = original.variation unless original.variation.nil?
516
+ out[:version] = original.version unless original.version.nil?
517
+ out[:prereqOf] = original.prereq_of unless original.prereq_of.nil?
518
+ out[:reason] = original.reason unless original.reason.nil?
556
519
  out
557
520
 
558
521
  else
@@ -569,9 +532,9 @@ module LaunchDarkly
569
532
  variations.each do |variation, counter|
570
533
  c = {
571
534
  value: counter.value,
572
- count: counter.count
535
+ count: counter.count,
573
536
  }
574
- c[:variation] = variation if !variation.nil?
537
+ c[:variation] = variation unless variation.nil?
575
538
  if version.nil?
576
539
  c[:unknown] = true
577
540
  else
@@ -580,32 +543,14 @@ module LaunchDarkly
580
543
  counters.push(c)
581
544
  end
582
545
  end
583
- flags[flagKey] = { default: flagInfo.default, counters: counters }
546
+ flags[flagKey] = { default: flagInfo.default, counters: counters, contextKinds: flagInfo.context_kinds.to_a }
584
547
  end
585
548
  {
586
549
  kind: SUMMARY_KIND,
587
550
  startDate: summary[:start_date],
588
551
  endDate: summary[:end_date],
589
- features: flags
552
+ features: flags,
590
553
  }
591
554
  end
592
-
593
- private def set_opt_context_kind(out, user)
594
- out[:contextKind] = ANONYMOUS_USER_CONTEXT_KIND if !user.nil? && user[:anonymous]
595
- end
596
-
597
- private def set_user_or_user_key(out, user)
598
- if @inline_users
599
- out[:user] = process_user(user)
600
- else
601
- key = user[:key]
602
- out[:userKey] = key.is_a?(String) ? key : key.to_s
603
- end
604
- end
605
-
606
- private def process_user(user)
607
- filtered = @user_filter.transform_user_props(user)
608
- Util.stringify_attrs(filtered, USER_ATTRS_TO_STRINGIFY_FOR_EVENTS)
609
- end
610
555
  end
611
556
  end
@@ -2,7 +2,7 @@ require 'json'
2
2
 
3
3
  module LaunchDarkly
4
4
  #
5
- # A snapshot of the state of all feature flags with regard to a specific user, generated by
5
+ # A snapshot of the state of all feature flags with regard to a specific context, generated by
6
6
  # calling the {LDClient#all_flags_state}. Serializing this object to JSON using
7
7
  # `JSON.generate` (or the `to_json` method) will produce the appropriate data structure for
8
8
  # bootstrapping the LaunchDarkly JavaScript client.
@@ -34,11 +34,11 @@ module LaunchDarkly
34
34
  meta[:reason] = reason
35
35
  end
36
36
 
37
- if !omit_details
37
+ unless omit_details
38
38
  meta[:version] = flag_state[:version]
39
39
  end
40
40
 
41
- meta[:variation] = flag_state[:variation] if !flag_state[:variation].nil?
41
+ meta[:variation] = flag_state[:variation] unless flag_state[:variation].nil?
42
42
  meta[:trackEvents] = true if flag_state[:trackEvents]
43
43
  meta[:trackReason] = true if flag_state[:trackReason]
44
44
  meta[:debugEventsUntilDate] = flag_state[:debugEventsUntilDate] if flag_state[:debugEventsUntilDate]
@@ -46,7 +46,7 @@ module LaunchDarkly
46
46
  end
47
47
 
48
48
  # Returns true if this object contains a valid snapshot of feature flag state, or false if the
49
- # state could not be computed (for instance, because the client was offline or there was no user).
49
+ # state could not be computed (for instance, because the client was offline or there was no context).
50
50
  def valid?
51
51
  @valid
52
52
  end
@@ -22,7 +22,7 @@ module LaunchDarkly
22
22
  @logger = logger
23
23
  @last_status = nil
24
24
 
25
- if !@store.nil?
25
+ unless @store.nil?
26
26
  @cache = ExpiringCache.new(big_segments_config.user_cache_size, big_segments_config.user_cache_time)
27
27
  @poll_worker = RepeatingTask.new(big_segments_config.status_poll_interval, 0, -> { poll_store_and_update_status }, logger)
28
28
  @poll_worker.start
@@ -32,25 +32,25 @@ module LaunchDarkly
32
32
  attr_reader :status_provider
33
33
 
34
34
  def stop
35
- @poll_worker.stop if !@poll_worker.nil?
36
- @store.stop if !@store.nil?
35
+ @poll_worker.stop unless @poll_worker.nil?
36
+ @store.stop unless @store.nil?
37
37
  end
38
38
 
39
- def get_user_membership(user_key)
40
- return nil if !@store
41
- membership = @cache[user_key]
42
- if !membership
39
+ def get_context_membership(context_key)
40
+ return nil unless @store
41
+ membership = @cache[context_key]
42
+ unless membership
43
43
  begin
44
- membership = @store.get_membership(BigSegmentStoreManager.hash_for_user_key(user_key))
44
+ membership = @store.get_membership(BigSegmentStoreManager.hash_for_context_key(context_key))
45
45
  membership = EMPTY_MEMBERSHIP if membership.nil?
46
- @cache[user_key] = membership
46
+ @cache[context_key] = membership
47
47
  rescue => e
48
48
  LaunchDarkly::Util.log_exception(@logger, "Big Segment store membership query returned error", e)
49
49
  return BigSegmentMembershipResult.new(nil, BigSegmentsStatus::STORE_ERROR)
50
50
  end
51
51
  end
52
- poll_store_and_update_status if !@last_status
53
- if !@last_status.available
52
+ poll_store_and_update_status unless @last_status
53
+ unless @last_status.available
54
54
  return BigSegmentMembershipResult.new(membership, BigSegmentsStatus::STORE_ERROR)
55
55
  end
56
56
  BigSegmentMembershipResult.new(membership, @last_status.stale ? BigSegmentsStatus::STALE : BigSegmentsStatus::HEALTHY)
@@ -62,26 +62,26 @@ module LaunchDarkly
62
62
 
63
63
  def poll_store_and_update_status
64
64
  new_status = Interfaces::BigSegmentStoreStatus.new(false, false) # default to "unavailable" if we don't get a new status below
65
- if !@store.nil?
65
+ unless @store.nil?
66
66
  begin
67
67
  metadata = @store.get_metadata
68
- new_status = Interfaces::BigSegmentStoreStatus.new(true, !metadata || is_stale(metadata.last_up_to_date))
68
+ new_status = Interfaces::BigSegmentStoreStatus.new(true, !metadata || stale?(metadata.last_up_to_date))
69
69
  rescue => e
70
70
  LaunchDarkly::Util.log_exception(@logger, "Big Segment store status query returned error", e)
71
71
  end
72
72
  end
73
73
  @last_status = new_status
74
74
  @status_provider.update_status(new_status)
75
-
75
+
76
76
  new_status
77
77
  end
78
78
 
79
- def is_stale(timestamp)
79
+ def stale?(timestamp)
80
80
  !timestamp || ((Impl::Util.current_time_millis - timestamp) >= @stale_after_millis)
81
81
  end
82
82
 
83
- def self.hash_for_user_key(user_key)
84
- Digest::SHA256.base64digest(user_key)
83
+ def self.hash_for_context_key(context_key)
84
+ Digest::SHA256.base64digest(context_key)
85
85
  end
86
86
  end
87
87
 
@@ -0,0 +1,96 @@
1
+ require "erb"
2
+
3
+ module LaunchDarkly
4
+ module Impl
5
+ module Context
6
+ ERR_KIND_NON_STRING = 'context kind must be a string'
7
+ ERR_KIND_CANNOT_BE_KIND = '"kind" is not a valid context kind'
8
+ ERR_KIND_CANNOT_BE_MULTI = '"multi" is not a valid context kind'
9
+ ERR_KIND_INVALID_CHARS = 'context kind contains disallowed characters'
10
+
11
+ ERR_KEY_NON_STRING = 'context key must be a string'
12
+ ERR_KEY_EMPTY = 'context key must not be empty'
13
+
14
+ ERR_NAME_NON_STRING = 'context name must be a string'
15
+
16
+ ERR_ANONYMOUS_NON_BOOLEAN = 'context anonymous must be a boolean'
17
+
18
+ #
19
+ # We allow consumers of this SDK to provide us with either a Hash or an
20
+ # instance of an LDContext. This is convenient for them but not as much
21
+ # for us. To make the conversion slightly more convenient for us, we have
22
+ # created this method.
23
+ #
24
+ # @param context [Hash, LDContext]
25
+ # @return [LDContext]
26
+ #
27
+ def self.make_context(context)
28
+ return context if context.is_a?(LDContext)
29
+
30
+ LDContext.create(context)
31
+ end
32
+
33
+ #
34
+ # Returns an error message if the kind is invalid; nil otherwise.
35
+ #
36
+ # @param kind [any]
37
+ # @return [String, nil]
38
+ #
39
+ def self.validate_kind(kind)
40
+ return ERR_KIND_NON_STRING unless kind.is_a?(String)
41
+ return ERR_KIND_CANNOT_BE_KIND if kind == "kind"
42
+ return ERR_KIND_CANNOT_BE_MULTI if kind == "multi"
43
+ return ERR_KIND_INVALID_CHARS unless kind.match?(/^[\w.-]+$/)
44
+ end
45
+
46
+ #
47
+ # Returns an error message if the key is invalid; nil otherwise.
48
+ #
49
+ # @param key [any]
50
+ # @return [String, nil]
51
+ #
52
+ def self.validate_key(key)
53
+ return ERR_KEY_NON_STRING unless key.is_a?(String)
54
+ return ERR_KEY_EMPTY if key == ""
55
+ end
56
+
57
+ #
58
+ # Returns an error message if the name is invalid; nil otherwise.
59
+ #
60
+ # @param name [any]
61
+ # @return [String, nil]
62
+ #
63
+ def self.validate_name(name)
64
+ return ERR_NAME_NON_STRING unless name.nil? || name.is_a?(String)
65
+ end
66
+
67
+ #
68
+ # Returns an error message if anonymous is invalid; nil otherwise.
69
+ #
70
+ # @param anonymous [any]
71
+ # @param allow_nil [Boolean]
72
+ # @return [String, nil]
73
+ #
74
+ def self.validate_anonymous(anonymous, allow_nil)
75
+ return nil if anonymous.nil? && allow_nil
76
+ return nil if [true, false].include? anonymous
77
+
78
+ ERR_ANONYMOUS_NON_BOOLEAN
79
+ end
80
+
81
+ #
82
+ # @param kind [String]
83
+ # @param key [String]
84
+ # @return [String]
85
+ #
86
+ def self.canonicalize_key_for_kind(kind, key)
87
+ # When building a FullyQualifiedKey, ':' and '%' are percent-escaped;
88
+ # we do not use a full URL-encoding function because implementations of
89
+ # this are inconsistent across platforms.
90
+ encoded = key.gsub("%", "%25").gsub(":", "%3A")
91
+
92
+ "#{kind}:#{encoded}"
93
+ end
94
+ end
95
+ end
96
+ end