Module: LaunchDarkly::Impl::DataSystem Private

Included in:
FDv1, FDv2
Defined in:
lib/ldclient-rb/impl/data_system.rb,
lib/ldclient-rb/impl/data_system/fdv1.rb,
lib/ldclient-rb/impl/data_system/fdv2.rb,
lib/ldclient-rb/impl/data_system/polling.rb,
lib/ldclient-rb/impl/data_system/streaming.rb,
lib/ldclient-rb/impl/data_system/protocolv2.rb,
lib/ldclient-rb/impl/data_system/http_config_options.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Mixin that defines the required methods of a data system implementation. The data system is responsible for managing the SDK’s data model, including storage, retrieval, and change detection for feature flag configurations.

This module also contains supporting classes and additional mixins for data system implementations, such as DataAvailability, Update, and protocol-specific mixins.

For operations that can fail, use Result from util.rb.

Application code should not need to implement this directly; it is used internally by the SDK’s data system implementations.

Since:

  • 5.5.0

Defined Under Namespace

Modules: DiagnosticAccumulator, DiagnosticSource, Initializer, ProtocolV2, SyncResult, Synchronizer Classes: DataAvailability, FDv1, FDv2, HTTPFDv1PollingRequester, HTTPPollingRequester, HttpConfigOptions, PollingDataSource, StreamingDataSource, Update

Constant Summary collapse

FDV2_POLLING_ENDPOINT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 5.5.0

"/sdk/poll"
FDV1_POLLING_ENDPOINT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 5.5.0

"/sdk/latest-all"
LD_ENVID_HEADER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 5.5.0

"X-LD-EnvID"
LD_FD_FALLBACK_HEADER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 5.5.0

"X-LD-FD-Fallback"
FDV2_STREAMING_ENDPOINT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 5.5.0

"/sdk/stream"
STREAM_READ_TIMEOUT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Allows for up to 5 minutes to elapse without any data sent across the stream. The heartbeats sent as comments on the stream will keep this from triggering.

Since:

  • 5.5.0

