Skip to content

Commit 00c3ce9

Browse files
committed
feat: Add C bindings for DynamoDB LazyLoad source
1 parent e06f24a commit 00c3ce9

6 files changed

Lines changed: 559 additions & 0 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/** @file dynamodb_client_options.h
2+
* @brief LaunchDarkly Server-side DynamoDB Client Options C Binding.
3+
*/
4+
// NOLINTBEGIN modernize-use-using
5+
#pragma once
6+
7+
#include <launchdarkly/bindings/c/export.h>
8+
9+
#ifdef __cplusplus
10+
extern "C" {
11+
// only need to export C interface if
12+
// used by C++ source code
13+
#endif
14+
15+
/**
16+
* @brief LDServerDynamoDBClientOptionsBuilder configures the AWS DynamoDB
17+
* client that a LaunchDarkly DynamoDB integration will use.
18+
*
19+
* All fields are optional. When left unset, the AWS SDK's default provider
20+
* chain (environment variables, shared config, EC2/ECS instance metadata) is
21+
* used to resolve the corresponding field.
22+
*
23+
* The builder is passed by handle to a DynamoDB source or store factory,
24+
* which takes ownership and frees it. Callers only need to call
25+
* @ref LDServerDynamoDBClientOptionsBuilder_Free directly if the builder is
26+
* not passed to a factory.
27+
*/
28+
typedef struct _LDServerDynamoDBClientOptionsBuilder*
29+
LDServerDynamoDBClientOptionsBuilder;
30+
31+
/**
32+
* @brief Creates a new DynamoDB client options builder with all fields unset.
33+
*
34+
* @return A new builder handle.
35+
*/
36+
LD_EXPORT(LDServerDynamoDBClientOptionsBuilder)
37+
LDServerDynamoDBClientOptionsBuilder_New(void);
38+
39+
/**
40+
* @brief Sets the AWS region for the DynamoDB client.
41+
*
42+
* When unset, the AWS SDK resolves the region via the standard region
43+
* provider chain (environment variables, shared config file, instance
44+
* metadata).
45+
*
46+
* @param b Builder. Must not be NULL.
47+
* @param region Region string (e.g. "us-east-1"). Must not be NULL.
48+
*/
49+
LD_EXPORT(void)
50+
LDServerDynamoDBClientOptionsBuilder_Region(
51+
LDServerDynamoDBClientOptionsBuilder b,
52+
char const* region);
53+
54+
/**
55+
* @brief Sets a custom endpoint for the DynamoDB client.
56+
*
57+
* Useful when pointing at DynamoDB Local or LocalStack for testing (e.g.
58+
* "http://localhost:8000"). When unset, the AWS SDK uses the standard
59+
* DynamoDB endpoint for the resolved region.
60+
*
61+
* @param b Builder. Must not be NULL.
62+
* @param endpoint Endpoint URL. Must not be NULL.
63+
*/
64+
LD_EXPORT(void)
65+
LDServerDynamoDBClientOptionsBuilder_Endpoint(
66+
LDServerDynamoDBClientOptionsBuilder b,
67+
char const* endpoint);
68+
69+
/**
70+
* @brief Sets the AWS access key ID for the DynamoDB client.
71+
*
72+
* When none of AccessKeyId, SecretAccessKey, or SessionToken are set, the
73+
* AWS SDK's default credential provider chain is used (environment
74+
* variables, shared credentials file, EC2/ECS roles).
75+
*
76+
* @param b Builder. Must not be NULL.
77+
* @param access_key_id AWS access key ID. Must not be NULL.
78+
*/
79+
LD_EXPORT(void)
80+
LDServerDynamoDBClientOptionsBuilder_AccessKeyId(
81+
LDServerDynamoDBClientOptionsBuilder b,
82+
char const* access_key_id);
83+
84+
/**
85+
* @brief Sets the AWS secret access key for the DynamoDB client.
86+
*
87+
* @param b Builder. Must not be NULL.
88+
* @param secret_access_key AWS secret access key. Must not be NULL.
89+
*/
90+
LD_EXPORT(void)
91+
LDServerDynamoDBClientOptionsBuilder_SecretAccessKey(
92+
LDServerDynamoDBClientOptionsBuilder b,
93+
char const* secret_access_key);
94+
95+
/**
96+
* @brief Sets the AWS session token for the DynamoDB client (for temporary
97+
* credentials).
98+
*
99+
* @param b Builder. Must not be NULL.
100+
* @param session_token AWS session token. Must not be NULL.
101+
*/
102+
LD_EXPORT(void)
103+
LDServerDynamoDBClientOptionsBuilder_SessionToken(
104+
LDServerDynamoDBClientOptionsBuilder b,
105+
char const* session_token);
106+
107+
/**
108+
* @brief Frees a DynamoDB client options builder. Do not call if the builder
109+
* was consumed by a DynamoDB source or store factory.
110+
*
111+
* @param b Builder to free.
112+
*/
113+
LD_EXPORT(void)
114+
LDServerDynamoDBClientOptionsBuilder_Free(
115+
LDServerDynamoDBClientOptionsBuilder b);
116+
117+
#ifdef __cplusplus
118+
}
119+
#endif
120+
121+
// NOLINTEND modernize-use-using
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/** @file dynamodb_source.h
2+
* @brief LaunchDarkly Server-side DynamoDB Source C Binding.
3+
*/
4+
// NOLINTBEGIN modernize-use-using
5+
#pragma once
6+
7+
#include <launchdarkly/server_side/bindings/c/integrations/dynamodb/dynamodb_client_options.h>
8+
9+
#include <launchdarkly/bindings/c/export.h>
10+
11+
#ifdef __cplusplus
12+
extern "C" {
13+
// only need to export C interface if
14+
// used by C++ source code
15+
#endif
16+
17+
/**
18+
* @brief LDServerLazyLoadDynamoDBSource represents a data source for the
19+
* Server-Side SDK backed by Amazon DynamoDB. It is meant to be used in place
20+
* of the standard LaunchDarkly Streaming or Polling data sources.
21+
*
22+
* Call @ref LDServerLazyLoadDynamoDBSource_New to obtain a new instance.
23+
* This instance can be passed into the SDK's DataSystem configuration via
24+
* the LazyLoad builder.
25+
*
26+
* The DynamoDB table must already exist and follow the LaunchDarkly schema:
27+
* a String partition key named `namespace` and a String sort key named
28+
* `key`. The LaunchDarkly Relay Proxy populates the table with this schema;
29+
* this source only reads from it.
30+
*
31+
* Example:
32+
* @code
33+
* // Optional: configure the AWS DynamoDB client. Pass NULL for defaults.
34+
* LDServerDynamoDBClientOptionsBuilder options =
35+
* LDServerDynamoDBClientOptionsBuilder_New();
36+
* LDServerDynamoDBClientOptionsBuilder_Region(options, "us-east-1");
37+
*
38+
* // Stack allocate the result struct, which will hold the result pointer or
39+
* // an error message.
40+
* struct LDServerLazyLoadDynamoDBResult result;
41+
*
42+
* if (!LDServerLazyLoadDynamoDBSource_New("my-table", "testprefix", options,
43+
* &result)) {
44+
* // On failure, you may print the error message (result.error_message),
45+
* // then exit or return.
46+
* }
47+
*
48+
* LDServerLazyLoadBuilder lazy_builder = LDServerLazyLoadBuilder_New();
49+
* LDServerLazyLoadBuilder_SourcePtr(
50+
* lazy_builder, (LDServerLazyLoadSourcePtr)result.source);
51+
*
52+
* LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123");
53+
* LDServerConfigBuilder_DataSystem_LazyLoad(cfg_builder, lazy_builder);
54+
* @endcode
55+
*
56+
* This implementation is backed by the AWS SDK for C++.
57+
*/
58+
typedef struct _LDServerLazyLoadDynamoDBSource* LDServerLazyLoadDynamoDBSource;
59+
60+
/* Defines the size of the error message buffer in
61+
* LDServerLazyLoadDynamoDBResult.
62+
*/
63+
#ifndef LDSERVER_LAZYLOAD_DYNAMODBSOURCE_ERROR_MESSAGE_SIZE
64+
#define LDSERVER_LAZYLOAD_DYNAMODBSOURCE_ERROR_MESSAGE_SIZE 256
65+
#endif
66+
67+
/**
68+
* @brief Stores the result of calling @ref LDServerLazyLoadDynamoDBSource_New.
69+
*
70+
* On successful creation, source will contain a pointer which may be passed
71+
* into the LaunchDarkly SDK's configuration.
72+
*
73+
* On failure, error_message contains a NULL-terminated string describing the
74+
* error.
75+
*
76+
* The message may be truncated if it was originally longer than
77+
* error_message's buffer size.
78+
*/
79+
struct LDServerLazyLoadDynamoDBResult {
80+
LDServerLazyLoadDynamoDBSource source;
81+
char error_message[LDSERVER_LAZYLOAD_DYNAMODBSOURCE_ERROR_MESSAGE_SIZE];
82+
};
83+
84+
/**
85+
* @brief Creates a new DynamoDB data source suitable for usage in the SDK's
86+
* Lazy Load data system.
87+
*
88+
* In this system, the SDK will query DynamoDB for flag/segment data as
89+
* required, with an in-memory cache to reduce the number of queries.
90+
*
91+
* Data is never written back to DynamoDB by the SDK; the LaunchDarkly Relay
92+
* Proxy populates the table.
93+
*
94+
* @param table_name Name of the DynamoDB table to read from. The table must
95+
* already exist; this function does not create it. Must not be NULL.
96+
*
97+
* @param prefix Prefix to use when reading SDK data from DynamoDB. This
98+
* allows multiple SDK environments to coexist in the same table. Must not
99+
* be NULL.
100+
*
101+
* @param options Optional AWS DynamoDB client configuration. When non-NULL,
102+
* the builder is consumed and freed by this function; do not call
103+
* @ref LDServerDynamoDBClientOptionsBuilder_Free on it afterward. When NULL,
104+
* the AWS SDK's default provider chain is used for region, endpoint, and
105+
* credentials.
106+
*
107+
* @param out_result Pointer to struct where the source pointer or an error
108+
* message should be stored. Must not be NULL.
109+
*
110+
* @return True if the source was created successfully; out_result->source
111+
* will contain the pointer. The caller must either free the pointer with
112+
* @ref LDServerLazyLoadDynamoDBSource_Free, OR pass it into the SDK's
113+
* configuration methods which will take ownership (in which case do not
114+
* call @ref LDServerLazyLoadDynamoDBSource_Free.)
115+
*/
116+
LD_EXPORT(bool)
117+
LDServerLazyLoadDynamoDBSource_New(
118+
char const* table_name,
119+
char const* prefix,
120+
LDServerDynamoDBClientOptionsBuilder options,
121+
struct LDServerLazyLoadDynamoDBResult* out_result);
122+
123+
/**
124+
* @brief Frees a DynamoDB data source pointer. Only necessary to call if not
125+
* passing ownership to SDK configuration.
126+
*/
127+
LD_EXPORT(void)
128+
LDServerLazyLoadDynamoDBSource_Free(LDServerLazyLoadDynamoDBSource source);
129+
130+
#ifdef __cplusplus
131+
}
132+
#endif
133+
134+
// NOLINTEND modernize-use-using

