From 6287dc1505c87f2318b6555fae31245a5eb59bea Mon Sep 17 00:00:00 2001 From: Bee Klimt Date: Tue, 30 Jun 2026 17:28:34 -0700 Subject: [PATCH 1/2] feat: Add C bindings for FDv2 data system --- .../server_side/bindings/c/config/builder.h | 22 ++ .../c/config/fdv2_builder/fdv2_builder.h | 285 ++++++++++++++++++ libs/server-sdk/src/CMakeLists.txt | 1 + libs/server-sdk/src/bindings/c/builder.cpp | 11 + .../src/bindings/c/fdv2_builder.cpp | 186 ++++++++++++ .../tests/server_c_bindings_test.cpp | 114 ++++++- 6 files changed, 618 insertions(+), 1 deletion(-) create mode 100644 libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/fdv2_builder/fdv2_builder.h create mode 100644 libs/server-sdk/src/bindings/c/fdv2_builder.cpp diff --git a/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/builder.h b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/builder.h index 61e14c0de..ee562ff61 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/builder.h +++ b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/builder.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include @@ -256,6 +257,27 @@ LDServerConfigBuilder_DataSystem_LazyLoad( LDServerConfigBuilder b, LDServerLazyLoadBuilder lazy_load_builder); +/** + * Configures the FDv2 data system. The builder is automatically consumed. + * + * This method is mutually exclusive with the BackgroundSync_Streaming, + * BackgroundSync_Polling, and LazyLoad builders. + * + * WARNING: Do not call any other LDServerFDv2Builder function on the provided + * builder after calling this function. It is undefined behavior. + * + * FDv2 receives flag delivery updates over the changeset-based protocol with + * built-in fallback and recovery semantics. An optional FDv1 fallback may be + * configured on the FDv2 builder for service-directed protocol fallback. + * + * @param b Server config builder. Must not be NULL. + * @param fdv2_builder The FDv2 builder. The builder is consumed; do not free + * it. Must not be NULL. + */ +LD_EXPORT(void) +LDServerConfigBuilder_DataSystem_FDv2(LDServerConfigBuilder b, + LDServerFDv2Builder fdv2_builder); + /** * Specify if the SDK's data system should be enabled or not. * diff --git a/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/fdv2_builder/fdv2_builder.h b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/fdv2_builder/fdv2_builder.h new file mode 100644 index 000000000..46359792b --- /dev/null +++ b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/fdv2_builder/fdv2_builder.h @@ -0,0 +1,285 @@ +/** @file fdv2_builder.h */ +// NOLINTBEGIN modernize-use-using + +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { // only need to export C interface if used by C++ source code +#endif + +typedef struct _LDServerFDv2Builder* LDServerFDv2Builder; +typedef struct _LDServerFDv2StreamingBuilder* LDServerFDv2StreamingBuilder; +typedef struct _LDServerFDv2PollingBuilder* LDServerFDv2PollingBuilder; + +/* Reused as the input handles for LDServerFDv2Builder_FDv1Fallback_*. Fully + * declared in . */ +typedef struct _LDServerDataSourceStreamBuilder* LDServerDataSourceStreamBuilder; +typedef struct _LDServerDataSourcePollBuilder* LDServerDataSourcePollBuilder; + +/** + * Creates an FDv2 builder pre-populated with the spec-recommended initializers, + * synchronizers, and FDv1 fallback. Suitable for most applications without + * further configuration. + * + * If not passed into the config builder, must be manually freed with + * LDServerFDv2Builder_Free. + * + * @return A new FDv2 builder with default sources. + */ +LD_EXPORT(LDServerFDv2Builder) +LDServerFDv2Builder_Default(void); + +/** + * Creates an FDv2 builder with no initializers, no synchronizers, and no + * FDv1 fallback. Sources must be added explicitly via Initializer_*, + * Synchronizer_*, and FDv1Fallback_* methods. + * + * If not passed into the config builder, must be manually freed with + * LDServerFDv2Builder_Free. + * + * @return A new empty FDv2 builder. + */ +LD_EXPORT(LDServerFDv2Builder) +LDServerFDv2Builder_Custom(void); + +/** + * Frees an FDv2 builder. Do not call if the builder was consumed by the + * config builder. + * + * @param b Builder to free. + */ +LD_EXPORT(void) +LDServerFDv2Builder_Free(LDServerFDv2Builder b); + +/** + * Creates a new FDv2 Streaming source builder. + * + * If not passed into an FDv2 builder, must be manually freed with + * LDServerFDv2StreamingBuilder_Free. + * + * @return A new FDv2 Streaming source builder. + */ +LD_EXPORT(LDServerFDv2StreamingBuilder) +LDServerFDv2StreamingBuilder_New(void); + +/** + * Sets the initial reconnect delay for the FDv2 streaming connection. + * + * The streaming service uses a backoff algorithm (with jitter) every time + * the connection needs to be reestablished. The delay for the first + * reconnection will start near this value, and then increase exponentially + * for any subsequent connection failures. + * + * @param b FDv2 Streaming source builder. Must not be NULL. + * @param milliseconds Initial delay for a reconnection attempt. + */ +LD_EXPORT(void) +LDServerFDv2StreamingBuilder_InitialReconnectDelayMs( + LDServerFDv2StreamingBuilder b, + unsigned int milliseconds); + +/** + * Overrides the base URL used by this FDv2 streaming source. By default the + * streaming source reads its endpoint from the top-level ServiceEndpoints + * configuration; this override takes precedence for this source only. + * + * @param b FDv2 Streaming source builder. Must not be NULL. + * @param base_url Target URL. Must not be NULL. + */ +LD_EXPORT(void) +LDServerFDv2StreamingBuilder_BaseUrl(LDServerFDv2StreamingBuilder b, + char const* base_url); + +/** + * Frees an FDv2 Streaming source builder. Do not call if the builder was + * consumed by an FDv2 builder. + * + * @param b Builder to free. + */ +LD_EXPORT(void) +LDServerFDv2StreamingBuilder_Free(LDServerFDv2StreamingBuilder b); + +/** + * Creates a new FDv2 Polling source builder. + * + * If not passed into an FDv2 builder, must be manually freed with + * LDServerFDv2PollingBuilder_Free. + * + * @return A new FDv2 Polling source builder. + */ +LD_EXPORT(LDServerFDv2PollingBuilder) +LDServerFDv2PollingBuilder_New(void); + +/** + * Sets the interval at which this FDv2 polling source will poll for flag + * updates. + * + * @param b FDv2 Polling source builder. Must not be NULL. + * @param seconds Polling interval in seconds. + */ +LD_EXPORT(void) +LDServerFDv2PollingBuilder_PollIntervalS(LDServerFDv2PollingBuilder b, + unsigned int seconds); + +/** + * Overrides the base URL used by this FDv2 polling source. By default the + * polling source reads its endpoint from the top-level ServiceEndpoints + * configuration; this override takes precedence for this source only. + * + * @param b FDv2 Polling source builder. Must not be NULL. + * @param base_url Target URL. Must not be NULL. + */ +LD_EXPORT(void) +LDServerFDv2PollingBuilder_BaseUrl(LDServerFDv2PollingBuilder b, + char const* base_url); + +/** + * Frees an FDv2 Polling source builder. Do not call if the builder was + * consumed by an FDv2 builder. + * + * @param b Builder to free. + */ +LD_EXPORT(void) +LDServerFDv2PollingBuilder_Free(LDServerFDv2PollingBuilder b); + +/** + * Appends an FDv2 polling initializer to the FDv2 builder's initializers + * list. The source builder is automatically freed. + * + * WARNING: Do not call any other LDServerFDv2PollingBuilder function on the + * provided builder after calling this function. It is undefined behavior. + * + * @param b FDv2 builder. Must not be NULL. + * @param polling The FDv2 Polling source builder. The builder is consumed; + * do not free it. Must not be NULL. + */ +LD_EXPORT(void) +LDServerFDv2Builder_Initializer_Polling(LDServerFDv2Builder b, + LDServerFDv2PollingBuilder polling); + +/** + * Appends an FDv2 streaming synchronizer to the FDv2 builder's synchronizers + * list. The source builder is automatically freed. + * + * Order in the list determines preference: the first entry is the primary + * synchronizer, subsequent entries are fallbacks. + * + * WARNING: Do not call any other LDServerFDv2StreamingBuilder function on + * the provided builder after calling this function. It is undefined behavior. + * + * @param b FDv2 builder. Must not be NULL. + * @param streaming The FDv2 Streaming source builder. The builder is + * consumed; do not free it. Must not be NULL. + */ +LD_EXPORT(void) +LDServerFDv2Builder_Synchronizer_Streaming( + LDServerFDv2Builder b, + LDServerFDv2StreamingBuilder streaming); + +/** + * Appends an FDv2 polling synchronizer to the FDv2 builder's synchronizers + * list. The source builder is automatically freed. + * + * See LDServerFDv2Builder_Synchronizer_Streaming for ordering semantics. + * + * WARNING: Do not call any other LDServerFDv2PollingBuilder function on the + * provided builder after calling this function. It is undefined behavior. + * + * @param b FDv2 builder. Must not be NULL. + * @param polling The FDv2 Polling source builder. The builder is consumed; + * do not free it. Must not be NULL. + */ +LD_EXPORT(void) +LDServerFDv2Builder_Synchronizer_Polling(LDServerFDv2Builder b, + LDServerFDv2PollingBuilder polling); + +/** + * Configures the FDv1 streaming source used as a last-resort fallback when + * the LaunchDarkly service signals (via the X-LD-FD-Fallback header) that + * the SDK should switch to FDv1. The fallback reads its endpoint from the + * top-level ServiceEndpoints configuration. The source builder is + * automatically freed. + * + * The fdv1_streaming parameter uses the same handle type as the + * BackgroundSync streaming synchronizer + * (LDServerDataSourceStreamBuilder_New); the underlying C++ type is shared. + * + * WARNING: Do not call any other LDServerDataSourceStreamBuilder function + * on the provided builder after calling this function. It is undefined + * behavior. + * + * @param b FDv2 builder. Must not be NULL. + * @param fdv1_streaming The FDv1 Streaming source builder. The builder is + * consumed; do not free it. Must not be NULL. + */ +LD_EXPORT(void) +LDServerFDv2Builder_FDv1Fallback_Streaming( + LDServerFDv2Builder b, + LDServerDataSourceStreamBuilder fdv1_streaming); + +/** + * Configures the FDv1 polling source used as a last-resort fallback when + * the LaunchDarkly service signals (via the X-LD-FD-Fallback header) that + * the SDK should switch to FDv1. The fallback reads its endpoint from the + * top-level ServiceEndpoints configuration. The source builder is + * automatically freed. + * + * The fdv1_polling parameter uses the same handle type as the BackgroundSync + * polling synchronizer (LDServerDataSourcePollBuilder_New); the underlying + * C++ type is shared. + * + * WARNING: Do not call any other LDServerDataSourcePollBuilder function on + * the provided builder after calling this function. It is undefined behavior. + * + * @param b FDv2 builder. Must not be NULL. + * @param fdv1_polling The FDv1 Polling source builder. The builder is + * consumed; do not free it. Must not be NULL. + */ +LD_EXPORT(void) +LDServerFDv2Builder_FDv1Fallback_Polling( + LDServerFDv2Builder b, + LDServerDataSourcePollBuilder fdv1_polling); + +/** + * Disables the FDv1 fallback. After this call, an FDv1 fallback directive + * from the service leaves the data source in the interrupted state and + * schedules an FDv2 retry on the directive's TTL. + * + * @param b FDv2 builder. Must not be NULL. + */ +LD_EXPORT(void) +LDServerFDv2Builder_DisableFDv1Fallback(LDServerFDv2Builder b); + +/** + * Sets how long the active synchronizer may remain interrupted before the + * orchestrator falls back to the next-preferred synchronizer. + * + * @param b FDv2 builder. Must not be NULL. + * @param milliseconds Duration the synchronizer must be continuously + * interrupted for before fallback fires. + */ +LD_EXPORT(void) +LDServerFDv2Builder_FallbackTimeoutMs(LDServerFDv2Builder b, + unsigned int milliseconds); + +/** + * Sets how long a fallback synchronizer must run successfully before the + * orchestrator attempts to recover to the primary synchronizer. + * + * @param b FDv2 builder. Must not be NULL. + * @param milliseconds Duration the fallback synchronizer must run before a + * recovery attempt is made. + */ +LD_EXPORT(void) +LDServerFDv2Builder_RecoveryTimeoutMs(LDServerFDv2Builder b, + unsigned int milliseconds); + +#ifdef __cplusplus +} +#endif + +// NOLINTEND modernize-use-using diff --git a/libs/server-sdk/src/CMakeLists.txt b/libs/server-sdk/src/CMakeLists.txt index 9034d2aee..8275e4a93 100644 --- a/libs/server-sdk/src/CMakeLists.txt +++ b/libs/server-sdk/src/CMakeLists.txt @@ -111,6 +111,7 @@ target_sources(${LIBNAME} hooks/hook_executor.cpp bindings/c/sdk.cpp bindings/c/builder.cpp + bindings/c/fdv2_builder.cpp bindings/c/config.cpp bindings/c/all_flags_state/all_flags_state.cpp bindings/c/hook.cpp diff --git a/libs/server-sdk/src/bindings/c/builder.cpp b/libs/server-sdk/src/bindings/c/builder.cpp index 9dea0e114..e80cfc6bd 100644 --- a/libs/server-sdk/src/bindings/c/builder.cpp +++ b/libs/server-sdk/src/bindings/c/builder.cpp @@ -225,6 +225,17 @@ LDServerConfigBuilder_DataSystem_LazyLoad( LDServerLazyLoadBuilder_Free(lazy_load_builder); } +LD_EXPORT(void) +LDServerConfigBuilder_DataSystem_FDv2(LDServerConfigBuilder b, + LDServerFDv2Builder fdv2_builder) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(fdv2_builder); + + auto* fb = reinterpret_cast(fdv2_builder); + TO_BUILDER(b)->DataSystem().Method(*fb); + LDServerFDv2Builder_Free(fdv2_builder); +} + LD_EXPORT(void) LDServerConfigBuilder_DataSystem_Enabled(LDServerConfigBuilder b, bool const enabled) { diff --git a/libs/server-sdk/src/bindings/c/fdv2_builder.cpp b/libs/server-sdk/src/bindings/c/fdv2_builder.cpp new file mode 100644 index 000000000..6a52b7033 --- /dev/null +++ b/libs/server-sdk/src/bindings/c/fdv2_builder.cpp @@ -0,0 +1,186 @@ +// NOLINTBEGIN cppcoreguidelines-pro-type-reinterpret-cast +// NOLINTBEGIN OCInconsistentNamingInspection + +#include +#include + +#include +#include +#include + +#include + +using namespace launchdarkly::server_side::config::builders; + +#define TO_FDV2_BUILDER(ptr) (reinterpret_cast(ptr)) +#define FROM_FDV2_BUILDER(ptr) (reinterpret_cast(ptr)) + +#define TO_FDV2_STREAM_BUILDER(ptr) \ + (reinterpret_cast(ptr)) +#define FROM_FDV2_STREAM_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +#define TO_FDV2_POLL_BUILDER(ptr) (reinterpret_cast(ptr)) +#define FROM_FDV2_POLL_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +/* LDServerDataSourceStreamBuilder / LDServerDataSourcePollBuilder wrap + * BackgroundSyncBuilder::Streaming / Polling, which are aliases for the same + * StreamingBuilder / PollingBuilder that FDv2Builder's + * FDv1Fallback overloads accept. */ +#define TO_FDV1_STREAM_BUILDER(ptr) \ + (reinterpret_cast(ptr)) +#define TO_FDV1_POLL_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +LD_EXPORT(LDServerFDv2Builder) +LDServerFDv2Builder_Default(void) { + return FROM_FDV2_BUILDER(new FDv2Builder(FDv2Builder::Default())); +} + +LD_EXPORT(LDServerFDv2Builder) +LDServerFDv2Builder_Custom(void) { + return FROM_FDV2_BUILDER(new FDv2Builder(FDv2Builder::Custom())); +} + +LD_EXPORT(void) +LDServerFDv2Builder_Free(LDServerFDv2Builder b) { + delete TO_FDV2_BUILDER(b); +} + +LD_EXPORT(LDServerFDv2StreamingBuilder) +LDServerFDv2StreamingBuilder_New(void) { + return FROM_FDV2_STREAM_BUILDER(new FDv2Builder::Streaming()); +} + +LD_EXPORT(void) +LDServerFDv2StreamingBuilder_InitialReconnectDelayMs( + LDServerFDv2StreamingBuilder b, + unsigned int milliseconds) { + LD_ASSERT_NOT_NULL(b); + + TO_FDV2_STREAM_BUILDER(b)->InitialReconnectDelay( + std::chrono::milliseconds{milliseconds}); +} + +LD_EXPORT(void) +LDServerFDv2StreamingBuilder_BaseUrl(LDServerFDv2StreamingBuilder b, + char const* base_url) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(base_url); + + TO_FDV2_STREAM_BUILDER(b)->BaseUrl(base_url); +} + +LD_EXPORT(void) +LDServerFDv2StreamingBuilder_Free(LDServerFDv2StreamingBuilder b) { + delete TO_FDV2_STREAM_BUILDER(b); +} + +LD_EXPORT(LDServerFDv2PollingBuilder) +LDServerFDv2PollingBuilder_New(void) { + return FROM_FDV2_POLL_BUILDER(new FDv2Builder::Polling()); +} + +LD_EXPORT(void) +LDServerFDv2PollingBuilder_PollIntervalS(LDServerFDv2PollingBuilder b, + unsigned int seconds) { + LD_ASSERT_NOT_NULL(b); + + TO_FDV2_POLL_BUILDER(b)->PollInterval(std::chrono::seconds{seconds}); +} + +LD_EXPORT(void) +LDServerFDv2PollingBuilder_BaseUrl(LDServerFDv2PollingBuilder b, + char const* base_url) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(base_url); + + TO_FDV2_POLL_BUILDER(b)->BaseUrl(base_url); +} + +LD_EXPORT(void) +LDServerFDv2PollingBuilder_Free(LDServerFDv2PollingBuilder b) { + delete TO_FDV2_POLL_BUILDER(b); +} + +LD_EXPORT(void) +LDServerFDv2Builder_Initializer_Polling(LDServerFDv2Builder b, + LDServerFDv2PollingBuilder polling) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(polling); + + TO_FDV2_BUILDER(b)->Initializer(*TO_FDV2_POLL_BUILDER(polling)); + LDServerFDv2PollingBuilder_Free(polling); +} + +LD_EXPORT(void) +LDServerFDv2Builder_Synchronizer_Streaming( + LDServerFDv2Builder b, + LDServerFDv2StreamingBuilder streaming) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(streaming); + + TO_FDV2_BUILDER(b)->Synchronizer(*TO_FDV2_STREAM_BUILDER(streaming)); + LDServerFDv2StreamingBuilder_Free(streaming); +} + +LD_EXPORT(void) +LDServerFDv2Builder_Synchronizer_Polling(LDServerFDv2Builder b, + LDServerFDv2PollingBuilder polling) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(polling); + + TO_FDV2_BUILDER(b)->Synchronizer(*TO_FDV2_POLL_BUILDER(polling)); + LDServerFDv2PollingBuilder_Free(polling); +} + +LD_EXPORT(void) +LDServerFDv2Builder_FDv1Fallback_Streaming( + LDServerFDv2Builder b, + LDServerDataSourceStreamBuilder fdv1_streaming) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(fdv1_streaming); + + TO_FDV2_BUILDER(b)->FDv1Fallback(*TO_FDV1_STREAM_BUILDER(fdv1_streaming)); + LDServerDataSourceStreamBuilder_Free(fdv1_streaming); +} + +LD_EXPORT(void) +LDServerFDv2Builder_FDv1Fallback_Polling( + LDServerFDv2Builder b, + LDServerDataSourcePollBuilder fdv1_polling) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(fdv1_polling); + + TO_FDV2_BUILDER(b)->FDv1Fallback(*TO_FDV1_POLL_BUILDER(fdv1_polling)); + LDServerDataSourcePollBuilder_Free(fdv1_polling); +} + +LD_EXPORT(void) +LDServerFDv2Builder_DisableFDv1Fallback(LDServerFDv2Builder b) { + LD_ASSERT_NOT_NULL(b); + + TO_FDV2_BUILDER(b)->DisableFDv1Fallback(); +} + +LD_EXPORT(void) +LDServerFDv2Builder_FallbackTimeoutMs(LDServerFDv2Builder b, + unsigned int milliseconds) { + LD_ASSERT_NOT_NULL(b); + + TO_FDV2_BUILDER(b)->FallbackTimeout( + std::chrono::milliseconds{milliseconds}); +} + +LD_EXPORT(void) +LDServerFDv2Builder_RecoveryTimeoutMs(LDServerFDv2Builder b, + unsigned int milliseconds) { + LD_ASSERT_NOT_NULL(b); + + TO_FDV2_BUILDER(b)->RecoveryTimeout( + std::chrono::milliseconds{milliseconds}); +} + +// NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast +// NOLINTEND OCInconsistentNamingInspection diff --git a/libs/server-sdk/tests/server_c_bindings_test.cpp b/libs/server-sdk/tests/server_c_bindings_test.cpp index 36b0a2d6b..a4fe54662 100644 --- a/libs/server-sdk/tests/server_c_bindings_test.cpp +++ b/libs/server-sdk/tests/server_c_bindings_test.cpp @@ -46,7 +46,7 @@ TEST(ClientBindings, RegisterDataSourceStatusChangeListener) { LDServerSDK sdk = LDServerSDK_New(config); - struct LDServerDataSourceStatusListener listener {}; + struct LDServerDataSourceStatusListener listener{}; LDServerDataSourceStatusListener_Init(&listener); listener.UserData = const_cast("Potato"); @@ -337,3 +337,115 @@ TEST(ClientBindings, PollingPayloadFilters) { LDServerConfig_Free(config); } + +TEST(ClientBindings, FDv2DefaultBuilds) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + + LDServerFDv2Builder fdv2 = LDServerFDv2Builder_Default(); + LDServerConfigBuilder_DataSystem_FDv2(cfg_builder, fdv2); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerConfig_Free(config); +} + +TEST(ClientBindings, FDv2CustomWithStreamingSynchronizer) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + + LDServerFDv2StreamingBuilder streaming = LDServerFDv2StreamingBuilder_New(); + LDServerFDv2StreamingBuilder_InitialReconnectDelayMs(streaming, 500); + LDServerFDv2StreamingBuilder_BaseUrl(streaming, + "https://stream.example.com"); + + LDServerFDv2Builder fdv2 = LDServerFDv2Builder_Custom(); + LDServerFDv2Builder_Synchronizer_Streaming(fdv2, streaming); + + LDServerConfigBuilder_DataSystem_FDv2(cfg_builder, fdv2); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerConfig_Free(config); +} + +TEST(ClientBindings, FDv2CustomWithPollingInitializerAndSynchronizer) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + + LDServerFDv2PollingBuilder initializer = LDServerFDv2PollingBuilder_New(); + LDServerFDv2PollingBuilder_PollIntervalS(initializer, 30); + + LDServerFDv2PollingBuilder synchronizer = LDServerFDv2PollingBuilder_New(); + LDServerFDv2PollingBuilder_BaseUrl(synchronizer, + "https://poll.example.com"); + + LDServerFDv2Builder fdv2 = LDServerFDv2Builder_Custom(); + LDServerFDv2Builder_Initializer_Polling(fdv2, initializer); + LDServerFDv2Builder_Synchronizer_Polling(fdv2, synchronizer); + + LDServerConfigBuilder_DataSystem_FDv2(cfg_builder, fdv2); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerConfig_Free(config); +} + +TEST(ClientBindings, FDv2FDv1FallbackStreamingReusesBackgroundSyncHandle) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + + LDServerDataSourceStreamBuilder fdv1_streaming = + LDServerDataSourceStreamBuilder_New(); + LDServerDataSourceStreamBuilder_InitialReconnectDelayMs(fdv1_streaming, + 1000); + + LDServerFDv2Builder fdv2 = LDServerFDv2Builder_Custom(); + LDServerFDv2Builder_FDv1Fallback_Streaming(fdv2, fdv1_streaming); + + LDServerConfigBuilder_DataSystem_FDv2(cfg_builder, fdv2); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerConfig_Free(config); +} + +TEST(ClientBindings, FDv2FDv1FallbackPollingReusesBackgroundSyncHandle) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + + LDServerDataSourcePollBuilder fdv1_polling = + LDServerDataSourcePollBuilder_New(); + LDServerDataSourcePollBuilder_IntervalS(fdv1_polling, 30); + + LDServerFDv2Builder fdv2 = LDServerFDv2Builder_Custom(); + LDServerFDv2Builder_FDv1Fallback_Polling(fdv2, fdv1_polling); + + LDServerConfigBuilder_DataSystem_FDv2(cfg_builder, fdv2); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerConfig_Free(config); +} + +TEST(ClientBindings, FDv2DisableFDv1FallbackAndTimeouts) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + + LDServerFDv2Builder fdv2 = LDServerFDv2Builder_Default(); + LDServerFDv2Builder_DisableFDv1Fallback(fdv2); + LDServerFDv2Builder_FallbackTimeoutMs(fdv2, 60000); + LDServerFDv2Builder_RecoveryTimeoutMs(fdv2, 300000); + + LDServerConfigBuilder_DataSystem_FDv2(cfg_builder, fdv2); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerConfig_Free(config); +} From 092caf83c45c9207f3189b7abdb6ea77adbafddf Mon Sep 17 00:00:00 2001 From: Bee Klimt Date: Tue, 30 Jun 2026 18:50:50 -0700 Subject: [PATCH 2/2] refactor: Match repo naming conventions on FDv2 sub-builders --- .../bindings/c/config/fdv2_builder/fdv2_builder.h | 6 +++--- libs/server-sdk/src/bindings/c/fdv2_builder.cpp | 8 ++++---- libs/server-sdk/tests/server_c_bindings_test.cpp | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/fdv2_builder/fdv2_builder.h b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/fdv2_builder/fdv2_builder.h index 46359792b..705d21f45 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/fdv2_builder/fdv2_builder.h +++ b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/fdv2_builder/fdv2_builder.h @@ -91,7 +91,7 @@ LDServerFDv2StreamingBuilder_InitialReconnectDelayMs( * @param base_url Target URL. Must not be NULL. */ LD_EXPORT(void) -LDServerFDv2StreamingBuilder_BaseUrl(LDServerFDv2StreamingBuilder b, +LDServerFDv2StreamingBuilder_BaseURL(LDServerFDv2StreamingBuilder b, char const* base_url); /** @@ -122,7 +122,7 @@ LDServerFDv2PollingBuilder_New(void); * @param seconds Polling interval in seconds. */ LD_EXPORT(void) -LDServerFDv2PollingBuilder_PollIntervalS(LDServerFDv2PollingBuilder b, +LDServerFDv2PollingBuilder_IntervalS(LDServerFDv2PollingBuilder b, unsigned int seconds); /** @@ -134,7 +134,7 @@ LDServerFDv2PollingBuilder_PollIntervalS(LDServerFDv2PollingBuilder b, * @param base_url Target URL. Must not be NULL. */ LD_EXPORT(void) -LDServerFDv2PollingBuilder_BaseUrl(LDServerFDv2PollingBuilder b, +LDServerFDv2PollingBuilder_BaseURL(LDServerFDv2PollingBuilder b, char const* base_url); /** diff --git a/libs/server-sdk/src/bindings/c/fdv2_builder.cpp b/libs/server-sdk/src/bindings/c/fdv2_builder.cpp index 6a52b7033..ac14f3014 100644 --- a/libs/server-sdk/src/bindings/c/fdv2_builder.cpp +++ b/libs/server-sdk/src/bindings/c/fdv2_builder.cpp @@ -64,7 +64,7 @@ LDServerFDv2StreamingBuilder_InitialReconnectDelayMs( } LD_EXPORT(void) -LDServerFDv2StreamingBuilder_BaseUrl(LDServerFDv2StreamingBuilder b, +LDServerFDv2StreamingBuilder_BaseURL(LDServerFDv2StreamingBuilder b, char const* base_url) { LD_ASSERT_NOT_NULL(b); LD_ASSERT_NOT_NULL(base_url); @@ -83,15 +83,15 @@ LDServerFDv2PollingBuilder_New(void) { } LD_EXPORT(void) -LDServerFDv2PollingBuilder_PollIntervalS(LDServerFDv2PollingBuilder b, - unsigned int seconds) { +LDServerFDv2PollingBuilder_IntervalS(LDServerFDv2PollingBuilder b, + unsigned int seconds) { LD_ASSERT_NOT_NULL(b); TO_FDV2_POLL_BUILDER(b)->PollInterval(std::chrono::seconds{seconds}); } LD_EXPORT(void) -LDServerFDv2PollingBuilder_BaseUrl(LDServerFDv2PollingBuilder b, +LDServerFDv2PollingBuilder_BaseURL(LDServerFDv2PollingBuilder b, char const* base_url) { LD_ASSERT_NOT_NULL(b); LD_ASSERT_NOT_NULL(base_url); diff --git a/libs/server-sdk/tests/server_c_bindings_test.cpp b/libs/server-sdk/tests/server_c_bindings_test.cpp index a4fe54662..18b54cfcb 100644 --- a/libs/server-sdk/tests/server_c_bindings_test.cpp +++ b/libs/server-sdk/tests/server_c_bindings_test.cpp @@ -356,7 +356,7 @@ TEST(ClientBindings, FDv2CustomWithStreamingSynchronizer) { LDServerFDv2StreamingBuilder streaming = LDServerFDv2StreamingBuilder_New(); LDServerFDv2StreamingBuilder_InitialReconnectDelayMs(streaming, 500); - LDServerFDv2StreamingBuilder_BaseUrl(streaming, + LDServerFDv2StreamingBuilder_BaseURL(streaming, "https://stream.example.com"); LDServerFDv2Builder fdv2 = LDServerFDv2Builder_Custom(); @@ -375,10 +375,10 @@ TEST(ClientBindings, FDv2CustomWithPollingInitializerAndSynchronizer) { LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); LDServerFDv2PollingBuilder initializer = LDServerFDv2PollingBuilder_New(); - LDServerFDv2PollingBuilder_PollIntervalS(initializer, 30); + LDServerFDv2PollingBuilder_IntervalS(initializer, 30); LDServerFDv2PollingBuilder synchronizer = LDServerFDv2PollingBuilder_New(); - LDServerFDv2PollingBuilder_BaseUrl(synchronizer, + LDServerFDv2PollingBuilder_BaseURL(synchronizer, "https://poll.example.com"); LDServerFDv2Builder fdv2 = LDServerFDv2Builder_Custom();