5 * 60

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.fdv1_fallback_requested?(headers) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Reports whether the response headers signal that the SDK should fall back to the FDv1 protocol. Lookup is case-insensitive so callers do not need to know whether the header map preserves canonical casing (e.g. HTTP::Headers) or has been normalized to lowercase (e.g. LaunchDarkly::Impl::DataSystem::HTTPPollingRequester#fetch).

Parameters:

  • headers (#[], Hash, nil)

Returns:

  • (Boolean)

Since:

  • 5.5.0



34
35
36
37
38
39
40
# File 'lib/ldclient-rb/impl/data_system/polling.rb', line 34

def self.fdv1_fallback_requested?(headers)
  return false if headers.nil?
  value = lookup_header(headers, LD_FD_FALLBACK_HEADER)
  # http gem returns arrays for repeated headers; normalize to a string.
  value = value.first if value.is_a?(Array)
  value == 'true'
end

.fdv1_polling_payload_to_changeset(data) ⇒ LaunchDarkly::Result<LaunchDarkly::Interfaces::DataSystem::ChangeSet, String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Converts an FDv1 polling payload into a ChangeSet.

Parameters:

  • data (Hash)

    The FDv1 polling payload

Returns:

Since:

  • 5.5.0



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/ldclient-rb/impl/data_system/polling.rb', line 537

def self.fdv1_polling_payload_to_changeset(data)
  builder = LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new
  builder.start(LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL)
  selector = LaunchDarkly::Interfaces::DataSystem::Selector.no_selector

  kind_mappings = [
    [LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG, :flags],
    [LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT, :segments],
  ]

  kind_mappings.each do |kind, fdv1_key|
    kind_data = data[fdv1_key]
    next if kind_data.nil?

    unless kind_data.is_a?(Hash)
      return LaunchDarkly::Result.fail("Invalid format: #{fdv1_key} is not an object")
    end

    kind_data.each do |key, flag_or_segment|
      unless flag_or_segment.is_a?(Hash)
        return LaunchDarkly::Result.fail("Invalid format: #{key} is not an object")
      end

      version = flag_or_segment[:version]
      return LaunchDarkly::Result.fail("Invalid format: #{key} does not have a version set") if version.nil?

      builder.add_put(kind, key, version, flag_or_segment)
    end
  end

  LaunchDarkly::Result.success(builder.finish(selector))
end

.lookup_header(headers, name) ⇒ String, ...

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Performs a case-insensitive header lookup that works with both case-insensitive header containers (e.g. ‘HTTP::Headers`) and plain Ruby hashes – including hashes whose keys we have downcased ourselves before reaching this code path.

Parameters:

  • headers (#[], Hash)
  • name (String)

Returns:

  • (String, Array, nil)

Since:

  • 5.5.0



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/ldclient-rb/impl/data_system/polling.rb', line 52

def self.lookup_header(headers, name)
  return nil if headers.nil?

  if headers.is_a?(Hash)
    # Plain hash: try canonical case, then exact lowercase, then a
    # case-insensitive scan as a final fallback.
    value = headers[name]
    return value unless value.nil?

    downcased = name.downcase
    value = headers[downcased]
    return value unless value.nil?

    headers.each_pair do |key, val|
      return val if key.to_s.downcase == downcased
    end
    return nil
  end

  # Non-hash container (e.g. HTTP::Headers). Lookup via [] is
  # already case-insensitive on those types.
  return headers[name] if headers.respond_to?(:[])

  nil
end

.polling_payload_to_changeset(data) ⇒ LaunchDarkly::Result<LaunchDarkly::Interfaces::DataSystem::ChangeSet, String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Converts a polling payload into a ChangeSet.

Parameters:

  • data (Hash)

    The polling payload

Returns:

Since:

  • 5.5.0



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/ldclient-rb/impl/data_system/polling.rb', line 471

def self.polling_payload_to_changeset(data)
  unless data[:events].is_a?(Array)
    return LaunchDarkly::Result.fail("Invalid payload: 'events' key is missing or not a list")
  end

  builder = LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new

  data[:events].each do |event|
    unless event.is_a?(Hash)
      return LaunchDarkly::Result.fail("Invalid payload: 'events' must be a list of objects")
    end

    next unless event[:event]

    case event[:event].to_sym
    when LaunchDarkly::Interfaces::DataSystem::EventName::SERVER_INTENT
      begin
        server_intent = LaunchDarkly::Interfaces::DataSystem::ServerIntent.from_h(event[:data])
      rescue ArgumentError => e
        return LaunchDarkly::Result.fail("Invalid JSON in server intent", e)
      end

      if server_intent.payload.code == LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_NONE
        return LaunchDarkly::Result.success(LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.no_changes)
      end

      builder.start(server_intent.payload.code)

    when LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT
      begin
        put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(event[:data])
      rescue ArgumentError => e
        return LaunchDarkly::Result.fail("Invalid JSON in put object", e)
      end

      builder.add_put(put.kind, put.key, put.version, put.object)

    when LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT
      begin
        delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(event[:data])
      rescue ArgumentError => e
        return LaunchDarkly::Result.fail("Invalid JSON in delete object", e)
      end

      builder.add_delete(delete_object.kind, delete_object.key, delete_object.version)

    when LaunchDarkly::Interfaces::DataSystem::EventName::PAYLOAD_TRANSFERRED
      begin
        selector = LaunchDarkly::Interfaces::DataSystem::Selector.from_h(event[:data])
        changeset = builder.finish(selector)
        return LaunchDarkly::Result.success(changeset)
      rescue ArgumentError, RuntimeError => e
        return LaunchDarkly::Result.fail("Invalid JSON in payload transferred object", e)
      end
    end
  end

  LaunchDarkly::Result.fail("didn't receive any known protocol events in polling payload")
end

Instance Method Details

#data_availabilitySymbol

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Indicates what form of data is currently available.

This is calculated dynamically based on current system state.

Returns:

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



89
90
91
# File 'lib/ldclient-rb/impl/data_system.rb', line 89

def data_availability
  raise NotImplementedError, "#{self.class} must implement #data_availability"
end

#data_source_status_providerLaunchDarkly::Interfaces::DataSource::StatusProvider

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns an interface for tracking the status of the data source.

The data source is the mechanism that the SDK uses to get feature flag configurations, such as a streaming connection (the default) or poll requests.

Returns:

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



51
52
53
# File 'lib/ldclient-rb/impl/data_system.rb', line 51

def data_source_status_provider
  raise NotImplementedError, "#{self.class} must implement #data_source_status_provider"
end

#data_store_status_providerLaunchDarkly::Interfaces::DataStore::StatusProvider

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns an interface for tracking the status of a persistent data store.

The provider has methods for checking whether the data store is (as far as the SDK knows) currently operational, tracking changes in this status, and getting cache statistics. These are only relevant for a persistent data store; if you are using an in-memory data store, then this method will return a stub object that provides no information.

Returns:

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



66
67
68
# File 'lib/ldclient-rb/impl/data_system.rb', line 66

def data_store_status_provider
  raise NotImplementedError, "#{self.class} must implement #data_store_status_provider"
end

#flag_change_broadcasterLaunchDarkly::Impl::Broadcaster

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the broadcaster for flag change notifications.

Consumers can use this broadcaster to build their own flag tracker or listen for flag changes directly.

Returns:

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



78
79
80
# File 'lib/ldclient-rb/impl/data_system.rb', line 78

def flag_change_broadcaster
  raise NotImplementedError, "#{self.class} must implement #flag_change_broadcaster"
end

#set_diagnostic_accumulator(diagnostic_accumulator) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Sets the diagnostic accumulator for streaming initialization metrics. This should be called before start() to ensure metrics are collected.

Parameters:

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



118
119
120
# File 'lib/ldclient-rb/impl/data_system.rb', line 118

def set_diagnostic_accumulator(diagnostic_accumulator)
  raise NotImplementedError, "#{self.class} must implement #set_diagnostic_accumulator"
end

#startConcurrent::Event

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Starts the data system.

This method will return immediately. The returned event will be set when the system has reached an initial state (either permanently failed, e.g. due to bad auth, or succeeded).

If called multiple times, returns the same event as the first call.

Returns:

  • (Concurrent::Event)

    Event that will be set when initialization is complete

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



29
30
31
# File 'lib/ldclient-rb/impl/data_system.rb', line 29

def start
  raise NotImplementedError, "#{self.class} must implement #start"
end

#stopvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Halts the data system. Should be called when the client is closed to stop any long running operations. Makes the data system no longer usable.

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



39
40
41
# File 'lib/ldclient-rb/impl/data_system.rb', line 39

def stop
  raise NotImplementedError, "#{self.class} must implement #stop"
end

#storeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the data store used by the data system.

Returns:

  • (Object)

    The read-only store

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



107
108
109
# File 'lib/ldclient-rb/impl/data_system.rb', line 107

def store
  raise NotImplementedError, "#{self.class} must implement #store"
end

#target_availabilitySymbol

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Indicates the ideal form of data attainable given the current configuration.

Returns:

  • (Symbol)

    one of the #DataAvailability constants

Raises:

  • (NotImplementedError)

Since:

  • 5.5.0



98
99
100
# File 'lib/ldclient-rb/impl/data_system.rb', line 98

def target_availability
  raise NotImplementedError, "#{self.class} must implement #target_availability"
end