libs/server-sdk-dynamodb-source/src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ target_sources(${LIBNAME}
1717
dynamodb_big_segment_store.cpp
1818
aws_sdk_guard.cpp
1919
client_factory.cpp
20+
bindings/dynamodb/client_options.cpp
21+
bindings/dynamodb/dynamodb_source.cpp
2022
)
2123

2224

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include <launchdarkly/server_side/bindings/c/integrations/dynamodb/dynamodb_client_options.h>
2+
3+
#include <launchdarkly/server_side/integrations/dynamodb/options.hpp>
4+
5+
#include <launchdarkly/detail/c_binding_helpers.hpp>
6+
7+
using namespace launchdarkly::server_side::integrations;
8+
9+
#define TO_OPTIONS(ptr) (reinterpret_cast<DynamoDBClientOptions*>(ptr))
10+
#define FROM_OPTIONS(ptr) \
11+
(reinterpret_cast<LDServerDynamoDBClientOptionsBuilder>(ptr))
12+
13+
LD_EXPORT(LDServerDynamoDBClientOptionsBuilder)
14+
LDServerDynamoDBClientOptionsBuilder_New(void) {
15+
return FROM_OPTIONS(new DynamoDBClientOptions{});
16+
}
17+
18+
LD_EXPORT(void)
19+
LDServerDynamoDBClientOptionsBuilder_Region(
20+
LDServerDynamoDBClientOptionsBuilder b,
21+
char const* region) {
22+
LD_ASSERT_NOT_NULL(b);
23+
LD_ASSERT_NOT_NULL(region);
24+
TO_OPTIONS(b)->region = region;
25+
}
26+
27+
LD_EXPORT(void)
28+
LDServerDynamoDBClientOptionsBuilder_Endpoint(
29+
LDServerDynamoDBClientOptionsBuilder b,
30+
char const* endpoint) {
31+
LD_ASSERT_NOT_NULL(b);
32+
LD_ASSERT_NOT_NULL(endpoint);
33+
TO_OPTIONS(b)->endpoint = endpoint;
34+
}
35+
36+
LD_EXPORT(void)
37+
LDServerDynamoDBClientOptionsBuilder_AccessKeyId(
38+
LDServerDynamoDBClientOptionsBuilder b,
39+
char const* access_key_id) {
40+
LD_ASSERT_NOT_NULL(b);
41+
LD_ASSERT_NOT_NULL(access_key_id);
42+
TO_OPTIONS(b)->aws_access_key_id = access_key_id;
43+
}
44+
45+
LD_EXPORT(void)
46+
LDServerDynamoDBClientOptionsBuilder_SecretAccessKey(
47+
LDServerDynamoDBClientOptionsBuilder b,
48+
char const* secret_access_key) {
49+
LD_ASSERT_NOT_NULL(b);
50+
LD_ASSERT_NOT_NULL(secret_access_key);
51+
TO_OPTIONS(b)->aws_secret_access_key = secret_access_key;
52+
}
53+
54+
LD_EXPORT(void)
55+
LDServerDynamoDBClientOptionsBuilder_SessionToken(
56+
LDServerDynamoDBClientOptionsBuilder b,
57+
char const* session_token) {
58+
LD_ASSERT_NOT_NULL(b);
59+
LD_ASSERT_NOT_NULL(session_token);
60+
TO_OPTIONS(b)->aws_session_token = session_token;
61+
}
62+
63+
LD_EXPORT(void)
64+
LDServerDynamoDBClientOptionsBuilder_Free(
65+
LDServerDynamoDBClientOptionsBuilder b) {
66+
delete TO_OPTIONS(b);
67+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <launchdarkly/server_side/bindings/c/integrations/dynamodb/dynamodb_source.h>
2+
3+
#include <launchdarkly/server_side/integrations/dynamodb/dynamodb_source.hpp>
4+
#include <launchdarkly/server_side/integrations/dynamodb/options.hpp>
5+
6+
#include <launchdarkly/detail/c_binding_helpers.hpp>
7+
8+
#include <cstring>
9+
#include <utility>
10+
11+
using namespace launchdarkly::server_side::integrations;
12+
13+
LD_EXPORT(bool)
14+
LDServerLazyLoadDynamoDBSource_New(char const* table_name,
15+
char const* prefix,
16+
LDServerDynamoDBClientOptionsBuilder options,
17+
LDServerLazyLoadDynamoDBResult* out_result) {
18+
LD_ASSERT_NOT_NULL(table_name);
19+
LD_ASSERT_NOT_NULL(prefix);
20+
LD_ASSERT_NOT_NULL(out_result);
21+
22+
// Explicitely zero out the error_message buffer in case the error is
23+
// shorter than the buffer.
24+
memset(out_result->error_message, 0,
25+
sizeof(LDServerLazyLoadDynamoDBResult::error_message));
26+
27+
// Ensure the source pointer isn't garbage.
28+
out_result->source = nullptr;
29+
30+
DynamoDBClientOptions opts{};
31+
if (options != nullptr) {
32+
auto* opts_ptr = reinterpret_cast<DynamoDBClientOptions*>(options);
33+
opts = *opts_ptr;
34+
LDServerDynamoDBClientOptionsBuilder_Free(options);
35+
}
36+
37+
auto maybe_source =
38+
DynamoDBDataSource::Create(table_name, prefix, std::move(opts));
39+
if (!maybe_source) {
40+
// Avoid heap allocating another string to pass back to the caller;
41+
// instead, we copy into the buffer and ensure a terminator is present.
42+
// This does mean the message may be truncated.
43+
std::size_t const len = maybe_source.error().copy(
44+
out_result->error_message, sizeof(out_result->error_message) - 1);
45+
out_result->error_message[len] = '\0';
46+
return false;
47+
}
48+
49+
// The pointer is no longer managed and must either be freed by the caller,
50+
// or passed into the SDK which will take ownership.
51+
out_result->source = reinterpret_cast<LDServerLazyLoadDynamoDBSource>(
52+
maybe_source->release());
53+
return true;
54+
}
55+
56+
LD_EXPORT(void)
57+
LDServerLazyLoadDynamoDBSource_Free(LDServerLazyLoadDynamoDBSource source) {
58+
delete reinterpret_cast<DynamoDBDataSource*>(source);
59+
}

0 commit comments

Comments
 (0)