diff --git a/lib/ldclient-rb/config.rb b/lib/ldclient-rb/config.rb index aa4d1d67..55a676ad 100644 --- a/lib/ldclient-rb/config.rb +++ b/lib/ldclient-rb/config.rb @@ -468,7 +468,7 @@ def self.default_capacity # @return [String] "https://sdk.launchdarkly.com" # def self.default_base_uri - Impl::DataSystem::PollingDataSourceBuilder::DEFAULT_BASE_URI + DataSystem::PollingDataSourceBuilder::DEFAULT_BASE_URI end # @@ -476,7 +476,7 @@ def self.default_base_uri # @return [String] "https://stream.launchdarkly.com" # def self.default_stream_uri - Impl::DataSystem::StreamingDataSourceBuilder::DEFAULT_BASE_URI + DataSystem::StreamingDataSourceBuilder::DEFAULT_BASE_URI end # @@ -516,7 +516,7 @@ def self.default_read_timeout # @return [Float] 1 # def self.default_initial_reconnect_delay - Impl::DataSystem::StreamingDataSourceBuilder::DEFAULT_INITIAL_RECONNECT_DELAY + DataSystem::StreamingDataSourceBuilder::DEFAULT_INITIAL_RECONNECT_DELAY end # @@ -578,7 +578,7 @@ def self.default_offline # @return [Float] 30 # def self.default_poll_interval - Impl::DataSystem::PollingDataSourceBuilder::DEFAULT_POLL_INTERVAL + DataSystem::PollingDataSourceBuilder::DEFAULT_POLL_INTERVAL end # diff --git a/lib/ldclient-rb/data_system.rb b/lib/ldclient-rb/data_system.rb index faed96a8..dd03b511 100644 --- a/lib/ldclient-rb/data_system.rb +++ b/lib/ldclient-rb/data_system.rb @@ -4,100 +4,66 @@ require 'ldclient-rb/config' require 'ldclient-rb/impl/data_system/polling' require 'ldclient-rb/impl/data_system/streaming' +require 'ldclient-rb/data_system/config_builder' +require 'ldclient-rb/data_system/polling_data_source_builder' +require 'ldclient-rb/data_system/streaming_data_source_builder' module LaunchDarkly # # Configuration for LaunchDarkly's data acquisition strategy. # - # This module provides factory methods for creating data system configurations. + # This module provides factory methods for creating data system configurations, + # as well as builder classes for constructing individual data sources (polling + # and streaming). + # + # == Quick Start + # + # For most users, the predefined strategies are sufficient: + # + # # Use the default strategy (recommended) + # config = LaunchDarkly::Config.new( + # data_system: LaunchDarkly::DataSystem.default + # ) + # + # # Use streaming only + # config = LaunchDarkly::Config.new( + # data_system: LaunchDarkly::DataSystem.streaming + # ) + # + # # Use polling only + # config = LaunchDarkly::Config.new( + # data_system: LaunchDarkly::DataSystem.polling + # ) + # + # == Custom Configurations + # + # For advanced use cases, you can build custom configurations using the + # data source builders: + # + # polling = LaunchDarkly::DataSystem.polling_ds_builder + # .poll_interval(60) + # .base_uri("https://custom-polling.example.com") + # + # streaming = LaunchDarkly::DataSystem.streaming_ds_builder + # .initial_reconnect_delay(2) + # .base_uri("https://custom-streaming.example.com") + # + # data_system = LaunchDarkly::DataSystem.custom + # .initializers([polling]) + # .synchronizers([streaming, polling]) + # + # config = LaunchDarkly::Config.new(data_system: data_system) # module DataSystem - # - # Builder for the data system configuration. - # - class ConfigBuilder - def initialize - @initializers = nil - @synchronizers = nil - @fdv1_fallback_synchronizer = nil - @data_store_mode = LaunchDarkly::Interfaces::DataSystem::DataStoreMode::READ_ONLY - @data_store = nil - end - - # - # Sets the initializers for the data system. - # - # @param initializers [Array<#build(String, Config)>] - # Array of builders that respond to build(sdk_key, config) and return an Initializer - # @return [ConfigBuilder] self for chaining - # - def initializers(initializers) - @initializers = initializers - self - end - - # - # Sets the synchronizers for the data system. - # - # @param synchronizers [Array<#build(String, Config)>] - # Array of builders that respond to build(sdk_key, config) and return a Synchronizer - # @return [ConfigBuilder] self for chaining - # - def synchronizers(synchronizers) - @synchronizers = synchronizers - self - end - - # - # Configures the SDK with a fallback synchronizer that is compatible with - # the Flag Delivery v1 API. - # - # @param fallback [#build(String, Config)] Builder that responds to build(sdk_key, config) and returns the fallback Synchronizer - # @return [ConfigBuilder] self for chaining - # - def fdv1_compatible_synchronizer(fallback) - @fdv1_fallback_synchronizer = fallback - self - end - - # - # Sets the data store configuration for the data system. - # - # @param data_store [LaunchDarkly::Interfaces::FeatureStore] The data store - # @param store_mode [Symbol] The store mode - # @return [ConfigBuilder] self for chaining - # - def data_store(data_store, store_mode) - @data_store = data_store - @data_store_mode = store_mode - self - end - - # - # Builds the data system configuration. - # - # @return [DataSystemConfig] - # - def build - DataSystemConfig.new( - initializers: @initializers, - synchronizers: @synchronizers, - data_store_mode: @data_store_mode, - data_store: @data_store, - fdv1_fallback_synchronizer: @fdv1_fallback_synchronizer - ) - end - end - # # Returns a builder for creating a polling data source. # This is a building block that can be used with {ConfigBuilder#initializers} # or {ConfigBuilder#synchronizers} to create custom data system configurations. # - # @return [LaunchDarkly::Impl::DataSystem::PollingDataSourceBuilder] + # @return [PollingDataSourceBuilder] # def self.polling_ds_builder - LaunchDarkly::Impl::DataSystem::PollingDataSourceBuilder.new + PollingDataSourceBuilder.new end # @@ -105,10 +71,10 @@ def self.polling_ds_builder # This is a building block that can be used with {ConfigBuilder#fdv1_compatible_synchronizer} # to provide FDv1 compatibility in custom data system configurations. # - # @return [LaunchDarkly::Impl::DataSystem::FDv1PollingDataSourceBuilder] + # @return [FDv1PollingDataSourceBuilder] # def self.fdv1_fallback_ds_builder - LaunchDarkly::Impl::DataSystem::FDv1PollingDataSourceBuilder.new + FDv1PollingDataSourceBuilder.new end # @@ -116,10 +82,10 @@ def self.fdv1_fallback_ds_builder # This is a building block that can be used with {ConfigBuilder#synchronizers} # to create custom data system configurations. # - # @return [LaunchDarkly::Impl::DataSystem::StreamingDataSourceBuilder] + # @return [StreamingDataSourceBuilder] # def self.streaming_ds_builder - LaunchDarkly::Impl::DataSystem::StreamingDataSourceBuilder.new + StreamingDataSourceBuilder.new end # @@ -224,4 +190,3 @@ def self.persistent_store(store) end end end - diff --git a/lib/ldclient-rb/data_system/config_builder.rb b/lib/ldclient-rb/data_system/config_builder.rb new file mode 100644 index 00000000..43dced05 --- /dev/null +++ b/lib/ldclient-rb/data_system/config_builder.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require "ldclient-rb/interfaces/data_system" + +module LaunchDarkly + module DataSystem + # + # Builder for the data system configuration. + # + # This builder configures the overall data acquisition strategy for the SDK, + # including which data sources to use for initialization and synchronization, + # and how to interact with a persistent data store. + # + # @see DataSystem.default + # @see DataSystem.streaming + # @see DataSystem.polling + # @see DataSystem.custom + # + class ConfigBuilder + def initialize + @initializers = nil + @synchronizers = nil + @fdv1_fallback_synchronizer = nil + @data_store_mode = LaunchDarkly::Interfaces::DataSystem::DataStoreMode::READ_ONLY + @data_store = nil + end + + # + # Sets the initializers for the data system. + # + # Initializers are used to fetch an initial set of data when the SDK starts. + # They are tried in order; if the first one fails, the next is tried, and so on. + # + # @param initializers [Array<#build(String, Config)>] + # Array of builders that respond to build(sdk_key, config) and return an Initializer + # @return [ConfigBuilder] self for chaining + # + def initializers(initializers) + @initializers = initializers + self + end + + # + # Sets the synchronizers for the data system. + # + # Synchronizers keep data up-to-date after initialization. Like initializers, + # they are tried in order. If the primary synchronizer fails, the next one + # takes over. + # + # @param synchronizers [Array<#build(String, Config)>] + # Array of builders that respond to build(sdk_key, config) and return a Synchronizer + # @return [ConfigBuilder] self for chaining + # + def synchronizers(synchronizers) + @synchronizers = synchronizers + self + end + + # + # Configures the SDK with a fallback synchronizer that is compatible with + # the Flag Delivery v1 API. + # + # This fallback is used when the server signals that the environment should + # revert to FDv1 protocol. Most users will not need to set this directly. + # + # @param fallback [#build(String, Config)] Builder that responds to build(sdk_key, config) and returns the fallback Synchronizer + # @return [ConfigBuilder] self for chaining + # + def fdv1_compatible_synchronizer(fallback) + @fdv1_fallback_synchronizer = fallback + self + end + + # + # Sets the data store configuration for the data system. + # + # @param data_store [LaunchDarkly::Interfaces::FeatureStore] The data store + # @param store_mode [Symbol] The store mode (use constants from + # {LaunchDarkly::Interfaces::DataSystem::DataStoreMode}) + # @return [ConfigBuilder] self for chaining + # + def data_store(data_store, store_mode) + @data_store = data_store + @data_store_mode = store_mode + self + end + + # + # Builds the data system configuration. + # + # @return [DataSystemConfig] + # + def build + DataSystemConfig.new( + initializers: @initializers, + synchronizers: @synchronizers, + data_store_mode: @data_store_mode, + data_store: @data_store, + fdv1_fallback_synchronizer: @fdv1_fallback_synchronizer + ) + end + end + end +end diff --git a/lib/ldclient-rb/data_system/data_source_builder_common.rb b/lib/ldclient-rb/data_system/data_source_builder_common.rb new file mode 100644 index 00000000..c264fc61 --- /dev/null +++ b/lib/ldclient-rb/data_system/data_source_builder_common.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "ldclient-rb/impl/data_system/http_config_options" + +module LaunchDarkly + module DataSystem + # + # Common HTTP configuration methods shared by all data source builders. + # + # This module is included by {PollingDataSourceBuilder}, + # {FDv1PollingDataSourceBuilder}, and {StreamingDataSourceBuilder} to provide + # a consistent set of HTTP connection settings. + # + # Each builder that includes this module must define a +DEFAULT_BASE_URI+ constant + # which is used as the fallback when {#base_uri} has not been called. + # + module DataSourceBuilderCommon + # + # Sets the base URI for HTTP requests. + # + # Use this to point the SDK at a Relay Proxy instance or any other URI + # that implements the corresponding LaunchDarkly API. + # + # @param uri [String] The base URI (e.g. "https://relay.example.com") + # @return [self] the builder, for chaining + # + def base_uri(uri) + @base_uri = uri + self + end + + # + # Sets a custom socket factory for HTTP connections. + # + # @param factory [#open] A socket factory that responds to +open+ + # @return [self] the builder, for chaining + # + def socket_factory(factory) + @socket_factory = factory + self + end + + # + # Sets the read timeout for HTTP connections. + # + # @param timeout [Float] Timeout in seconds + # @return [self] the builder, for chaining + # + def read_timeout(timeout) + @read_timeout = timeout + self + end + + # + # Sets the connect timeout for HTTP connections. + # + # @param timeout [Float] Timeout in seconds + # @return [self] the builder, for chaining + # + def connect_timeout(timeout) + @connect_timeout = timeout + self + end + + # + # Builds an HttpConfigOptions instance from the current builder settings. + # Uses +self.class::DEFAULT_BASE_URI+ if {#base_uri} was not explicitly set. + # Read/connect timeouts default to HttpConfigOptions defaults if not set. + # + # @return [LaunchDarkly::Impl::DataSystem::HttpConfigOptions] + # + private def build_http_config + LaunchDarkly::Impl::DataSystem::HttpConfigOptions.new( + base_uri: (@base_uri || self.class::DEFAULT_BASE_URI).chomp("/"), + socket_factory: @socket_factory, + read_timeout: @read_timeout, + connect_timeout: @connect_timeout + ) + end + end + end +end diff --git a/lib/ldclient-rb/data_system/polling_data_source_builder.rb b/lib/ldclient-rb/data_system/polling_data_source_builder.rb new file mode 100644 index 00000000..e68a88db --- /dev/null +++ b/lib/ldclient-rb/data_system/polling_data_source_builder.rb @@ -0,0 +1,227 @@ +# frozen_string_literal: true + +require "ldclient-rb/data_system/data_source_builder_common" + +module LaunchDarkly + module DataSystem + # + # Interface for custom polling requesters. + # + # A Requester is responsible for fetching data from a data source. The SDK + # ships with built-in HTTP requesters for both FDv2 and FDv1 polling endpoints, + # but you can implement this interface to provide custom data fetching logic + # (e.g., reading from a file, a database, or a custom API). + # + # == Implementing a Custom Requester + # + # To create a custom requester, include this module and implement the {#fetch} + # method: + # + # class MyCustomRequester + # include LaunchDarkly::DataSystem::Requester + # + # def fetch(selector) + # # Fetch data and return a Result containing [ChangeSet, headers] + # # ... + # LaunchDarkly::Result.success([change_set, {}]) + # end + # + # def stop + # # Clean up resources + # end + # end + # + # polling = LaunchDarkly::DataSystem.polling_ds_builder + # .requester(MyCustomRequester.new) + # + # @see PollingDataSourceBuilder#requester + # + module Requester + # + # Fetches data for the given selector. + # + # @param selector [LaunchDarkly::Interfaces::DataSystem::Selector, nil] + # The selector describing what data to fetch. May be nil if no + # selector is available (e.g., on the first request). + # @return [LaunchDarkly::Result] A Result containing a tuple of + # [ChangeSet, headers] on success, or an error message on failure. + # + def fetch(selector) + raise NotImplementedError + end + + # + # Releases any resources held by this requester (e.g., persistent HTTP + # connections). Called when the requester is no longer needed. + # + # Implementations should handle being called multiple times gracefully. + # The default implementation is a no-op. + # + def stop + # Optional - implementations may override if they need cleanup + end + end + + # + # Builder for a polling data source that communicates with LaunchDarkly's + # FDv2 polling endpoint. + # + # This builder can be used with {ConfigBuilder#initializers} or + # {ConfigBuilder#synchronizers} to create custom data system configurations. + # + # The polling data source periodically fetches data from LaunchDarkly. It + # supports conditional requests via ETags, so subsequent polls after the + # initial request only transfer data if changes have occurred. + # + # == Example + # + # polling = LaunchDarkly::DataSystem.polling_ds_builder + # .poll_interval(60) + # .base_uri("https://custom-endpoint.example.com") + # + # data_system = LaunchDarkly::DataSystem.custom + # .synchronizers([polling]) + # + # @see DataSystem.polling_ds_builder + # + class PollingDataSourceBuilder + include LaunchDarkly::DataSystem::DataSourceBuilderCommon + + # @return [String] The default base URI for polling requests + DEFAULT_BASE_URI = "https://sdk.launchdarkly.com" + + # @return [Float] The default polling interval in seconds + DEFAULT_POLL_INTERVAL = 30 + + def initialize + @requester = nil + end + + # + # Sets the polling interval in seconds. + # + # This controls how frequently the SDK polls LaunchDarkly for updates. + # Lower values mean more frequent updates but higher network traffic. + # The default is {DEFAULT_POLL_INTERVAL} seconds. + # + # @param secs [Float] Polling interval in seconds + # @return [PollingDataSourceBuilder] self for chaining + # + def poll_interval(secs) + @poll_interval = secs + self + end + + # + # Sets a custom {Requester} for this polling data source. + # + # By default, the builder uses an HTTP requester that communicates with + # LaunchDarkly's FDv2 polling endpoint. Use this method to provide a + # custom requester implementation for testing or non-standard environments. + # + # @param requester [Requester] A custom requester that implements the + # {Requester} interface + # @return [PollingDataSourceBuilder] self for chaining + # + # @see Requester + # + def requester(requester) + @requester = requester + self + end + + # + # Builds the polling data source with the configured parameters. + # + # This method is called internally by the SDK. You do not need to call it + # directly; instead, pass the builder to {ConfigBuilder#initializers} or + # {ConfigBuilder#synchronizers}. + # + # @param sdk_key [String] The SDK key + # @param config [LaunchDarkly::Config] The SDK configuration + # @return [LaunchDarkly::Impl::DataSystem::PollingDataSource] + # + def build(sdk_key, config) + http_opts = build_http_config + requester = @requester || LaunchDarkly::Impl::DataSystem::HTTPPollingRequester.new(sdk_key, http_opts, config) + LaunchDarkly::Impl::DataSystem::PollingDataSource.new(@poll_interval || DEFAULT_POLL_INTERVAL, requester, config.logger) + end + end + + # + # Builder for a polling data source that communicates with LaunchDarkly's + # FDv1 (Flag Delivery v1) polling endpoint. + # + # This builder is typically used with {ConfigBuilder#fdv1_compatible_synchronizer} + # to provide a fallback when the server signals that the environment should + # revert to the FDv1 protocol. + # + # Most users will not need to interact with this builder directly, as the + # predefined strategies ({DataSystem.default}, {DataSystem.streaming}, + # {DataSystem.polling}) already configure an appropriate FDv1 fallback. + # + # @see DataSystem.fdv1_fallback_ds_builder + # + class FDv1PollingDataSourceBuilder + include LaunchDarkly::DataSystem::DataSourceBuilderCommon + + # @return [String] The default base URI for FDv1 polling requests + DEFAULT_BASE_URI = "https://sdk.launchdarkly.com" + + # @return [Float] The default polling interval in seconds + DEFAULT_POLL_INTERVAL = 30 + + def initialize + @requester = nil + end + + # + # Sets the polling interval in seconds. + # + # This controls how frequently the SDK polls LaunchDarkly for updates. + # The default is {DEFAULT_POLL_INTERVAL} seconds. + # + # @param secs [Float] Polling interval in seconds + # @return [FDv1PollingDataSourceBuilder] self for chaining + # + def poll_interval(secs) + @poll_interval = secs + self + end + + # + # Sets a custom {Requester} for this polling data source. + # + # By default, the builder uses an HTTP requester that communicates with + # LaunchDarkly's FDv1 polling endpoint. Use this method to provide a + # custom requester implementation. + # + # @param requester [Requester] A custom requester that implements the + # {Requester} interface + # @return [FDv1PollingDataSourceBuilder] self for chaining + # + # @see Requester + # + def requester(requester) + @requester = requester + self + end + + # + # Builds the FDv1 polling data source with the configured parameters. + # + # This method is called internally by the SDK. You do not need to call it + # directly; instead, pass the builder to {ConfigBuilder#fdv1_compatible_synchronizer}. + # + # @param sdk_key [String] The SDK key + # @param config [LaunchDarkly::Config] The SDK configuration + # @return [LaunchDarkly::Impl::DataSystem::PollingDataSource] + # + def build(sdk_key, config) + http_opts = build_http_config + requester = @requester || LaunchDarkly::Impl::DataSystem::HTTPFDv1PollingRequester.new(sdk_key, http_opts, config) + LaunchDarkly::Impl::DataSystem::PollingDataSource.new(@poll_interval || DEFAULT_POLL_INTERVAL, requester, config.logger) + end + end + end +end diff --git a/lib/ldclient-rb/data_system/streaming_data_source_builder.rb b/lib/ldclient-rb/data_system/streaming_data_source_builder.rb new file mode 100644 index 00000000..7fdcf44b --- /dev/null +++ b/lib/ldclient-rb/data_system/streaming_data_source_builder.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "ldclient-rb/data_system/data_source_builder_common" + +module LaunchDarkly + module DataSystem + # + # Builder for a streaming data source that uses Server-Sent Events (SSE) + # to receive real-time updates from LaunchDarkly's Flag Delivery services. + # + # This builder can be used with {ConfigBuilder#synchronizers} to create + # custom data system configurations. Streaming provides the lowest latency + # for flag updates compared to polling. + # + # == Example + # + # streaming = LaunchDarkly::DataSystem.streaming_ds_builder + # .initial_reconnect_delay(2) + # .base_uri("https://custom-stream.example.com") + # + # data_system = LaunchDarkly::DataSystem.custom + # .synchronizers([streaming]) + # + # @see DataSystem.streaming_ds_builder + # + class StreamingDataSourceBuilder + include LaunchDarkly::DataSystem::DataSourceBuilderCommon + + # @return [String] The default base URI for streaming connections + DEFAULT_BASE_URI = "https://stream.launchdarkly.com" + + # @return [Float] The default initial reconnect delay in seconds + DEFAULT_INITIAL_RECONNECT_DELAY = 1 + + def initialize + # No initialization needed - defaults applied in build via nil-check + end + + # + # Sets the initial delay before reconnecting after a stream connection error. + # + # The SDK uses an exponential backoff strategy starting from this delay. + # The default is {DEFAULT_INITIAL_RECONNECT_DELAY} second. + # + # @param delay [Float] Delay in seconds + # @return [StreamingDataSourceBuilder] self for chaining + # + def initial_reconnect_delay(delay) + @initial_reconnect_delay = delay + self + end + + # + # Builds the streaming data source with the configured parameters. + # + # This method is called internally by the SDK. You do not need to call it + # directly; instead, pass the builder to {ConfigBuilder#synchronizers}. + # + # @param sdk_key [String] The SDK key + # @param config [LaunchDarkly::Config] The SDK configuration + # @return [LaunchDarkly::Impl::DataSystem::StreamingDataSource] + # + def build(sdk_key, config) + http_opts = build_http_config + LaunchDarkly::Impl::DataSystem::StreamingDataSource.new( + sdk_key, http_opts, + @initial_reconnect_delay || DEFAULT_INITIAL_RECONNECT_DELAY, + config + ) + end + end + end +end diff --git a/lib/ldclient-rb/impl/data_system/data_source_builder_common.rb b/lib/ldclient-rb/impl/data_system/data_source_builder_common.rb deleted file mode 100644 index c2eede08..00000000 --- a/lib/ldclient-rb/impl/data_system/data_source_builder_common.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -require "ldclient-rb/impl/data_system/http_config_options" - -module LaunchDarkly - module Impl - module DataSystem - # - # DataSourceBuilderCommon is a mixin that provides common HTTP configuration - # setters for data source builders (polling and streaming). - # - # Each builder that includes this module must define a DEFAULT_BASE_URI constant. - # - module DataSourceBuilderCommon - # - # Sets the base URI for HTTP requests. - # - # @param uri [String] - # @return [self] - # - def base_uri(uri) - @base_uri = uri - self - end - - # - # Sets a custom socket factory for HTTP connections. - # - # @param factory [Object] - # @return [self] - # - def socket_factory(factory) - @socket_factory = factory - self - end - - # - # Sets the read timeout for HTTP connections. - # - # @param timeout [Float] Timeout in seconds - # @return [self] - # - def read_timeout(timeout) - @read_timeout = timeout - self - end - - # - # Sets the connect timeout for HTTP connections. - # - # @param timeout [Float] Timeout in seconds - # @return [self] - # - def connect_timeout(timeout) - @connect_timeout = timeout - self - end - - # - # Builds an HttpConfigOptions instance from the current builder settings. - # Uses self.class::DEFAULT_BASE_URI if base_uri was not explicitly set. - # Read/connect timeouts default to HttpConfigOptions defaults if not set. - # - # @return [HttpConfigOptions] - # - private def build_http_config - HttpConfigOptions.new( - base_uri: (@base_uri || self.class::DEFAULT_BASE_URI).chomp("/"), - socket_factory: @socket_factory, - read_timeout: @read_timeout, - connect_timeout: @connect_timeout - ) - end - end - end - end -end diff --git a/lib/ldclient-rb/impl/data_system/polling.rb b/lib/ldclient-rb/impl/data_system/polling.rb index 7942571f..fe680f54 100644 --- a/lib/ldclient-rb/impl/data_system/polling.rb +++ b/lib/ldclient-rb/impl/data_system/polling.rb @@ -4,7 +4,7 @@ require "ldclient-rb/interfaces/data_system" require "ldclient-rb/impl/data_system" require "ldclient-rb/impl/data_system/protocolv2" -require "ldclient-rb/impl/data_system/data_source_builder_common" +require "ldclient-rb/data_system/polling_data_source_builder" require "ldclient-rb/impl/data_source/requestor" require "ldclient-rb/impl/util" require "concurrent" @@ -21,32 +21,6 @@ module DataSystem LD_ENVID_HEADER = "X-LD-EnvID" LD_FD_FALLBACK_HEADER = "X-LD-FD-Fallback" - # - # Requester protocol for polling data source - # - module Requester - # - # Fetches the data for the given selector. - # Returns a Result containing a tuple of [ChangeSet, headers], - # or an error if the data could not be retrieved. - # - # @param selector [LaunchDarkly::Interfaces::DataSystem::Selector, nil] - # @return [Result] - # - def fetch(selector) - raise NotImplementedError - end - - # - # Closes any persistent connections and releases resources. - # This method should be called when the requester is no longer needed. - # Implementations should handle being called multiple times gracefully. - # - def stop - # Optional - implementations may override if they need cleanup - end - end - # # PollingDataSource is a data source that can retrieve information from # LaunchDarkly either as an Initializer or as a Synchronizer. @@ -246,7 +220,7 @@ def stop # requests to the FDv2 polling endpoint. # class HTTPPollingRequester - include Requester + include LaunchDarkly::DataSystem::Requester # # @param sdk_key [String] @@ -338,7 +312,7 @@ def stop # requests to the FDv1 polling endpoint. # class HTTPFDv1PollingRequester - include Requester + include LaunchDarkly::DataSystem::Requester # # @param sdk_key [String] @@ -526,103 +500,6 @@ def self.fdv1_polling_payload_to_changeset(data) LaunchDarkly::Result.success(builder.finish(selector)) end - # - # Builder for a PollingDataSource. - # - class PollingDataSourceBuilder - include DataSourceBuilderCommon - - DEFAULT_BASE_URI = "https://sdk.launchdarkly.com" - DEFAULT_POLL_INTERVAL = 30 - - def initialize - @requester = nil - end - - # - # Sets the polling interval in seconds. - # - # @param secs [Float] Polling interval in seconds - # @return [PollingDataSourceBuilder] - # - def poll_interval(secs) - @poll_interval = secs - self - end - - # - # Sets a custom Requester for the PollingDataSource. - # - # @param requester [Requester] - # @return [PollingDataSourceBuilder] - # - def requester(requester) - @requester = requester - self - end - - # - # Builds the PollingDataSource with the configured parameters. - # - # @param sdk_key [String] - # @param config [LaunchDarkly::Config] - # @return [PollingDataSource] - # - def build(sdk_key, config) - http_opts = build_http_config - requester = @requester || HTTPPollingRequester.new(sdk_key, http_opts, config) - PollingDataSource.new(@poll_interval || DEFAULT_POLL_INTERVAL, requester, config.logger) - end - end - - # - # Builder for an FDv1 PollingDataSource. - # - class FDv1PollingDataSourceBuilder - include DataSourceBuilderCommon - - DEFAULT_BASE_URI = "https://sdk.launchdarkly.com" - DEFAULT_POLL_INTERVAL = 30 - - def initialize - @requester = nil - end - - # - # Sets the polling interval in seconds. - # - # @param secs [Float] Polling interval in seconds - # @return [FDv1PollingDataSourceBuilder] - # - def poll_interval(secs) - @poll_interval = secs - self - end - - # - # Sets a custom Requester for the PollingDataSource. - # - # @param requester [Requester] - # @return [FDv1PollingDataSourceBuilder] - # - def requester(requester) - @requester = requester - self - end - - # - # Builds the PollingDataSource with the configured parameters. - # - # @param sdk_key [String] - # @param config [LaunchDarkly::Config] - # @return [PollingDataSource] - # - def build(sdk_key, config) - http_opts = build_http_config - requester = @requester || HTTPFDv1PollingRequester.new(sdk_key, http_opts, config) - PollingDataSource.new(@poll_interval || DEFAULT_POLL_INTERVAL, requester, config.logger) - end - end end end end diff --git a/lib/ldclient-rb/impl/data_system/streaming.rb b/lib/ldclient-rb/impl/data_system/streaming.rb index 2947aa63..05a39e47 100644 --- a/lib/ldclient-rb/impl/data_system/streaming.rb +++ b/lib/ldclient-rb/impl/data_system/streaming.rb @@ -5,7 +5,7 @@ require "ldclient-rb/impl/data_system" require "ldclient-rb/impl/data_system/protocolv2" require "ldclient-rb/impl/data_system/polling" # For shared constants -require "ldclient-rb/impl/data_system/data_source_builder_common" +require "ldclient-rb/data_system/streaming_data_source_builder" require "ldclient-rb/impl/util" require "concurrent" require "json" @@ -356,46 +356,6 @@ def stop end end - # - # Builder for a StreamingDataSource. - # - class StreamingDataSourceBuilder - include DataSourceBuilderCommon - - DEFAULT_BASE_URI = "https://stream.launchdarkly.com" - DEFAULT_INITIAL_RECONNECT_DELAY = 1 - - def initialize - # No initialization needed - defaults applied in build via nil-check - end - - # - # Sets the initial delay before reconnecting after an error. - # - # @param delay [Float] Delay in seconds - # @return [StreamingDataSourceBuilder] - # - def initial_reconnect_delay(delay) - @initial_reconnect_delay = delay - self - end - - # - # Builds the StreamingDataSource with the configured parameters. - # - # @param sdk_key [String] - # @param config [LaunchDarkly::Config] - # @return [StreamingDataSource] - # - def build(sdk_key, config) - http_opts = build_http_config - StreamingDataSource.new( - sdk_key, http_opts, - @initial_reconnect_delay || DEFAULT_INITIAL_RECONNECT_DELAY, - config - ) - end - end end end end diff --git a/spec/impl/data_system/polling_initializer_spec.rb b/spec/impl/data_system/polling_initializer_spec.rb index c94d9fdb..dc699c36 100644 --- a/spec/impl/data_system/polling_initializer_spec.rb +++ b/spec/impl/data_system/polling_initializer_spec.rb @@ -11,7 +11,7 @@ module DataSystem let(:logger) { double("Logger", info: nil, warn: nil, error: nil, debug: nil) } class MockExceptionThrowingPollingRequester - include Requester + include LaunchDarkly::DataSystem::Requester def fetch(selector) raise "This is a mock exception for testing purposes." @@ -19,7 +19,7 @@ def fetch(selector) end class MockPollingRequester - include Requester + include LaunchDarkly::DataSystem::Requester def initialize(result) @result = result diff --git a/spec/impl/data_system/polling_synchronizer_spec.rb b/spec/impl/data_system/polling_synchronizer_spec.rb index c4fca3c8..f7d9031e 100644 --- a/spec/impl/data_system/polling_synchronizer_spec.rb +++ b/spec/impl/data_system/polling_synchronizer_spec.rb @@ -11,7 +11,7 @@ module DataSystem let(:logger) { double("Logger", info: nil, warn: nil, error: nil, debug: nil) } class ListBasedRequester - include Requester + include LaunchDarkly::DataSystem::Requester def initialize(results) @results = results @@ -24,7 +24,7 @@ def fetch(selector) end class RequesterWithCleanup - include Requester + include LaunchDarkly::DataSystem::Requester attr_reader :stop_called diff --git a/spec/impl/data_system/streaming_headers_spec.rb b/spec/impl/data_system/streaming_headers_spec.rb index e646e7cf..2b059b9b 100644 --- a/spec/impl/data_system/streaming_headers_spec.rb +++ b/spec/impl/data_system/streaming_headers_spec.rb @@ -22,7 +22,7 @@ module DataSystem ) end - let(:synchronizer) { StreamingDataSourceBuilder.new.build(sdk_key, config) } + let(:synchronizer) { LaunchDarkly::DataSystem::StreamingDataSourceBuilder.new.build(sdk_key, config) } describe "on_error callback" do it "triggers FDv1 fallback when X-LD-FD-FALLBACK header is true" do diff --git a/spec/impl/data_system/streaming_synchronizer_spec.rb b/spec/impl/data_system/streaming_synchronizer_spec.rb index 04b20399..11351e76 100644 --- a/spec/impl/data_system/streaming_synchronizer_spec.rb +++ b/spec/impl/data_system/streaming_synchronizer_spec.rb @@ -34,7 +34,7 @@ def initialize(type, data = nil) end describe '#process_message' do - let(:synchronizer) { StreamingDataSourceBuilder.new.build(sdk_key, config) } + let(:synchronizer) { LaunchDarkly::DataSystem::StreamingDataSourceBuilder.new.build(sdk_key, config) } let(:change_set_builder) { LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new } let(:envid) { nil } @@ -312,7 +312,7 @@ def initialize(type, data = nil) end describe 'diagnostic event recording' do - let(:synchronizer) { StreamingDataSourceBuilder.new.build(sdk_key, config) } + let(:synchronizer) { LaunchDarkly::DataSystem::StreamingDataSourceBuilder.new.build(sdk_key, config) } it "logs successful connection when diagnostic_accumulator is provided" do diagnostic_accumulator = double("DiagnosticAccumulator")