From 2f888ea859ff449bd706fa4dd6f27e434c511bef Mon Sep 17 00:00:00 2001 From: Bee Klimt Date: Tue, 30 Jun 2026 19:16:02 -0700 Subject: [PATCH] feat: Add C bindings for Big Segments --- .../big_segments_builder.h | 111 +++++++++++++ .../server_side/bindings/c/config/builder.h | 16 ++ .../launchdarkly/server_side/bindings/c/sdk.h | 104 ++++++++++++ libs/server-sdk/src/CMakeLists.txt | 1 + .../src/bindings/c/big_segments_builder.cpp | 67 ++++++++ libs/server-sdk/src/bindings/c/builder.cpp | 11 ++ libs/server-sdk/src/bindings/c/sdk.cpp | 149 ++++++++++++------ .../tests/server_c_bindings_test.cpp | 107 +++++++++++++ 8 files changed, 519 insertions(+), 47 deletions(-) create mode 100644 libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/big_segments_builder/big_segments_builder.h create mode 100644 libs/server-sdk/src/bindings/c/big_segments_builder.cpp diff --git a/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/big_segments_builder/big_segments_builder.h b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/big_segments_builder/big_segments_builder.h new file mode 100644 index 000000000..0a72a2c9f --- /dev/null +++ b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/big_segments_builder/big_segments_builder.h @@ -0,0 +1,111 @@ +/** @file big_segments_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 _LDServerBigSegmentsBuilder* LDServerBigSegmentsBuilder; + +/** + * Opaque handle to a Big Segment store implementation, produced by an + * integration library (for example, the server-side Redis or DynamoDB Big + * Segments source libraries). + * + * Behavior is undefined if the pointer does not originate from a + * LaunchDarkly-provided Big Segments integration. + */ +typedef struct _LDServerBigSegmentStorePtr* LDServerBigSegmentStorePtr; + +/** + * Creates a Big Segments builder wrapping the given store. + * + * The store pointer is consumed: the SDK takes ownership of the underlying + * store and will free it when the SDK is destroyed. Do not free the store + * pointer after calling this function. + * + * If not passed into the config builder, the returned Big Segments builder + * must be manually freed with LDServerBigSegmentsBuilder_Free. + * + * @param store Big Segment store handle. Must not be NULL. + * @return A new Big Segments builder. + */ +LD_EXPORT(LDServerBigSegmentsBuilder) +LDServerBigSegmentsBuilder_New(LDServerBigSegmentStorePtr store); + +/** + * Sets the maximum number of context membership lookups cached by the SDK. + * Defaults to 1000. + * + * A higher value reduces store queries for recently-referenced contexts at + * the cost of memory. + * + * @param b Big Segments builder. Must not be NULL. + * @param size Maximum cached lookups. + */ +LD_EXPORT(void) +LDServerBigSegmentsBuilder_ContextCacheSize(LDServerBigSegmentsBuilder b, + size_t size); + +/** + * Sets the time-to-live for cached membership lookups. Defaults to 5 seconds. + * + * A higher value reduces store queries for any given context, but delays + * the SDK noticing membership changes. Zero is coerced to the default. + * + * @param b Big Segments builder. Must not be NULL. + * @param milliseconds Cache time-to-live. + */ +LD_EXPORT(void) +LDServerBigSegmentsBuilder_ContextCacheTimeMs(LDServerBigSegmentsBuilder b, + unsigned int milliseconds); + +/** + * Sets the interval at which the SDK polls the store's metadata to determine + * availability and staleness. Defaults to 5 seconds. + * + * Zero is coerced to the default. + * + * @param b Big Segments builder. Must not be NULL. + * @param milliseconds Poll interval. + */ +LD_EXPORT(void) +LDServerBigSegmentsBuilder_StatusPollIntervalMs(LDServerBigSegmentsBuilder b, + unsigned int milliseconds); + +/** + * Sets how long the SDK waits before treating store data as stale. + * Defaults to 2 minutes. + * + * If the store's last-updated timestamp falls behind the current time by + * more than this duration, evaluations report a big segments status of + * STALE and the status provider reports the store as stale. Zero is coerced + * to the default. + * + * @param b Big Segments builder. Must not be NULL. + * @param milliseconds Staleness threshold. + */ +LD_EXPORT(void) +LDServerBigSegmentsBuilder_StaleAfterMs(LDServerBigSegmentsBuilder b, + unsigned int milliseconds); + +/** + * Frees a Big Segments builder. Do not call if the builder was consumed by + * the config builder. + * + * @param b Builder to free. + */ +LD_EXPORT(void) +LDServerBigSegmentsBuilder_Free(LDServerBigSegmentsBuilder b); + +#ifdef __cplusplus +} +#endif + +// NOLINTEND modernize-use-using 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 ee562ff61..105a299fb 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 @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -278,6 +279,21 @@ LD_EXPORT(void) LDServerConfigBuilder_DataSystem_FDv2(LDServerConfigBuilder b, LDServerFDv2Builder fdv2_builder); +/** + * Configures the SDK's Big Segments behavior. The builder is automatically + * consumed. + * + * WARNING: Do not call any other LDServerBigSegmentsBuilder function on the + * provided builder after calling this function. It is undefined behavior. + * + * @param b Server config builder. Must not be NULL. + * @param big_segments The Big Segments builder. The builder is consumed; do + * not free it. Must not be NULL. + */ +LD_EXPORT(void) +LDServerConfigBuilder_BigSegments(LDServerConfigBuilder b, + LDServerBigSegmentsBuilder big_segments); + /** * Specify if the SDK's data system should be enabled or not. * diff --git a/libs/server-sdk/include/launchdarkly/server_side/bindings/c/sdk.h b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/sdk.h index ace156981..03d2eb7de 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/bindings/c/sdk.h +++ b/libs/server-sdk/include/launchdarkly/server_side/bindings/c/sdk.h @@ -937,6 +937,110 @@ LDServerSDK_DataSourceStatus_Status(LDServerSDK sdk); LD_EXPORT(void) LDServerDataSourceStatus_Free(LDServerDataSourceStatus status); +typedef struct _LDServerBigSegmentStoreStatus* LDServerBigSegmentStoreStatus; + +/** + * True if the most recent Big Segment store query or metadata poll succeeded. + * If false, Big Segment membership cannot currently be evaluated reliably. + * + * @param status The Big Segment store status. Must not be NULL. + */ +LD_EXPORT(bool) +LDServerBigSegmentStoreStatus_Available(LDServerBigSegmentStoreStatus status); + +/** + * True if the Big Segment store's data has not been updated within the + * configured stale-after threshold. The data may still be queried; it is + * just older than desired. + * + * @param status The Big Segment store status. Must not be NULL. + */ +LD_EXPORT(bool) +LDServerBigSegmentStoreStatus_Stale(LDServerBigSegmentStoreStatus status); + +/** + * Frees the Big Segment store status. + * @param status The Big Segment store status to free. + */ +LD_EXPORT(void) +LDServerBigSegmentStoreStatus_Free(LDServerBigSegmentStoreStatus status); + +typedef void (*ServerBigSegmentStoreStatusCallbackFn)( + LDServerBigSegmentStoreStatus status, + void* user_data); + +/** + * Defines a Big Segment store status listener which may be used to listen + * for changes to the Big Segment store status. + * The struct should be initialized using + * LDServerBigSegmentStoreStatusListener_Init before use. + */ +struct LDServerBigSegmentStoreStatusListener { + /** + * Callback function which is invoked for Big Segment store status + * changes. + * + * The provided pointers are only valid for the duration of the function + * call (excluding UserData, whose lifetime is controlled by the caller). + * + * @param status The updated Big Segment store status. + */ + ServerBigSegmentStoreStatusCallbackFn StatusChanged; + + /** + * UserData is forwarded into callback functions. + */ + void* UserData; +}; + +/** + * Initializes a Big Segment store status change listener. Must be called + * before passing the listener to + * LDServerSDK_BigSegmentStoreStatus_OnStatusChange. + * + * If the StatusChanged member of the listener struct is not set (NULL), + * then the function will not register a listener. In that case the return + * value will be NULL. + * + * Create the struct, initialize the struct, set the StatusChanged handler + * and optionally UserData, and then pass the struct to + * LDServerSDK_BigSegmentStoreStatus_OnStatusChange. + * + * @param listener Listener to initialize. + */ +LD_EXPORT(void) +LDServerBigSegmentStoreStatusListener_Init( + struct LDServerBigSegmentStoreStatusListener* listener); + +/** + * Listen for changes to the Big Segment store status. + * + * @param sdk SDK. Must not be NULL. + * @param listener The listener, whose StatusChanged callback will be + * invoked when the Big Segment store status changes. + * + * @return A LDListenerConnection. The connection can be freed using + * LDListenerConnection_Free and the listener can be disconnected using + * LDListenerConnection_Disconnect. NULL is returned if the listener's + * StatusChanged member is NULL. + */ +LD_EXPORT(LDListenerConnection) +LDServerSDK_BigSegmentStoreStatus_OnStatusChange( + LDServerSDK sdk, + struct LDServerBigSegmentStoreStatusListener listener); + +/** + * The current status of the Big Segment store. + * + * If no store is configured, the returned status reports unavailable and + * not stale. + * + * The caller must free the returned value using + * LDServerBigSegmentStoreStatus_Free. + */ +LD_EXPORT(LDServerBigSegmentStoreStatus) +LDServerSDK_BigSegmentStoreStatus_Status(LDServerSDK sdk); + #ifdef __cplusplus } #endif diff --git a/libs/server-sdk/src/CMakeLists.txt b/libs/server-sdk/src/CMakeLists.txt index 8275e4a93..6be375da6 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/big_segments_builder.cpp bindings/c/fdv2_builder.cpp bindings/c/config.cpp bindings/c/all_flags_state/all_flags_state.cpp diff --git a/libs/server-sdk/src/bindings/c/big_segments_builder.cpp b/libs/server-sdk/src/bindings/c/big_segments_builder.cpp new file mode 100644 index 000000000..00028bd9c --- /dev/null +++ b/libs/server-sdk/src/bindings/c/big_segments_builder.cpp @@ -0,0 +1,67 @@ +// NOLINTBEGIN cppcoreguidelines-pro-type-reinterpret-cast +// NOLINTBEGIN OCInconsistentNamingInspection + +#include + +#include +#include +#include + +#include +#include + +using namespace launchdarkly::server_side; +using namespace launchdarkly::server_side::config::builders; + +#define TO_BS_BUILDER(ptr) (reinterpret_cast(ptr)) +#define FROM_BS_BUILDER(ptr) (reinterpret_cast(ptr)) + +LD_EXPORT(LDServerBigSegmentsBuilder) +LDServerBigSegmentsBuilder_New(LDServerBigSegmentStorePtr store) { + LD_ASSERT_NOT_NULL(store); + + auto* raw_store = reinterpret_cast(store); + auto owned = std::shared_ptr(raw_store); + return FROM_BS_BUILDER(new BigSegmentsBuilder(std::move(owned))); +} + +LD_EXPORT(void) +LDServerBigSegmentsBuilder_ContextCacheSize(LDServerBigSegmentsBuilder b, + size_t size) { + LD_ASSERT_NOT_NULL(b); + + TO_BS_BUILDER(b)->ContextCacheSize(size); +} + +LD_EXPORT(void) +LDServerBigSegmentsBuilder_ContextCacheTimeMs(LDServerBigSegmentsBuilder b, + unsigned int milliseconds) { + LD_ASSERT_NOT_NULL(b); + + TO_BS_BUILDER(b)->ContextCacheTime(std::chrono::milliseconds{milliseconds}); +} + +LD_EXPORT(void) +LDServerBigSegmentsBuilder_StatusPollIntervalMs(LDServerBigSegmentsBuilder b, + unsigned int milliseconds) { + LD_ASSERT_NOT_NULL(b); + + TO_BS_BUILDER(b)->StatusPollInterval( + std::chrono::milliseconds{milliseconds}); +} + +LD_EXPORT(void) +LDServerBigSegmentsBuilder_StaleAfterMs(LDServerBigSegmentsBuilder b, + unsigned int milliseconds) { + LD_ASSERT_NOT_NULL(b); + + TO_BS_BUILDER(b)->StaleAfter(std::chrono::milliseconds{milliseconds}); +} + +LD_EXPORT(void) +LDServerBigSegmentsBuilder_Free(LDServerBigSegmentsBuilder b) { + delete TO_BS_BUILDER(b); +} + +// NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast +// NOLINTEND OCInconsistentNamingInspection diff --git a/libs/server-sdk/src/bindings/c/builder.cpp b/libs/server-sdk/src/bindings/c/builder.cpp index e80cfc6bd..bd8066234 100644 --- a/libs/server-sdk/src/bindings/c/builder.cpp +++ b/libs/server-sdk/src/bindings/c/builder.cpp @@ -236,6 +236,17 @@ LDServerConfigBuilder_DataSystem_FDv2(LDServerConfigBuilder b, LDServerFDv2Builder_Free(fdv2_builder); } +LD_EXPORT(void) +LDServerConfigBuilder_BigSegments(LDServerConfigBuilder b, + LDServerBigSegmentsBuilder big_segments) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(big_segments); + + auto* bsb = reinterpret_cast(big_segments); + TO_BUILDER(b)->BigSegments(std::move(*bsb)); + LDServerBigSegmentsBuilder_Free(big_segments); +} + LD_EXPORT(void) LDServerConfigBuilder_DataSystem_Enabled(LDServerConfigBuilder b, bool const enabled) { diff --git a/libs/server-sdk/src/bindings/c/sdk.cpp b/libs/server-sdk/src/bindings/c/sdk.cpp index 1c46f7d9f..5290aa42f 100644 --- a/libs/server-sdk/src/bindings/c/sdk.cpp +++ b/libs/server-sdk/src/bindings/c/sdk.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,11 @@ struct Detail; #define TO_ALLFLAGS(ptr) (reinterpret_cast(ptr)) #define FROM_ALLFLAGS(ptr) (reinterpret_cast(ptr)) +#define TO_BIGSEGMENTSTORESTATUS(ptr) \ + (reinterpret_cast(ptr)) +#define FROM_BIGSEGMENTSTORESTATUS(ptr) \ + (reinterpret_cast(ptr)) + /* * Helper to perform the common functionality of checking if the user * requested a detail out parameter. If so, we allocate a copy of it @@ -407,6 +413,58 @@ LD_EXPORT(void) LDServerDataSourceStatus_Free(LDServerDataSourceStatus status) { delete TO_DATASOURCESTATUS(status); } +LD_EXPORT(bool) +LDServerBigSegmentStoreStatus_Available(LDServerBigSegmentStoreStatus status) { + LD_ASSERT_NOT_NULL(status); + return TO_BIGSEGMENTSTORESTATUS(status)->IsAvailable(); +} + +LD_EXPORT(bool) +LDServerBigSegmentStoreStatus_Stale(LDServerBigSegmentStoreStatus status) { + LD_ASSERT_NOT_NULL(status); + return TO_BIGSEGMENTSTORESTATUS(status)->IsStale(); +} + +LD_EXPORT(void) +LDServerBigSegmentStoreStatus_Free(LDServerBigSegmentStoreStatus status) { + delete TO_BIGSEGMENTSTORESTATUS(status); +} + +LD_EXPORT(void) +LDServerBigSegmentStoreStatusListener_Init( + struct LDServerBigSegmentStoreStatusListener* listener) { + listener->StatusChanged = nullptr; + listener->UserData = nullptr; +} + +LD_EXPORT(LDListenerConnection) +LDServerSDK_BigSegmentStoreStatus_OnStatusChange( + LDServerSDK sdk, + struct LDServerBigSegmentStoreStatusListener listener) { + LD_ASSERT_NOT_NULL(sdk); + + if (listener.StatusChanged) { + auto connection = + TO_SDK(sdk)->BigSegmentStoreStatus().OnBigSegmentStoreStatusChange( + [listener](BigSegmentStoreStatus status) { + listener.StatusChanged(FROM_BIGSEGMENTSTORESTATUS(&status), + listener.UserData); + }); + + return reinterpret_cast(connection.release()); + } + + return nullptr; +} + +LD_EXPORT(LDServerBigSegmentStoreStatus) +LDServerSDK_BigSegmentStoreStatus_Status(LDServerSDK sdk) { + LD_ASSERT_NOT_NULL(sdk); + + return FROM_BIGSEGMENTSTORESTATUS(new BigSegmentStoreStatus( + TO_SDK(sdk)->BigSegmentStoreStatus().Status())); +} + // HookContext variations #define AS_HOOK_CONTEXT(ptr) \ @@ -423,7 +481,7 @@ LDServerSDK_TrackEvent_WithHookContext(LDServerSDK sdk, if (hook_context) { TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, - *AS_HOOK_CONTEXT(hook_context)); + *AS_HOOK_CONTEXT(hook_context)); } else { TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name); } @@ -442,12 +500,11 @@ LDServerSDK_TrackMetric_WithHookContext(LDServerSDK sdk, LD_ASSERT_NOT_NULL(data); if (hook_context) { - TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, - *TO_VALUE(data), metric_value, - *AS_HOOK_CONTEXT(hook_context)); + TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, *TO_VALUE(data), + metric_value, *AS_HOOK_CONTEXT(hook_context)); } else { - TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, - *TO_VALUE(data), metric_value); + TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, *TO_VALUE(data), + metric_value); } LDValue_Free(data); @@ -465,11 +522,10 @@ LDServerSDK_TrackData_WithHookContext(LDServerSDK sdk, LD_ASSERT_NOT_NULL(data); if (hook_context) { - TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, - *TO_VALUE(data), *AS_HOOK_CONTEXT(hook_context)); + TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, *TO_VALUE(data), + *AS_HOOK_CONTEXT(hook_context)); } else { - TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, - *TO_VALUE(data)); + TO_SDK(sdk)->Track(*TO_CONTEXT(context), event_name, *TO_VALUE(data)); } LDValue_Free(data); @@ -487,11 +543,11 @@ LDServerSDK_BoolVariation_WithHookContext(LDServerSDK sdk, if (hook_context) { return TO_SDK(sdk)->BoolVariation(*TO_CONTEXT(context), flag_key, - default_value, - *AS_HOOK_CONTEXT(hook_context)); + default_value, + *AS_HOOK_CONTEXT(hook_context)); } else { return TO_SDK(sdk)->BoolVariation(*TO_CONTEXT(context), flag_key, - default_value); + default_value); } } @@ -508,11 +564,11 @@ LDServerSDK_BoolVariationDetail_WithHookContext(LDServerSDK sdk, LD_ASSERT_NOT_NULL(out_detail); auto result = hook_context - ? TO_SDK(sdk)->BoolVariationDetail(*TO_CONTEXT(context), - flag_key, default_value, - *AS_HOOK_CONTEXT(hook_context)) - : TO_SDK(sdk)->BoolVariationDetail(*TO_CONTEXT(context), - flag_key, default_value); + ? TO_SDK(sdk)->BoolVariationDetail( + *TO_CONTEXT(context), flag_key, default_value, + *AS_HOOK_CONTEXT(hook_context)) + : TO_SDK(sdk)->BoolVariationDetail( + *TO_CONTEXT(context), flag_key, default_value); *out_detail = FROM_DETAIL(new CEvaluationDetail(result)); return result.Value(); @@ -558,11 +614,11 @@ LDServerSDK_StringVariationDetail_WithHookContext(LDServerSDK sdk, LD_ASSERT_NOT_NULL(out_detail); auto result = hook_context - ? TO_SDK(sdk)->StringVariationDetail(*TO_CONTEXT(context), - flag_key, default_value, - *AS_HOOK_CONTEXT(hook_context)) - : TO_SDK(sdk)->StringVariationDetail(*TO_CONTEXT(context), - flag_key, default_value); + ? TO_SDK(sdk)->StringVariationDetail( + *TO_CONTEXT(context), flag_key, default_value, + *AS_HOOK_CONTEXT(hook_context)) + : TO_SDK(sdk)->StringVariationDetail( + *TO_CONTEXT(context), flag_key, default_value); *out_detail = FROM_DETAIL(new CEvaluationDetail(result)); @@ -583,11 +639,11 @@ LDServerSDK_IntVariation_WithHookContext(LDServerSDK sdk, if (hook_context) { return TO_SDK(sdk)->IntVariation(*TO_CONTEXT(context), flag_key, - default_value, - *AS_HOOK_CONTEXT(hook_context)); + default_value, + *AS_HOOK_CONTEXT(hook_context)); } else { return TO_SDK(sdk)->IntVariation(*TO_CONTEXT(context), flag_key, - default_value); + default_value); } } @@ -604,11 +660,11 @@ LDServerSDK_IntVariationDetail_WithHookContext(LDServerSDK sdk, LD_ASSERT_NOT_NULL(out_detail); auto result = hook_context - ? TO_SDK(sdk)->IntVariationDetail(*TO_CONTEXT(context), - flag_key, default_value, - *AS_HOOK_CONTEXT(hook_context)) - : TO_SDK(sdk)->IntVariationDetail(*TO_CONTEXT(context), - flag_key, default_value); + ? TO_SDK(sdk)->IntVariationDetail( + *TO_CONTEXT(context), flag_key, default_value, + *AS_HOOK_CONTEXT(hook_context)) + : TO_SDK(sdk)->IntVariationDetail( + *TO_CONTEXT(context), flag_key, default_value); *out_detail = FROM_DETAIL(new CEvaluationDetail(result)); return result.Value(); @@ -626,11 +682,11 @@ LDServerSDK_DoubleVariation_WithHookContext(LDServerSDK sdk, if (hook_context) { return TO_SDK(sdk)->DoubleVariation(*TO_CONTEXT(context), flag_key, - default_value, - *AS_HOOK_CONTEXT(hook_context)); + default_value, + *AS_HOOK_CONTEXT(hook_context)); } else { return TO_SDK(sdk)->DoubleVariation(*TO_CONTEXT(context), flag_key, - default_value); + default_value); } } @@ -647,11 +703,11 @@ LDServerSDK_DoubleVariationDetail_WithHookContext(LDServerSDK sdk, LD_ASSERT_NOT_NULL(out_detail); auto result = hook_context - ? TO_SDK(sdk)->DoubleVariationDetail(*TO_CONTEXT(context), - flag_key, default_value, - *AS_HOOK_CONTEXT(hook_context)) - : TO_SDK(sdk)->DoubleVariationDetail(*TO_CONTEXT(context), - flag_key, default_value); + ? TO_SDK(sdk)->DoubleVariationDetail( + *TO_CONTEXT(context), flag_key, default_value, + *AS_HOOK_CONTEXT(hook_context)) + : TO_SDK(sdk)->DoubleVariationDetail( + *TO_CONTEXT(context), flag_key, default_value); *out_detail = FROM_DETAIL(new CEvaluationDetail(result)); return result.Value(); @@ -694,14 +750,13 @@ LDServerSDK_JsonVariationDetail_WithHookContext(LDServerSDK sdk, LD_ASSERT_NOT_NULL(default_value); LD_ASSERT_NOT_NULL(out_detail); - auto result = hook_context - ? TO_SDK(sdk)->JsonVariationDetail(*TO_CONTEXT(context), - flag_key, - *TO_VALUE(default_value), - *AS_HOOK_CONTEXT(hook_context)) - : TO_SDK(sdk)->JsonVariationDetail(*TO_CONTEXT(context), - flag_key, - *TO_VALUE(default_value)); + auto result = + hook_context + ? TO_SDK(sdk)->JsonVariationDetail(*TO_CONTEXT(context), flag_key, + *TO_VALUE(default_value), + *AS_HOOK_CONTEXT(hook_context)) + : TO_SDK(sdk)->JsonVariationDetail(*TO_CONTEXT(context), flag_key, + *TO_VALUE(default_value)); *out_detail = FROM_DETAIL(new CEvaluationDetail(result)); return FROM_VALUE(new Value(std::move(result.Value()))); diff --git a/libs/server-sdk/tests/server_c_bindings_test.cpp b/libs/server-sdk/tests/server_c_bindings_test.cpp index e62c10a7c..aa8fcc4f8 100644 --- a/libs/server-sdk/tests/server_c_bindings_test.cpp +++ b/libs/server-sdk/tests/server_c_bindings_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -13,6 +14,24 @@ #include +namespace { + +class NoopBigSegmentStore + : public launchdarkly::server_side::integrations::IBigSegmentStore { + public: + [[nodiscard]] GetMembershipResult GetMembership( + std::string const&) const noexcept override { + return launchdarkly::server_side::integrations::Membership:: + FromSegmentRefs({}, {}); + } + + [[nodiscard]] GetMetadataResult GetMetadata() const noexcept override { + return std::nullopt; + } +}; + +} // namespace + TEST(ClientBindings, MinimalInstantiation) { LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); @@ -449,3 +468,91 @@ TEST(ClientBindings, FDv2DisableFDv1FallbackAndTimeouts) { LDServerConfig_Free(config); } + +TEST(ClientBindings, BigSegmentsBuilderAttachesToConfig) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + + auto* store = new NoopBigSegmentStore(); + LDServerBigSegmentsBuilder bs_builder = LDServerBigSegmentsBuilder_New( + reinterpret_cast(store)); + LDServerBigSegmentsBuilder_ContextCacheSize(bs_builder, 500); + LDServerBigSegmentsBuilder_ContextCacheTimeMs(bs_builder, 10000); + LDServerBigSegmentsBuilder_StatusPollIntervalMs(bs_builder, 15000); + LDServerBigSegmentsBuilder_StaleAfterMs(bs_builder, 60000); + + LDServerConfigBuilder_BigSegments(cfg_builder, bs_builder); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerConfig_Free(config); +} + +TEST(ClientBindings, BigSegmentStoreStatusWithoutStoreReportsUnavailable) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + LDServerConfigBuilder_Offline(cfg_builder, true); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerSDK sdk = LDServerSDK_New(config); + + LDServerBigSegmentStoreStatus bs_status = + LDServerSDK_BigSegmentStoreStatus_Status(sdk); + EXPECT_FALSE(LDServerBigSegmentStoreStatus_Available(bs_status)); + EXPECT_FALSE(LDServerBigSegmentStoreStatus_Stale(bs_status)); + + LDServerBigSegmentStoreStatus_Free(bs_status); + LDServerSDK_Free(sdk); +} + +TEST(ClientBindings, BigSegmentStoreStatusListenerReturnsNullWithoutCallback) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + LDServerConfigBuilder_Offline(cfg_builder, true); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerSDK sdk = LDServerSDK_New(config); + + struct LDServerBigSegmentStoreStatusListener listener{}; + LDServerBigSegmentStoreStatusListener_Init(&listener); + + LDListenerConnection connection = + LDServerSDK_BigSegmentStoreStatus_OnStatusChange(sdk, listener); + + EXPECT_EQ(nullptr, connection); + + LDServerSDK_Free(sdk); +} + +void BigSegmentStoreStatusListenerFunction(LDServerBigSegmentStoreStatus, + void*) {} + +TEST(ClientBindings, BigSegmentStoreStatusListenerRegistersWithCallback) { + LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123"); + LDServerConfigBuilder_Offline(cfg_builder, true); + + LDServerConfig config; + LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + LDServerSDK sdk = LDServerSDK_New(config); + + struct LDServerBigSegmentStoreStatusListener listener{}; + LDServerBigSegmentStoreStatusListener_Init(&listener); + listener.StatusChanged = BigSegmentStoreStatusListenerFunction; + listener.UserData = const_cast("cabbage"); + + LDListenerConnection connection = + LDServerSDK_BigSegmentStoreStatus_OnStatusChange(sdk, listener); + + EXPECT_NE(nullptr, connection); + + LDListenerConnection_Disconnect(connection); + LDListenerConnection_Free(connection); + LDServerSDK_Free(sdk); +}