Skip to content

Commit 219b9f8

Browse files
authored
feat: incomplete C bindings for client configuration (#45)
* add c_bindings/config/client.h * add error conversion helper * rename LDError to LDStatus * add config unit test
1 parent 7a51ffa commit 219b9f8

File tree

12 files changed

+340
-4
lines changed

12 files changed

+340
-4
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include <functional>
2+
#include <tl/expected.hpp>
3+
#include "c_bindings/status.h"
4+
#include "error.hpp"
5+
6+
template <typename T, typename = void>
7+
struct has_result_type : std::false_type {};
8+
9+
template <typename T>
10+
struct has_result_type<T, std::void_t<typename T::Result>> : std::true_type {};
11+
12+
template <typename T, typename ReturnType, typename = void>
13+
struct has_build_method : std::false_type {};
14+
15+
template <typename T, typename ReturnType>
16+
struct has_build_method<T,
17+
ReturnType,
18+
std::void_t<decltype(std::declval<T>().Build())>>
19+
: std::integral_constant<
20+
bool,
21+
std::is_same_v<decltype(std::declval<T>().Build()), ReturnType>> {};
22+
23+
// NOLINTBEGIN cppcoreguidelines-pro-type-reinterpret-cast
24+
25+
/*
26+
* Given a Builder, calls the Build() method and converts it into an
27+
* OpaqueResult if successful, or an LDError if unsuccessful.
28+
*
29+
* In the case of an error, out_result is set to nullptr.
30+
*
31+
* In all cases, the given builder is freed.
32+
*/
33+
template <typename Builder, typename OpaqueBuilder, typename OpaqueResult>
34+
LDStatus ConsumeBuilder(OpaqueBuilder opaque_builder,
35+
OpaqueResult* out_result) {
36+
using ReturnType =
37+
tl::expected<typename Builder::Result, launchdarkly::Error>;
38+
39+
static_assert(has_result_type<Builder>::value,
40+
"Builder must have an associated type named Result");
41+
42+
static_assert(
43+
has_build_method<Builder, ReturnType>::value,
44+
"Builder must have a Build method that returns "
45+
"tl::expected<typename Builder::Result, launchdarkly::Error>");
46+
47+
auto builder = reinterpret_cast<Builder*>(opaque_builder);
48+
49+
tl::expected<typename Builder::Result, launchdarkly::Error> res =
50+
builder->Build();
51+
52+
delete builder;
53+
54+
if (!res) {
55+
*out_result = nullptr;
56+
return reinterpret_cast<LDStatus>(new launchdarkly::Error(res.error()));
57+
}
58+
59+
*out_result = reinterpret_cast<OpaqueResult>(
60+
new typename Builder::Result(std::move(res.value())));
61+
62+
return LDStatus_Success();
63+
}
64+
65+
// NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// NOLINTBEGIN modernize-use-using
2+
3+
#pragma once
4+
5+
#include "../export.h"
6+
#include "../status.h"
7+
#include "./config.h"
8+
9+
#ifdef __cplusplus
10+
extern "C" { // only need to export C interface if
11+
// used by C++ source code
12+
#endif
13+
14+
typedef struct _LDClientConfigBuilder* LDClientConfigBuilder;
15+
16+
LD_EXPORT(LDClientConfigBuilder) LDClientConfigBuilder_New(char const* sdk_key);
17+
18+
/**
19+
* Creates an LDClientConfig. The LDClientConfigBuilder is consumed.
20+
* On success, the config will be stored in out_config; otherwise,
21+
* out_config will be set to NULL and the returned LDStatus will indicate the
22+
* error.
23+
* @param builder Builder to consume.
24+
* @param out_config Pointer to where the built config will be
25+
* stored.
26+
* @return Error status on failure.
27+
*/
28+
LD_EXPORT(LDStatus)
29+
LDClientConfigBuilder_Build(LDClientConfigBuilder builder,
30+
LDClientConfig* out_config);
31+
32+
/**
33+
* Frees the builder; only necessary if not calling Build.
34+
* @param builder Builder to free.
35+
*/
36+
LD_EXPORT(void)
37+
LDClientConfigBuilder_Free(LDClientConfigBuilder builder);
38+
39+
#ifdef __cplusplus
40+
}
41+
#endif
42+
43+
// NOLINTEND modernize-use-using
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// NOLINTBEGIN modernize-use-using
2+
3+
#pragma once
4+
5+
#include "../export.h"
6+
#include "../status.h"
7+
8+
#ifdef __cplusplus
9+
extern "C" { // only need to export C interface if
10+
// used by C++ source code
11+
#endif
12+
13+
typedef struct _LDClientConfig* LDClientConfig;
14+
15+
/**
16+
* Free the configuration. Configurations passed into an LDClient do not need to
17+
* be freed.
18+
* @param config Config to free.
19+
*/
20+
LD_EXPORT(void) LDClientConfig_Free(LDClientConfig config);
21+
22+
#ifdef __cplusplus
23+
}
24+
#endif
25+
26+
// NOLINTEND modernize-use-using
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// NOLINTBEGIN modernize-use-using
2+
3+
#pragma once
4+
5+
#include "./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+
typedef struct _LDStatus* LDStatus;
13+
14+
/**
15+
* Returns a string representing the error stored in an LDStatus, or
16+
* NULL if the result indicates success.
17+
* @param error Result to inspect.
18+
* @return String or NULL. The returned string is valid only while the LDStatus
19+
* is alive.
20+
*/
21+
LD_EXPORT(char const*) LDStatus_Error(LDStatus res);
22+
23+
/**
24+
* Checks if a result indicates success.
25+
* @param result Result to inspect.
26+
* @return True if the result indicates success.
27+
*/
28+
LD_EXPORT(bool) LDStatus_Ok(LDStatus res);
29+
30+
/**
31+
* Frees an LDStatus. It is only necessary to call LDStatus_Free if LDStatus_Ok
32+
* returns false.
33+
* @param res Result to free.
34+
*/
35+
LD_EXPORT(void) LDStatus_Free(LDStatus res);
36+
37+
/**
38+
* Returns a status representing success.
39+
* @return Successful status.
40+
*/
41+
LD_EXPORT(LDStatus) LDStatus_Success(void);
42+
43+
#ifdef __cplusplus
44+
}
45+
#endif
46+
47+
// NOLINTEND modernize-use-using

