From 673a30efa30762324956997334d2719f48b95c6c Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 28 Jan 2026 22:40:49 +0000 Subject: [PATCH 1/6] chore: Allow individual http settings in fdv2 --- lib/ldclient-rb/config.rb | 41 +++++----- lib/ldclient-rb/data_system.rb | 36 ++++---- .../data_system/data_source_builder_common.rb | 79 ++++++++++++++++++ lib/ldclient-rb/impl/data_system/fdv2.rb | 6 +- .../impl/data_system/http_config_options.rb | 32 ++++++++ lib/ldclient-rb/impl/data_system/polling.rb | 82 +++++++++++++------ lib/ldclient-rb/impl/data_system/streaming.rb | 47 ++++++++--- 7 files changed, 241 insertions(+), 82 deletions(-) create mode 100644 lib/ldclient-rb/impl/data_system/data_source_builder_common.rb create mode 100644 lib/ldclient-rb/impl/data_system/http_config_options.rb diff --git a/lib/ldclient-rb/config.rb b/lib/ldclient-rb/config.rb index ca6afa5d..f82860b0 100644 --- a/lib/ldclient-rb/config.rb +++ b/lib/ldclient-rb/config.rb @@ -1,5 +1,8 @@ require "logger" require "ldclient-rb/impl/cache_store" +require "ldclient-rb/impl/data_system/http_config_options" +require "ldclient-rb/impl/data_system/polling" +require "ldclient-rb/impl/data_system/streaming" module LaunchDarkly # @@ -465,7 +468,7 @@ def self.default_capacity # @return [String] "https://sdk.launchdarkly.com" # def self.default_base_uri - "https://sdk.launchdarkly.com" + Impl::DataSystem::PollingDataSourceBuilder::DEFAULT_BASE_URI end # @@ -473,7 +476,7 @@ def self.default_base_uri # @return [String] "https://stream.launchdarkly.com" # def self.default_stream_uri - "https://stream.launchdarkly.com" + Impl::DataSystem::StreamingDataSourceBuilder::DEFAULT_BASE_URI end # @@ -505,7 +508,7 @@ def self.default_flush_interval # @return [Float] 10 # def self.default_read_timeout - 10 + Impl::DataSystem::HttpConfigOptions::DEFAULT_READ_TIMEOUT end # @@ -513,7 +516,7 @@ def self.default_read_timeout # @return [Float] 1 # def self.default_initial_reconnect_delay - 1 + Impl::DataSystem::StreamingDataSourceBuilder::DEFAULT_INITIAL_RECONNECT_DELAY end # @@ -521,7 +524,7 @@ def self.default_initial_reconnect_delay # @return [Float] 2 # def self.default_connect_timeout - 2 + Impl::DataSystem::HttpConfigOptions::DEFAULT_CONNECT_TIMEOUT end # @@ -575,7 +578,7 @@ def self.default_offline # @return [Float] 30 # def self.default_poll_interval - 30 + Impl::DataSystem::PollingDataSourceBuilder::DEFAULT_POLL_INTERVAL end # @@ -699,13 +702,13 @@ def initialize(store:, context_cache_size: nil, context_cache_time: nil, status_ # class DataSystemConfig # - # @param initializers [Array LaunchDarkly::Interfaces::DataSystem::Initializer>, nil] The (optional) array of builder procs - # @param primary_synchronizer [Proc(Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer, nil] The (optional) builder proc for primary synchronizer - # @param secondary_synchronizer [Proc(Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer, nil] The (optional) builder proc for secondary synchronizer + # @param initializers [Array<#build(String, Config)>, nil] The (optional) array of builders + # @param primary_synchronizer [#build(String, Config), nil] The (optional) builder for primary synchronizer + # @param secondary_synchronizer [#build(String, Config), nil] The (optional) builder for secondary synchronizer # @param data_store_mode [Symbol] The (optional) data store mode # @param data_store [LaunchDarkly::Interfaces::FeatureStore, nil] The (optional) data store - # @param fdv1_fallback_synchronizer [Proc(Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer, nil] - # The (optional) builder proc for FDv1-compatible fallback synchronizer + # @param fdv1_fallback_synchronizer [#build(String, Config), nil] + # The (optional) builder for FDv1-compatible fallback synchronizer # def initialize(initializers: nil, primary_synchronizer: nil, secondary_synchronizer: nil, data_store_mode: LaunchDarkly::Interfaces::DataSystem::DataStoreMode::READ_ONLY, data_store: nil, fdv1_fallback_synchronizer: nil) @@ -717,16 +720,16 @@ def initialize(initializers: nil, primary_synchronizer: nil, secondary_synchroni @fdv1_fallback_synchronizer = fdv1_fallback_synchronizer end - # The initializers for the data system. Each proc takes sdk_key and Config and returns an Initializer. - # @return [Array LaunchDarkly::Interfaces::DataSystem::Initializer>, nil] + # The initializers for the data system. Each builder responds to build(sdk_key, config) and returns an Initializer. + # @return [Array<#build(String, Config)>, nil] attr_reader :initializers - # The primary synchronizer builder. Takes sdk_key and Config and returns a Synchronizer. - # @return [Proc(String, Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer, nil] + # The primary synchronizer builder. Responds to build(sdk_key, config) and returns a Synchronizer. + # @return [#build(String, Config), nil] attr_reader :primary_synchronizer - # The secondary synchronizer builder. Takes sdk_key and Config and returns a Synchronizer. - # @return [Proc(String, Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer, nil] + # The secondary synchronizer builder. Responds to build(sdk_key, config) and returns a Synchronizer. + # @return [#build(String, Config), nil] attr_reader :secondary_synchronizer # The data store mode. @@ -737,8 +740,8 @@ def initialize(initializers: nil, primary_synchronizer: nil, secondary_synchroni # @return [LaunchDarkly::Interfaces::FeatureStore, nil] attr_reader :data_store - # The FDv1-compatible fallback synchronizer builder. Takes sdk_key and Config and returns a Synchronizer. - # @return [Proc(String, Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer, nil] + # The FDv1-compatible fallback synchronizer builder. Responds to build(sdk_key, config) and returns a Synchronizer. + # @return [#build(String, Config), nil] attr_reader :fdv1_fallback_synchronizer end end diff --git a/lib/ldclient-rb/data_system.rb b/lib/ldclient-rb/data_system.rb index 038dea03..2e002b63 100644 --- a/lib/ldclient-rb/data_system.rb +++ b/lib/ldclient-rb/data_system.rb @@ -28,8 +28,8 @@ def initialize # # Sets the initializers for the data system. # - # @param initializers [Array LaunchDarkly::Interfaces::DataSystem::Initializer>] - # Array of builder procs that take sdk_key and Config and return an Initializer + # @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) @@ -40,9 +40,8 @@ def initializers(initializers) # # Sets the synchronizers for the data system. # - # @param primary [Proc(String, Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer] Builder proc that takes sdk_key and Config and returns the primary Synchronizer - # @param secondary [Proc(String, Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer, nil] - # Builder proc that takes sdk_key and Config and returns the secondary Synchronizer + # @param primary [#build(String, Config)] Builder that responds to build(sdk_key, config) and returns the primary Synchronizer + # @param secondary [#build(String, Config), nil] Builder that responds to build(sdk_key, config) and returns the secondary Synchronizer # @return [ConfigBuilder] self for chaining # def synchronizers(primary, secondary = nil) @@ -55,8 +54,7 @@ def synchronizers(primary, secondary = nil) # Configures the SDK with a fallback synchronizer that is compatible with # the Flag Delivery v1 API. # - # @param fallback [Proc(String, Config) => LaunchDarkly::Interfaces::DataSystem::Synchronizer] - # Builder proc that takes sdk_key and Config and returns the fallback Synchronizer + # @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) @@ -100,42 +98,36 @@ def build end # - # Returns a builder proc for creating a polling data source. + # 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 [Proc] A proc that takes (sdk_key, config) and returns a polling data source + # @return [LaunchDarkly::Impl::DataSystem::PollingDataSourceBuilder] # def self.polling_ds_builder - lambda do |sdk_key, config| - LaunchDarkly::Impl::DataSystem::PollingDataSourceBuilder.new(sdk_key, config).build - end + LaunchDarkly::Impl::DataSystem::PollingDataSourceBuilder.new end # - # Returns a builder proc for creating an FDv1 fallback polling data source. + # Returns a builder for creating an FDv1 fallback polling data source. # This is a building block that can be used with {ConfigBuilder#fdv1_compatible_synchronizer} # to provide FDv1 compatibility in custom data system configurations. # - # @return [Proc] A proc that takes (sdk_key, config) and returns an FDv1 polling data source + # @return [LaunchDarkly::Impl::DataSystem::FDv1PollingDataSourceBuilder] # def self.fdv1_fallback_ds_builder - lambda do |sdk_key, config| - LaunchDarkly::Impl::DataSystem::FDv1PollingDataSourceBuilder.new(sdk_key, config).build - end + LaunchDarkly::Impl::DataSystem::FDv1PollingDataSourceBuilder.new end # - # Returns a builder proc for creating a streaming data source. + # Returns a builder for creating a streaming data source. # This is a building block that can be used with {ConfigBuilder#synchronizers} # to create custom data system configurations. # - # @return [Proc] A proc that takes (sdk_key, config) and returns a streaming data source + # @return [LaunchDarkly::Impl::DataSystem::StreamingDataSourceBuilder] # def self.streaming_ds_builder - lambda do |sdk_key, config| - LaunchDarkly::Impl::DataSystem::StreamingDataSourceBuilder.new(sdk_key, config).build - end + LaunchDarkly::Impl::DataSystem::StreamingDataSourceBuilder.new 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 new file mode 100644 index 00000000..bf180bc5 --- /dev/null +++ b/lib/ldclient-rb/impl/data_system/data_source_builder_common.rb @@ -0,0 +1,79 @@ +# 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 + + private + + # + # 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] + # + def build_http_config + HttpConfigOptions.new( + base_uri: @base_uri || self.class::DEFAULT_BASE_URI, + 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/fdv2.rb b/lib/ldclient-rb/impl/data_system/fdv2.rb index 67f5bf52..5fa0631f 100644 --- a/lib/ldclient-rb/impl/data_system/fdv2.rb +++ b/lib/ldclient-rb/impl/data_system/fdv2.rb @@ -214,7 +214,7 @@ def run_initializers return if @stop_event.set? begin - initializer = initializer_builder.call(@sdk_key, @config) + initializer = initializer_builder.build(@sdk_key, @config) @logger.info { "[LDClient] Attempting to initialize via #{initializer.name}" } basis_result = initializer.fetch(@store) @@ -269,7 +269,7 @@ def synchronizer_loop # Try primary synchronizer begin @lock.synchronize do - primary_sync = @primary_synchronizer_builder.call(@sdk_key, @config) + primary_sync = @primary_synchronizer_builder.build(@sdk_key, @config) if primary_sync.respond_to?(:set_diagnostic_accumulator) && @diagnostic_accumulator primary_sync.set_diagnostic_accumulator(@diagnostic_accumulator) end @@ -304,7 +304,7 @@ def synchronizer_loop next if @secondary_synchronizer_builder.nil? @lock.synchronize do - secondary_sync = @secondary_synchronizer_builder.call(@sdk_key, @config) + secondary_sync = @secondary_synchronizer_builder.build(@sdk_key, @config) if secondary_sync.respond_to?(:set_diagnostic_accumulator) && @diagnostic_accumulator secondary_sync.set_diagnostic_accumulator(@diagnostic_accumulator) end diff --git a/lib/ldclient-rb/impl/data_system/http_config_options.rb b/lib/ldclient-rb/impl/data_system/http_config_options.rb new file mode 100644 index 00000000..af8313c8 --- /dev/null +++ b/lib/ldclient-rb/impl/data_system/http_config_options.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module LaunchDarkly + module Impl + module DataSystem + # + # HttpConfigOptions contains HTTP connection configuration settings. + # This class is created by data source builders and passed to v2 Requesters/DataSources. + # + class HttpConfigOptions + # Generic HTTP defaults - base URIs live in the respective builders + DEFAULT_READ_TIMEOUT = 10 + DEFAULT_CONNECT_TIMEOUT = 2 + + attr_reader :base_uri, :socket_factory, :read_timeout, :connect_timeout + + # + # @param base_uri [String] The base URI for HTTP requests + # @param socket_factory [Object, nil] Optional socket factory for custom connections + # @param read_timeout [Float, nil] Read timeout in seconds (defaults to DEFAULT_READ_TIMEOUT) + # @param connect_timeout [Float, nil] Connect timeout in seconds (defaults to DEFAULT_CONNECT_TIMEOUT) + # + def initialize(base_uri:, socket_factory: nil, read_timeout: nil, connect_timeout: nil) + @base_uri = base_uri + @socket_factory = socket_factory + @read_timeout = read_timeout || DEFAULT_READ_TIMEOUT + @connect_timeout = connect_timeout || DEFAULT_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 b1ead5ea..f347c011 100644 --- a/lib/ldclient-rb/impl/data_system/polling.rb +++ b/lib/ldclient-rb/impl/data_system/polling.rb @@ -4,6 +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/impl/data_source/requestor" require "ldclient-rb/impl/util" require "concurrent" @@ -249,14 +250,15 @@ class HTTPPollingRequester # # @param sdk_key [String] - # @param config [LaunchDarkly::Config] + # @param http_config [HttpConfigOptions] HTTP connection settings + # @param config [LaunchDarkly::Config] Used for global header settings # - def initialize(sdk_key, config) + def initialize(sdk_key, http_config, config) @etag = nil @config = config @sdk_key = sdk_key - @poll_uri = config.base_uri + FDV2_POLLING_ENDPOINT - @http_client = Impl::Util.new_http_client(config.base_uri, config) + @poll_uri = http_config.base_uri + FDV2_POLLING_ENDPOINT + @http_client = Impl::Util.new_http_client(http_config.base_uri, http_config) .use(:auto_inflate) .headers("Accept-Encoding" => "gzip") end @@ -340,14 +342,16 @@ class HTTPFDv1PollingRequester # # @param sdk_key [String] - # @param config [LaunchDarkly::Config] + # @param http_config [HttpConfigOptions] HTTP connection settings + # @param config [LaunchDarkly::Config] Used for global header settings and payload_filter_key # - def initialize(sdk_key, config) + def initialize(sdk_key, http_config, config) @etag = nil @config = config @sdk_key = sdk_key - @poll_uri = config.base_uri + FDV1_POLLING_ENDPOINT - @http_client = Impl::Util.new_http_client(config.base_uri, config) + @poll_uri = http_config.base_uri + FDV1_POLLING_ENDPOINT + # http_config duck-types with config for socket_factory, read_timeout, connect_timeout + @http_client = Impl::Util.new_http_client(http_config.base_uri, http_config) .use(:auto_inflate) .headers("Accept-Encoding" => "gzip") end @@ -527,14 +531,24 @@ def self.fdv1_polling_payload_to_changeset(data) # Builder for a PollingDataSource. # class PollingDataSourceBuilder + include DataSourceBuilderCommon + + DEFAULT_BASE_URI = "https://sdk.launchdarkly.com" + DEFAULT_POLL_INTERVAL = 30 + + def initialize + @requester = nil + end + # - # @param sdk_key [String] - # @param config [LaunchDarkly::Config] + # Sets the polling interval in seconds. # - def initialize(sdk_key, config) - @sdk_key = sdk_key - @config = config - @requester = nil + # @param secs [Float] Polling interval in seconds + # @return [PollingDataSourceBuilder] + # + def poll_interval(secs) + @poll_interval = secs + self end # @@ -551,11 +565,14 @@ def requester(requester) # # Builds the PollingDataSource with the configured parameters. # + # @param sdk_key [String] + # @param config [LaunchDarkly::Config] # @return [PollingDataSource] # - def build - requester = @requester || HTTPPollingRequester.new(@sdk_key, @config) - PollingDataSource.new(@config.poll_interval, requester, @config.logger) + 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 @@ -563,14 +580,24 @@ def build # 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 + # - # @param sdk_key [String] - # @param config [LaunchDarkly::Config] + # Sets the polling interval in seconds. # - def initialize(sdk_key, config) - @sdk_key = sdk_key - @config = config - @requester = nil + # @param secs [Float] Polling interval in seconds + # @return [FDv1PollingDataSourceBuilder] + # + def poll_interval(secs) + @poll_interval = secs + self end # @@ -587,11 +614,14 @@ def requester(requester) # # Builds the PollingDataSource with the configured parameters. # + # @param sdk_key [String] + # @param config [LaunchDarkly::Config] # @return [PollingDataSource] # - def build - requester = @requester || HTTPFDv1PollingRequester.new(@sdk_key, @config) - PollingDataSource.new(@config.poll_interval, requester, @config.logger) + 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 diff --git a/lib/ldclient-rb/impl/data_system/streaming.rb b/lib/ldclient-rb/impl/data_system/streaming.rb index 4fb46e84..2947aa63 100644 --- a/lib/ldclient-rb/impl/data_system/streaming.rb +++ b/lib/ldclient-rb/impl/data_system/streaming.rb @@ -5,6 +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/impl/util" require "concurrent" require "json" @@ -31,10 +32,14 @@ class StreamingDataSource # # @param sdk_key [String] - # @param config [LaunchDarkly::Config] + # @param http_config [HttpConfigOptions] HTTP connection settings + # @param initial_reconnect_delay [Float] Initial delay before reconnecting after an error + # @param config [LaunchDarkly::Config] Used for global header settings # - def initialize(sdk_key, config) + def initialize(sdk_key, http_config, initial_reconnect_delay, config) @sdk_key = sdk_key + @http_config = http_config + @initial_reconnect_delay = initial_reconnect_delay @config = config @logger = config.logger @name = "StreamingDataSourceV2" @@ -68,14 +73,14 @@ def sync(ss) change_set_builder = LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new envid = nil - base_uri = @config.stream_uri + FDV2_STREAMING_ENDPOINT + base_uri = @http_config.base_uri + FDV2_STREAMING_ENDPOINT headers = Impl::Util.default_http_headers(@sdk_key, @config) opts = { headers: headers, read_timeout: STREAM_READ_TIMEOUT, logger: @logger, - socket_factory: @config.socket_factory, - reconnect_time: @config.initial_reconnect_delay, + socket_factory: @http_config.socket_factory, + reconnect_time: @initial_reconnect_delay, } @sse = SSE::Client.new(base_uri, **opts) do |client| @@ -355,22 +360,40 @@ def stop # 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 + # - # @param sdk_key [String] - # @param config [LaunchDarkly::Config] + # Sets the initial delay before reconnecting after an error. # - def initialize(sdk_key, config) - @sdk_key = sdk_key - @config = config + # @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 - StreamingDataSource.new(@sdk_key, @config) + 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 From 73cb0af536e8bab51a366fd5db018fa8b2b5ca28 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 28 Jan 2026 22:46:29 +0000 Subject: [PATCH 2/6] simplify new_http_client parameters --- lib/ldclient-rb/events.rb | 2 +- lib/ldclient-rb/impl/data_source/requestor.rb | 11 ++++++++-- lib/ldclient-rb/impl/data_system/polling.rb | 5 ++--- lib/ldclient-rb/impl/event_sender.rb | 15 ++++++++++---- lib/ldclient-rb/impl/util.rb | 20 ++++++++++++------- 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/lib/ldclient-rb/events.rb b/lib/ldclient-rb/events.rb index 3447a55e..b5df9711 100644 --- a/lib/ldclient-rb/events.rb +++ b/lib/ldclient-rb/events.rb @@ -156,7 +156,7 @@ def initialize(sdk_key, config, client = nil, diagnostic_accumulator = nil, test @inbox_full = Concurrent::AtomicBoolean.new(false) event_sender = (test_properties || {})[:event_sender] || - Impl::EventSender.new(sdk_key, config, client || Impl::Util.new_http_client(config.events_uri, config)) + Impl::EventSender.new(sdk_key, config) @timestamp_fn = (test_properties || {})[:timestamp_fn] || proc { Impl::Util.current_time_millis } @omit_anonymous_contexts = config.omit_anonymous_contexts diff --git a/lib/ldclient-rb/impl/data_source/requestor.rb b/lib/ldclient-rb/impl/data_source/requestor.rb index b612df72..9bf81417 100644 --- a/lib/ldclient-rb/impl/data_source/requestor.rb +++ b/lib/ldclient-rb/impl/data_source/requestor.rb @@ -1,5 +1,6 @@ require "ldclient-rb/impl/model/serialization" require "ldclient-rb/impl/util" +require "ldclient-rb/impl/data_system/http_config_options" require "concurrent/atomics" require "json" @@ -26,7 +27,13 @@ class Requestor def initialize(sdk_key, config) @sdk_key = sdk_key @config = config - @http_client = Impl::Util.new_http_client(config.base_uri, config) + @http_config = DataSystem::HttpConfigOptions.new( + base_uri: config.base_uri, + socket_factory: config.socket_factory, + read_timeout: config.read_timeout, + connect_timeout: config.connect_timeout + ) + @http_client = Impl::Util.new_http_client(@http_config) .use(:auto_inflate) .headers("Accept-Encoding" => "gzip") @cache = @config.cache_store @@ -48,7 +55,7 @@ def stop def make_request(path) uri = URI( - Util.add_payload_filter_key(@config.base_uri + path, @config) + Util.add_payload_filter_key(@http_config.base_uri + path, @config) ) headers = {} Impl::Util.default_http_headers(@sdk_key, @config).each { |k, v| headers[k] = v } diff --git a/lib/ldclient-rb/impl/data_system/polling.rb b/lib/ldclient-rb/impl/data_system/polling.rb index f347c011..3ca71ae4 100644 --- a/lib/ldclient-rb/impl/data_system/polling.rb +++ b/lib/ldclient-rb/impl/data_system/polling.rb @@ -258,7 +258,7 @@ def initialize(sdk_key, http_config, config) @config = config @sdk_key = sdk_key @poll_uri = http_config.base_uri + FDV2_POLLING_ENDPOINT - @http_client = Impl::Util.new_http_client(http_config.base_uri, http_config) + @http_client = Impl::Util.new_http_client(http_config) .use(:auto_inflate) .headers("Accept-Encoding" => "gzip") end @@ -350,8 +350,7 @@ def initialize(sdk_key, http_config, config) @config = config @sdk_key = sdk_key @poll_uri = http_config.base_uri + FDV1_POLLING_ENDPOINT - # http_config duck-types with config for socket_factory, read_timeout, connect_timeout - @http_client = Impl::Util.new_http_client(http_config.base_uri, http_config) + @http_client = Impl::Util.new_http_client(http_config) .use(:auto_inflate) .headers("Accept-Encoding" => "gzip") end diff --git a/lib/ldclient-rb/impl/event_sender.rb b/lib/ldclient-rb/impl/event_sender.rb index 754aee6d..660c7df7 100644 --- a/lib/ldclient-rb/impl/event_sender.rb +++ b/lib/ldclient-rb/impl/event_sender.rb @@ -1,5 +1,6 @@ require "ldclient-rb/impl/unbounded_pool" require "ldclient-rb/impl/util" +require "ldclient-rb/impl/data_system/http_config_options" require "securerandom" require "http" @@ -14,15 +15,21 @@ class EventSender CURRENT_SCHEMA_VERSION = 4 DEFAULT_RETRY_INTERVAL = 1 - def initialize(sdk_key, config, http_client = nil, retry_interval = DEFAULT_RETRY_INTERVAL) + def initialize(sdk_key, config, retry_interval = DEFAULT_RETRY_INTERVAL) @sdk_key = sdk_key @config = config - @events_uri = config.events_uri + "/bulk" - @diagnostic_uri = config.events_uri + "/diagnostic" + @http_config = DataSystem::HttpConfigOptions.new( + base_uri: config.events_uri, + socket_factory: config.socket_factory, + read_timeout: config.read_timeout, + connect_timeout: config.connect_timeout + ) + @events_uri = @http_config.base_uri + "/bulk" + @diagnostic_uri = @http_config.base_uri + "/diagnostic" @logger = config.logger @retry_interval = retry_interval @http_client_pool = UnboundedPool.new( - lambda { Impl::Util.new_http_client(@config.events_uri, @config) }, + lambda { Impl::Util.new_http_client(@http_config) }, lambda { |client| client.close }) end diff --git a/lib/ldclient-rb/impl/util.rb b/lib/ldclient-rb/impl/util.rb index 46fc0b00..48dc33a1 100644 --- a/lib/ldclient-rb/impl/util.rb +++ b/lib/ldclient-rb/impl/util.rb @@ -118,12 +118,18 @@ def self.add_payload_filter_key(uri, config) end end - def self.new_http_client(uri_s, config) + # + # Creates a new persistent HTTP client with the given configuration. + # + # @param http_config [LaunchDarkly::Impl::DataSystem::HttpConfigOptions] HTTP connection settings + # @return [HTTP::Client] + # + def self.new_http_client(http_config) http_client_options = {} - if config.socket_factory - http_client_options["socket_class"] = config.socket_factory + if http_config.socket_factory + http_client_options["socket_class"] = http_config.socket_factory end - proxy = URI.parse(uri_s).find_proxy + proxy = URI.parse(http_config.base_uri).find_proxy unless proxy.nil? http_client_options["proxy"] = { proxy_address: proxy.host, @@ -134,10 +140,10 @@ def self.new_http_client(uri_s, config) end HTTP::Client.new(http_client_options) .timeout({ - read: config.read_timeout, - connect: config.connect_timeout, + read: http_config.read_timeout, + connect: http_config.connect_timeout, }) - .persistent(uri_s) + .persistent(http_config.base_uri) end def self.log_exception(logger, message, exc) From 3bcc1f8b2d4d7f86987f9c2608c3089340bdb2ca Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 29 Jan 2026 14:46:31 +0000 Subject: [PATCH 3/6] fix tests --- lib/ldclient-rb/integrations/test_data_v2.rb | 32 +++++++---- spec/impl/data_system/fdv2_datasystem_spec.rb | 55 +++++++++++-------- .../impl/data_system/fdv2_persistence_spec.rb | 22 ++++---- .../data_system/streaming_headers_spec.rb | 2 +- .../streaming_synchronizer_spec.rb | 4 +- spec/impl/event_sender_spec.rb | 4 +- 6 files changed, 69 insertions(+), 50 deletions(-) diff --git a/lib/ldclient-rb/integrations/test_data_v2.rb b/lib/ldclient-rb/integrations/test_data_v2.rb index 1482b805..cddd73ae 100644 --- a/lib/ldclient-rb/integrations/test_data_v2.rb +++ b/lib/ldclient-rb/integrations/test_data_v2.rb @@ -223,25 +223,33 @@ def use_preconfigured_segment(segment) end # - # Creates an initializer that can be used with the FDv2 data system. + # Returns a builder for creating a data source that can be used with the FDv2 data system + # as either an initializer or synchronizer. # - # @param sdk_key [String] the SDK key - # @param config [LaunchDarkly::Config] the SDK configuration - # @return [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] a test data initializer + # @return [TestDataV2Builder] a builder for a test data source # - def build_initializer(sdk_key, config) - LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2.new(self) + def test_data_ds_builder + TestDataV2Builder.new(self) + end + end + + # + # Builder for TestDataV2 data sources (works for both initializers and synchronizers). + # + class TestDataV2Builder + def initialize(test_data) + @test_data = test_data end # - # Creates a synchronizer that can be used with the FDv2 data system. + # Builds the data source. # - # @param sdk_key [String] the SDK key - # @param config [LaunchDarkly::Config] the SDK configuration - # @return [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] a test data synchronizer + # @param sdk_key [String] the SDK key (unused, for interface compatibility) + # @param config [LaunchDarkly::Config] the SDK configuration (unused, for interface compatibility) + # @return [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] a test data source # - def build_synchronizer(sdk_key, config) - LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2.new(self) + def build(sdk_key, config) + LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2.new(@test_data) end end end diff --git a/spec/impl/data_system/fdv2_datasystem_spec.rb b/spec/impl/data_system/fdv2_datasystem_spec.rb index c94cab9f..488a0fd9 100644 --- a/spec/impl/data_system/fdv2_datasystem_spec.rb +++ b/spec/impl/data_system/fdv2_datasystem_spec.rb @@ -7,6 +7,17 @@ module LaunchDarkly module Impl module DataSystem + # Helper class that wraps a data source in a builder interface for testing + class MockBuilder + def initialize(data_source) + @data_source = data_source + end + + def build(_sdk_key, _config) + @data_source + end + end + describe FDv2 do let(:sdk_key) { "test-sdk-key" } let(:config) { LaunchDarkly::Config.new(logger: $null_log) } @@ -24,8 +35,8 @@ module DataSystem td_synchronizer.update(td_synchronizer.flag("flagkey").on(false)) data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new - .initializers([td_initializer.method(:build_initializer)]) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .initializers([td_initializer.test_data_ds_builder]) + .synchronizers(td_synchronizer.test_data_ds_builder) .build fdv2 = FDv2.new(sdk_key, config, data_system_config) @@ -67,7 +78,7 @@ module DataSystem td = LaunchDarkly::Integrations::TestDataV2.data_source data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td.method(:build_synchronizer)) + .synchronizers(td.test_data_ds_builder) .build fdv2 = FDv2.new(sdk_key, config, data_system_config) @@ -99,7 +110,7 @@ module DataSystem td = LaunchDarkly::Integrations::TestDataV2.data_source data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td.method(:build_synchronizer)) + .synchronizers(td.test_data_ds_builder) .build fdv2 = FDv2.new(sdk_key, config, data_system_config) @@ -126,10 +137,10 @@ module DataSystem td.update(td.flag("flagkey").on(true)) data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new - .initializers([td.method(:build_initializer)]) + .initializers([td.test_data_ds_builder]) .synchronizers( - lambda { |_, _| mock_primary }, - td.method(:build_synchronizer) + MockBuilder.new(mock_primary), + td.test_data_ds_builder ) .build @@ -179,10 +190,10 @@ module DataSystem td.update(td.flag("flagkey").on(true)) data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new - .initializers([td.method(:build_initializer)]) + .initializers([td.test_data_ds_builder]) .synchronizers( - lambda { |_, _| mock_primary }, - lambda { |_, _| mock_secondary } + MockBuilder.new(mock_primary), + MockBuilder.new(mock_secondary) ) .build @@ -225,8 +236,8 @@ module DataSystem data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(lambda { |_, _| mock_primary }) - .fdv1_compatible_synchronizer(td_fdv1.method(:build_synchronizer)) + .synchronizers(MockBuilder.new(mock_primary)) + .fdv1_compatible_synchronizer(td_fdv1.test_data_ds_builder) .build changed = Concurrent::Event.new @@ -274,8 +285,8 @@ module DataSystem data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(lambda { |_, _| mock_primary }) - .fdv1_compatible_synchronizer(td_fdv1.method(:build_synchronizer)) + .synchronizers(MockBuilder.new(mock_primary)) + .fdv1_compatible_synchronizer(td_fdv1.test_data_ds_builder) .build changed = Concurrent::Event.new @@ -333,9 +344,9 @@ module DataSystem td_fdv1.update(td_fdv1.flag("fdv1replacementflag").on(true)) data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new - .initializers([td_initializer.method(:build_initializer)]) - .synchronizers(lambda { |_, _| mock_primary }) - .fdv1_compatible_synchronizer(td_fdv1.method(:build_synchronizer)) + .initializers([td_initializer.test_data_ds_builder]) + .synchronizers(MockBuilder.new(mock_primary)) + .fdv1_compatible_synchronizer(td_fdv1.test_data_ds_builder) .build changed = Concurrent::Event.new @@ -393,10 +404,10 @@ module DataSystem data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) .synchronizers( - lambda { |_, _| mock_primary }, - lambda { |_, _| mock_secondary } + MockBuilder.new(mock_primary), + MockBuilder.new(mock_secondary) ) - .fdv1_compatible_synchronizer(td_fdv1.method(:build_synchronizer)) + .fdv1_compatible_synchronizer(td_fdv1.test_data_ds_builder) .build fdv2 = FDv2.new(sdk_key, config, data_system_config) @@ -433,8 +444,8 @@ module DataSystem data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(lambda { |_, _| mock_primary }) - .fdv1_compatible_synchronizer(td_fdv1.method(:build_synchronizer)) + .synchronizers(MockBuilder.new(mock_primary)) + .fdv1_compatible_synchronizer(td_fdv1.test_data_ds_builder) .build fdv2 = FDv2.new(sdk_key, config, data_system_config) diff --git a/spec/impl/data_system/fdv2_persistence_spec.rb b/spec/impl/data_system/fdv2_persistence_spec.rb index ebee45ff..5ce1c075 100644 --- a/spec/impl/data_system/fdv2_persistence_spec.rb +++ b/spec/impl/data_system/fdv2_persistence_spec.rb @@ -124,7 +124,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_write) .build @@ -180,7 +180,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_write) .build @@ -210,7 +210,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_write) .build @@ -240,7 +240,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_write) .build @@ -283,7 +283,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_only) .build @@ -307,7 +307,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_write) .build @@ -353,7 +353,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_only) .build @@ -400,8 +400,8 @@ def get_data_snapshot td_synchronizer.update(td_synchronizer.flag("sync-flag").on(false)) data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new - .initializers([td_initializer.method(:build_initializer)]) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .initializers([td_initializer.test_data_ds_builder]) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_write) .build @@ -440,7 +440,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(persistent_store, :read_write) .build @@ -467,7 +467,7 @@ def get_data_snapshot data_system_config = LaunchDarkly::DataSystem::ConfigBuilder.new .initializers(nil) - .synchronizers(td_synchronizer.method(:build_synchronizer)) + .synchronizers(td_synchronizer.test_data_ds_builder) .data_store(nil, :read_write) # No persistent store .build diff --git a/spec/impl/data_system/streaming_headers_spec.rb b/spec/impl/data_system/streaming_headers_spec.rb index 2ebcf298..e646e7cf 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) { StreamingDataSource.new(sdk_key, config) } + let(:synchronizer) { 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 87d256fe..04b20399 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) { StreamingDataSource.new(sdk_key, config) } + let(:synchronizer) { 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) { StreamingDataSource.new(sdk_key, config) } + let(:synchronizer) { StreamingDataSourceBuilder.new.build(sdk_key, config) } it "logs successful connection when diagnostic_accumulator is provided" do diagnostic_accumulator = double("DiagnosticAccumulator") diff --git a/spec/impl/event_sender_spec.rb b/spec/impl/event_sender_spec.rb index 517d7985..fce600b2 100644 --- a/spec/impl/event_sender_spec.rb +++ b/spec/impl/event_sender_spec.rb @@ -15,7 +15,7 @@ module Impl def make_sender(config_options = {}) config_options = {logger: $null_log}.merge(config_options) - subject.new(sdk_key, Config.new(config_options), nil, 0.1) + subject.new(sdk_key, Config.new(config_options), 0.1) end def with_sender_and_server(config_options = {}) @@ -85,7 +85,7 @@ def with_sender_and_server(config_options = {}) config = Config.new(events_uri: "http://fake-event-server/bulk", socket_factory: SocketFactoryFromHash.new({"fake-event-server" => server.port}), logger: $null_log) - es = subject.new(sdk_key, config, nil, 0.1) + es = subject.new(sdk_key, config, 0.1) result = es.send_event_data(fake_data, "", false) From dc598f870b328ac924d4d7fe43408924cf6379ad Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 29 Jan 2026 16:25:39 +0000 Subject: [PATCH 4/6] address feedback and attempt to fix contract tests --- contract-tests/client_entity.rb | 101 ++++++++++++------ .../data_system/data_source_builder_common.rb | 4 +- 2 files changed, 68 insertions(+), 37 deletions(-) diff --git a/contract-tests/client_entity.rb b/contract-tests/client_entity.rb index 1381e9ad..11c8e121 100644 --- a/contract-tests/client_entity.rb +++ b/contract-tests/client_entity.rb @@ -32,9 +32,7 @@ def initialize(log, config) polling = init_config[:polling] next unless polling - opts[:base_uri] = polling[:baseUri] if polling[:baseUri] - set_optional_time_prop(polling, :pollIntervalMs, opts, :poll_interval) - initializers << LaunchDarkly::DataSystem.polling_ds_builder + initializers << build_polling_ds_builder(polling) end data_system.initializers(initializers) end @@ -44,41 +42,13 @@ def initialize(log, config) primary = sync_config[:primary] secondary = sync_config[:secondary] - primary_builder = nil - secondary_builder = nil - - if primary - streaming = primary[:streaming] - if streaming - opts[:stream_uri] = streaming[:baseUri] if streaming[:baseUri] - set_optional_time_prop(streaming, :initialRetryDelayMs, opts, :initial_reconnect_delay) - primary_builder = LaunchDarkly::DataSystem.streaming_ds_builder - elsif primary[:polling] - polling = primary[:polling] - opts[:base_uri] = polling[:baseUri] if polling[:baseUri] - set_optional_time_prop(polling, :pollIntervalMs, opts, :poll_interval) - primary_builder = LaunchDarkly::DataSystem.polling_ds_builder - end - end - - if secondary - streaming = secondary[:streaming] - if streaming - opts[:stream_uri] = streaming[:baseUri] if streaming[:baseUri] - set_optional_time_prop(streaming, :initialRetryDelayMs, opts, :initial_reconnect_delay) - secondary_builder = LaunchDarkly::DataSystem.streaming_ds_builder - elsif secondary[:polling] - polling = secondary[:polling] - opts[:base_uri] = polling[:baseUri] if polling[:baseUri] - set_optional_time_prop(polling, :pollIntervalMs, opts, :poll_interval) - secondary_builder = LaunchDarkly::DataSystem.polling_ds_builder - end - end + primary_builder = build_synchronizer_builder(primary) + secondary_builder = build_synchronizer_builder(secondary) data_system.synchronizers(primary_builder, secondary_builder) if primary_builder if primary_builder || secondary_builder - fallback_builder = LaunchDarkly::DataSystem.fdv1_fallback_ds_builder + fallback_builder = build_fdv1_fallback_builder(primary, secondary) data_system.fdv1_compatible_synchronizer(fallback_builder) end end @@ -313,6 +283,69 @@ def close LaunchDarkly::LDContext.create(context) end + # + # Builds a synchronizer builder from the contract test configuration. + # + # @param sync_config [Hash, nil] The synchronizer configuration (primary or secondary) + # @return [Object, nil] Returns the configured builder or nil + # + private def build_synchronizer_builder(sync_config) + return nil unless sync_config + + streaming = sync_config[:streaming] + if streaming + build_streaming_ds_builder(streaming) + elsif sync_config[:polling] + build_polling_ds_builder(sync_config[:polling]) + end + end + + # + # Builds a streaming data source builder with the configured parameters. + # + # @param streaming_config [Hash] The streaming configuration + # @return [Object] Returns the configured streaming builder + # + private def build_streaming_ds_builder(streaming_config) + builder = LaunchDarkly::DataSystem.streaming_ds_builder + builder.base_uri(streaming_config[:baseUri]) if streaming_config[:baseUri] + builder.initial_reconnect_delay(streaming_config[:initialRetryDelayMs] / 1_000.0) if streaming_config[:initialRetryDelayMs] + builder + end + + # + # Builds a polling data source builder with the configured parameters. + # + # @param polling_config [Hash] The polling configuration + # @return [Object] Returns the configured polling builder + # + private def build_polling_ds_builder(polling_config) + builder = LaunchDarkly::DataSystem.polling_ds_builder + builder.base_uri(polling_config[:baseUri]) if polling_config[:baseUri] + builder.poll_interval(polling_config[:pollIntervalMs] / 1_000.0) if polling_config[:pollIntervalMs] + builder + end + + # + # Builds an FDv1 fallback polling data source builder using the first available config. + # + # @param primary [Hash, nil] The primary synchronizer configuration + # @param secondary [Hash, nil] The secondary synchronizer configuration + # @return [Object] Returns the configured FDv1 fallback builder + # + private def build_fdv1_fallback_builder(primary, secondary) + builder = LaunchDarkly::DataSystem.fdv1_fallback_ds_builder + + # Use the first available polling config for the fallback base_uri + polling_config = primary&.dig(:polling) || secondary&.dig(:polling) + if polling_config + builder.base_uri(polling_config[:baseUri]) if polling_config[:baseUri] + builder.poll_interval(polling_config[:pollIntervalMs] / 1_000.0) if polling_config[:pollIntervalMs] + end + + builder + end + # # Builds a persistent data store from the contract test configuration. # 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 index bf180bc5..34190655 100644 --- a/lib/ldclient-rb/impl/data_system/data_source_builder_common.rb +++ b/lib/ldclient-rb/impl/data_system/data_source_builder_common.rb @@ -56,8 +56,6 @@ def connect_timeout(timeout) self end - private - # # Builds an HttpConfigOptions instance from the current builder settings. # Uses self.class::DEFAULT_BASE_URI if base_uri was not explicitly set. @@ -65,7 +63,7 @@ def connect_timeout(timeout) # # @return [HttpConfigOptions] # - def build_http_config + private def build_http_config HttpConfigOptions.new( base_uri: @base_uri || self.class::DEFAULT_BASE_URI, socket_factory: @socket_factory, From c8e162b4eb5e97fec92c2d306b296947b7d84d01 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 29 Jan 2026 16:46:57 +0000 Subject: [PATCH 5/6] empty to trigger clean ci run From c33b4d8f3e6366f69a974cc3bacf9c2e0404533b Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 29 Jan 2026 16:54:30 +0000 Subject: [PATCH 6/6] fix failing test --- lib/ldclient-rb/impl/data_system/data_source_builder_common.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 34190655..c2eede08 100644 --- a/lib/ldclient-rb/impl/data_system/data_source_builder_common.rb +++ b/lib/ldclient-rb/impl/data_system/data_source_builder_common.rb @@ -65,7 +65,7 @@ def connect_timeout(timeout) # private def build_http_config HttpConfigOptions.new( - base_uri: @base_uri || self.class::DEFAULT_BASE_URI, + base_uri: (@base_uri || self.class::DEFAULT_BASE_URI).chomp("/"), socket_factory: @socket_factory, read_timeout: @read_timeout, connect_timeout: @connect_timeout