Skip to content

Commit 11a7f61

Browse files
authored
feat: Add c bindings for FlagNotifier. (#119)
1 parent fc90335 commit 11a7f61

File tree

7 files changed

+205
-2
lines changed

7 files changed

+205
-2
lines changed

libs/client-sdk/include/launchdarkly/client_side/bindings/c/sdk.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
#include <launchdarkly/bindings/c/context.h>
77
#include <launchdarkly/bindings/c/data/evaluation_detail.h>
88
#include <launchdarkly/bindings/c/export.h>
9+
#include <launchdarkly/bindings/c/listener_connection.h>
910
#include <launchdarkly/bindings/c/memory_routines.h>
1011
#include <launchdarkly/bindings/c/status.h>
1112
#include <launchdarkly/bindings/c/value.h>
13+
1214
#include <stddef.h>
1315

1416
#ifdef __cplusplus
@@ -381,6 +383,74 @@ LDClientSDK_JsonVariationDetail(LDClientSDK sdk,
381383
*/
382384
LD_EXPORT(void) LDClientSDK_Free(LDClientSDK sdk);
383385

386+
typedef void (*FlagChangedCallbackFn)(char const* flag_key,
387+
LDValue new_value,
388+
LDValue old_value,
389+
bool deleted,
390+
void* user_data);
391+
392+
/**
393+
* Defines a feature flag listener which may be used to listen for flag changes.
394+
* The struct should be initialized using LDFlagListener_Init before use.
395+
*/
396+
struct LDFlagListener {
397+
/**
398+
* Callback function which is invoked for flag changes.
399+
*
400+
* The provided pointers are only valid for the duration of the function
401+
* call.
402+
*
403+
* @param flag_key The name of the flag that changed.
404+
* @param new_value The new value of the flag. If there was not an new
405+
* value, because the flag was deleted, then the LDValue will be of a null
406+
* type. Check the deleted parameter to see if a flag was deleted.
407+
* @param old_value The old value of the flag. If there was not an old
408+
* value, for instance a newly created flag, then the Value will be of a
409+
* null type.
410+
* @param deleted True if the flag has been deleted.
411+
*/
412+
FlagChangedCallbackFn FlagChanged;
413+
414+
/**
415+
* UserData is forwarded into callback functions.
416+
*/
417+
void* UserData;
418+
};
419+
420+
/**
421+
* Initializes a flag listener. Must be called before passing the listener
422+
* to LDClientSDK_FlagNotifier_OnFlagChange.
423+
*
424+
* Create the struct, initialize the struct, set the FlagChanged handler
425+
* and optionally UserData, and then pass the struct to
426+
* LDClientSDK_FlagNotifier_OnFlagChange.
427+
*
428+
* @param listener Listener to initialize.
429+
*/
430+
LD_EXPORT(void) LDFlagListener_Init(struct LDFlagListener listener);
431+
432+
/**
433+
* Listen for changes for the specific flag.
434+
*
435+
* If the FlagChanged member of the listener struct is not set (NULL), then the
436+
* function will not register a listener. In that case the return value
437+
* will be NULL.
438+
*
439+
* @param sdk SDK. Must not be NULL.
440+
* @param flag_key The unique key for the feature flag. Must not be NULL.
441+
* @param listener The listener, whose FlagChanged callback will be invoked,
442+
* when the flag changes. Must not be NULL.
443+
*
444+
* @return A LDListenerConnection. The connection can be freed using
445+
* LDListenerConnection_Free and the listener can be disconnected using
446+
* LDListenerConnection_Disconnect. NULL will be returned if the FlagChanged
447+
* member of the listener struct is NULL.
448+
*/
449+
LD_EXPORT(LDListenerConnection)
450+
LDClientSDK_FlagNotifier_OnFlagChange(LDClientSDK sdk,
451+
char const* flag_key,
452+
struct LDFlagListener listener);
453+
384454
#ifdef __cplusplus
385455
}
386456
#endif

libs/client-sdk/src/bindings/c/sdk.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,5 +296,37 @@ LD_EXPORT(void) LDClientSDK_Free(LDClientSDK sdk) {
296296
delete TO_SDK(sdk);
297297
}
298298

299+
LD_EXPORT(LDListenerConnection)
300+
LDClientSDK_FlagNotifier_OnFlagChange(LDClientSDK sdk,
301+
char const* flag_key,
302+
struct LDFlagListener listener) {
303+
LD_ASSERT_NOT_NULL(sdk);
304+
LD_ASSERT_NOT_NULL(flag_key);
305+
306+
if (listener.FlagChanged) {
307+
auto connection = TO_SDK(sdk)->FlagNotifier().OnFlagChange(
308+
flag_key,
309+
[listener](std::shared_ptr<launchdarkly::client_side::flag_manager::
310+
FlagValueChangeEvent> event) {
311+
listener.FlagChanged(
312+
event->FlagName().c_str(),
313+
reinterpret_cast<LDValue>(
314+
const_cast<Value*>(&event->NewValue())),
315+
reinterpret_cast<LDValue>(
316+
const_cast<Value*>(&event->OldValue())),
317+
event->Deleted(), listener.UserData);
318+
});
319+
320+
return reinterpret_cast<LDListenerConnection>(connection.release());
321+
}
322+
323+
return nullptr;
324+
}
325+
326+
LD_EXPORT(void) LDFlagListener_Init(struct LDFlagListener listener) {
327+
listener.FlagChanged = nullptr;
328+
listener.UserData = nullptr;
329+
}
330+
299331
// NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast
300332
// NOLINTEND OCInconsistentNamingInspection

libs/client-sdk/tests/client_c_bindings_test.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,44 @@ TEST(ClientBindings, MinimalInstantiation) {
2424

2525
LDClientSDK_Free(sdk);
2626
}
27+
28+
void ListenerFunction(char const* flag_key,
29+
LDValue new_value,
30+
LDValue old_value,
31+
bool deleted,
32+
void* user_data) {
33+
}
34+
35+
// This test registers a listener. It doesn't use the listener, but it
36+
// will at least ensure 1.) Compilation, and 2.) Allow sanitizers to run.
37+
TEST(ClientBindings, RegisterFlagListener) {
38+
LDClientConfigBuilder cfg_builder = LDClientConfigBuilder_New("sdk-123");
39+
LDClientConfigBuilder_Offline(cfg_builder, true);
40+
41+
LDClientConfig config;
42+
LDStatus status = LDClientConfigBuilder_Build(cfg_builder, &config);
43+
ASSERT_TRUE(LDStatus_Ok(status));
44+
45+
LDContextBuilder ctx_builder = LDContextBuilder_New();
46+
LDContextBuilder_AddKind(ctx_builder, "user", "shadow");
47+
48+
LDContext context = LDContextBuilder_Build(ctx_builder);
49+
50+
LDClientSDK sdk = LDClientSDK_New(config, context);
51+
52+
bool success = false;
53+
LDClientSDK_Start(sdk, 3000, &success);
54+
EXPECT_TRUE(success);
55+
56+
struct LDFlagListener listener{};
57+
LDFlagListener_Init(listener);
58+
listener.UserData = const_cast<char*>("Potato");
59+
listener.FlagChanged = ListenerFunction;
60+
61+
LDListenerConnection connection = LDClientSDK_FlagNotifier_OnFlagChange(sdk, "my-boolean-flag", listener);
62+
63+
LDListenerConnection_Disconnect(connection);
64+
65+
LDListenerConnection_Free(connection);
66+
LDClientSDK_Free(sdk);
67+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// NOLINTBEGIN modernize-use-using
2+
3+
#pragma once
4+
5+
#include <launchdarkly/bindings/c/export.h>
6+
7+
#ifdef __cplusplus
8+
extern "C" { // only need to export C interface if
9+
// used by C++ source code
10+
#endif
11+
12+
/**
13+
* Handle that represents a listener connection.
14+
*
15+
* To stop unregister a listener call LDListenerConnection_Disconnect.
16+
* To free a connection listener call LDListenerConnection_Free.
17+
*
18+
* Freeing an LDListenerConnection does not disconnect the connection. If it is
19+
* deleted, without being disconnected, then the listener will remain active
20+
* until the associated SDK is freed.
21+
*/
22+
typedef struct _LDListenerConnection* LDListenerConnection;
23+
24+
/**
25+
* Disconnect a listener.
26+
*
27+
* @param connection The connection for the listener to disconnect.
28+
* Must not be NULL.
29+
*/
30+
LD_EXPORT(void) LDListenerConnection_Disconnect(LDListenerConnection connection);
31+
32+
/**
33+
* Free a listener connection.
34+
*
35+
* @param connection The LDListenerConnection to free.
36+
*/
37+
LD_EXPORT(void) LDListenerConnection_Free(LDListenerConnection connection);
38+
39+
#ifdef __cplusplus
40+
}
41+
#endif

libs/common/include/launchdarkly/connection.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#pragma once
22

3-
namespace launchdarkly::client_side {
3+
namespace launchdarkly {
44

55
/**
66
* Represents the connection of a listener.

libs/common/src/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ add_library(${LIBNAME} OBJECT
4949
config/persistence_builder.cpp
5050
bindings/c/data/evaluation_detail.cpp
5151
bindings/c/memory_routines.cpp
52-
config/logging_builder.cpp)
52+
config/logging_builder.cpp
53+
bindings/c/listener_connection.cpp)
5354

5455
add_library(launchdarkly::common ALIAS ${LIBNAME})
5556

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <launchdarkly/bindings/c/listener_connection.h>
2+
#include <launchdarkly/detail/c_binding_helpers.hpp>
3+
4+
#include <launchdarkly/connection.hpp>
5+
6+
#include <memory>
7+
8+
#define TO_LC(ptr) (reinterpret_cast<launchdarkly::IConnection*>(ptr))
9+
10+
LD_EXPORT(void)
11+
LDListenerConnection_Disconnect(LDListenerConnection connection) {
12+
LD_ASSERT_NOT_NULL(connection);
13+
TO_LC(connection)->Disconnect();
14+
}
15+
16+
LD_EXPORT(void) LDListenerConnection_Free(LDListenerConnection connection) {
17+
delete TO_LC(connection);
18+
}

0 commit comments

Comments
 (0)