libs/common/include/config/detail/builders/config_builder.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ namespace launchdarkly::config::detail::builders {
2222
template <typename SDK>
2323
class ConfigBuilder {
2424
public:
25+
using Result = detail::Config<SDK>;
2526
using EndpointsBuilder = detail::builders::EndpointsBuilder<SDK>;
2627
using EventsBuilder = detail::builders::EventsBuilder<SDK>;
27-
using ConfigResult = tl::expected<detail::Config<SDK>, Error>;
2828
using DataSourceBuilder = detail::builders::DataSourceBuilder<SDK>;
2929
using HttpPropertiesBuilder = detail::builders::HttpPropertiesBuilder<SDK>;
3030
/**
@@ -85,7 +85,7 @@ class ConfigBuilder {
8585
* Builds a Configuration, suitable for passing into an instance of Client.
8686
* @return
8787
*/
88-
ConfigResult Build() const;
88+
tl::expected<Result, Error> Build() const;
8989

9090
private:
9191
std::string sdk_key_;

libs/common/src/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ add_library(${LIBNAME}
5353
c_bindings/array_builder.cpp
5454
c_bindings/object_builder.cpp
5555
c_bindings/context_builder.cpp
56-
c_bindings/context.cpp)
56+
c_bindings/status.cpp
57+
c_bindings/context.cpp
58+
c_bindings/config/builder.cpp
59+
c_bindings/config/config.cpp
60+
)
5761

5862

5963
add_library(launchdarkly::common ALIAS ${LIBNAME})
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// NOLINTBEGIN cppcoreguidelines-pro-type-reinterpret-cast
2+
// NOLINTBEGIN OCInconsistentNamingInspection
3+
4+
#include "c_bindings/config/builder.h"
5+
6+
#include "c_binding_helpers.hpp"
7+
#include "config/client.hpp"
8+
9+
using namespace launchdarkly::client_side;
10+
11+
#define TO_BUILDER(ptr) (reinterpret_cast<ConfigBuilder*>(ptr))
12+
#define FROM_BUILDER(ptr) (reinterpret_cast<LDClientConfigBuilder>(ptr))
13+
14+
LD_EXPORT(LDClientConfigBuilder)
15+
LDClientConfigBuilder_New(char const* sdk_key) {
16+
return FROM_BUILDER(new ConfigBuilder(sdk_key ? sdk_key : ""));
17+
}
18+
19+
LD_EXPORT(void)
20+
LDClientConfigBuilder_Free(LDClientConfigBuilder builder) {
21+
if (ConfigBuilder* b = TO_BUILDER(builder)) {
22+
delete b;
23+
}
24+
}
25+
26+
LD_EXPORT(LDStatus)
27+
LDClientConfigBuilder_Build(LDClientConfigBuilder builder,
28+
LDClientConfig* out_config) {
29+
return ConsumeBuilder<ConfigBuilder>(builder, out_config);
30+
}
31+
32+
// NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast
33+
// NOLINTEND OCInconsistentNamingInspection
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// NOLINTBEGIN cppcoreguidelines-pro-type-reinterpret-cast
2+
// NOLINTBEGIN OCInconsistentNamingInspection
3+
4+
#include "c_bindings/config/config.h"
5+
#include "config/client.hpp"
6+
7+
#define TO_CONFIG(ptr) (reinterpret_cast<Config*>(ptr))
8+
#define FROM_CONFIG(ptr) (reinterpret_cast<LDClientConfig>(ptr))
9+
10+
using namespace launchdarkly::client_side;
11+
12+
LD_EXPORT(void) LDClientConfig_Free(LDClientConfig config) {
13+
if (Config* c = TO_CONFIG(config)) {
14+
delete c;
15+
}
16+
}
17+
18+
// NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast
19+
// NOLINTEND OCInconsistentNamingInspection

libs/common/src/c_bindings/status.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// NOLINTBEGIN cppcoreguidelines-pro-type-reinterpret-cast
2+
// NOLINTBEGIN OCInconsistentNamingInspection
3+
4+
#include "c_bindings/status.h"
5+
6+
#include "error.hpp"
7+
8+
using namespace launchdarkly;
9+
10+
#define TO_ERROR(ptr) (reinterpret_cast<Error*>(ptr))
11+
12+
LD_EXPORT(char const*) LDStatus_Error(LDStatus res) {
13+
if (Error* e = TO_ERROR(res)) {
14+
return ErrorToString(*e);
15+
}
16+
return nullptr;
17+
}
18+
19+
LD_EXPORT(bool) LDStatus_Ok(LDStatus res) {
20+
if (TO_ERROR(res) == nullptr) {
21+
return true;
22+
}
23+
return false;
24+
}
25+
26+
LD_EXPORT(void) LDStatus_Free(LDStatus error) {
27+
if (Error* e = TO_ERROR(error)) {
28+
delete e;
29+
}
30+
}
31+
32+
LD_EXPORT(LDStatus) LDStatus_Success(void) {
33+
return nullptr;
34+
}
35+
36+
// NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast
37+
// NOLINTEND OCInconsistentNamingInspection

libs/common/src/config/config_builder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ ConfigBuilder<SDK>& ConfigBuilder<SDK>::HttpProperties(
4747
}
4848

4949
template <typename SDK>
50-
[[nodiscard]] typename ConfigBuilder<SDK>::ConfigResult
50+
[[nodiscard]] tl::expected<typename ConfigBuilder<SDK>::Result, Error>
5151
ConfigBuilder<SDK>::Build() const {
5252
auto sdk_key = sdk_key_;
5353
if (sdk_key.empty()) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include <gtest/gtest.h>
2+
#include "c_bindings/config/builder.h"
3+
4+
TEST(ClientConfigBindings, ConfigBuilderNewFree) {
5+
LDClientConfigBuilder builder = LDClientConfigBuilder_New("sdk-123");
6+
ASSERT_TRUE(builder);
7+
LDClientConfigBuilder_Free(builder);
8+
}
9+
10+
TEST(ClientConfigBindings, ConfigBuilderEmptyResultsInError) {
11+
LDClientConfigBuilder builder = LDClientConfigBuilder_New(nullptr);
12+
13+
LDClientConfig config = nullptr;
14+
LDStatus status = LDClientConfigBuilder_Build(builder, &config);
15+
16+
ASSERT_FALSE(config);
17+
ASSERT_FALSE(LDStatus_Ok(status));
18+
ASSERT_TRUE(LDStatus_Error(status));
19+
20+
LDStatus_Free(status);
21+
// LDClientConfigBuilder is consumed by Build; no need to free it.
22+
}
23+
24+
TEST(ClientConfigBindings, MinimalValidConfig) {
25+
LDClientConfigBuilder builder = LDClientConfigBuilder_New("sdk-123");
26+
27+
LDClientConfig config = nullptr;
28+
LDStatus status = LDClientConfigBuilder_Build(builder, &config);
29+
ASSERT_TRUE(LDStatus_Ok(status));
30+
ASSERT_TRUE(config);
31+
32+
LDClientConfig_Free(config);
33+
// LDClientConfigBuilder is consumed by Build; no need to free it.
34+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <gtest/gtest.h>
2+
3+
#include "error.hpp"
4+
5+
#include "c_bindings/status.h"
6+
7+
// NOLINTBEGIN cppcoreguidelines-pro-type-reinterpret-cast
8+
9+
TEST(StatusBindingTests, StatusOk) {
10+
LDStatus status = LDStatus_Success();
11+
ASSERT_TRUE(LDStatus_Ok(status));
12+
ASSERT_FALSE(LDStatus_Error(status));
13+
LDStatus_Free(status);
14+
}
15+
16+
TEST(StatusBindingTests, StatusError) {
17+
using namespace launchdarkly;
18+
19+
Error err = Error::kConfig_SDKKey_Empty;
20+
21+
auto status = reinterpret_cast<LDStatus>(new Error(err));
22+
ASSERT_FALSE(LDStatus_Ok(status));
23+
ASSERT_TRUE(LDStatus_Error(status));
24+
ASSERT_STREQ(LDStatus_Error(status), ErrorToString(err));
25+
LDStatus_Free(status);
26+
}
27+
28+
// NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast

0 commit comments

Comments
 (0)