Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add LDAllFlagsState_Map C binding #350

Merged
merged 8 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ LDClientSDK_JsonVariationDetail(LDClientSDK sdk,
* @code
* LDValue all_flags = LDClientSDK_AllFlags(sdk);
* LDValue_ObjectIter it;
* for (it = LDValue_CreateObjectIter(all_flags);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(typo)

* for (it = LDValue_ObjectIter_New(all_flags);
* !LDValue_ObjectIter_End(it); LDValue_ObjectIter_Next(it)) { char
* const* flag_key = LDValue_ObjectIter_Key(it); LDValue flag_val_ref =
* LDValue_ObjectIter_Value(it);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma once

#include <launchdarkly/bindings/c/status.h>
#include <launchdarkly/error.hpp>

Expand Down
86 changes: 86 additions & 0 deletions libs/server-sdk-redis-source/tests/c_bindings_test.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
#include <gtest/gtest.h>

Check notice on line 1 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

Run clang-format on libs/server-sdk-redis-source/tests/c_bindings_test.cpp

File libs/server-sdk-redis-source/tests/c_bindings_test.cpp does not conform to Custom style guidelines. (lines 94, 95)

#include <launchdarkly/server_side/bindings/c/integrations/redis/redis_source.h>

Check failure on line 3 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:3:10 [clang-diagnostic-error]

'launchdarkly/server_side/bindings/c/integrations/redis/redis_source.h' file not found

#include <launchdarkly/bindings/c/context_builder.h>
#include <launchdarkly/server_side/bindings/c/config/builder.h>
#include <launchdarkly/server_side/bindings/c/sdk.h>

#include <launchdarkly/data_model/flag.hpp>

#include "prefixed_redis_client.hpp"

using namespace launchdarkly::data_model;

TEST(RedisBindings, SourcePointerIsStoredOnSuccessfulCreation) {
LDServerLazyLoadRedisResult result;

Check warning on line 16 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:16:33 [cppcoreguidelines-init-variables]

variable 'result' is not initialized
ASSERT_TRUE(LDServerLazyLoadRedisSource_New("tcp://localhost:1234", "foo",
&result));
ASSERT_NE(result.source, nullptr);
Expand All @@ -11,7 +21,7 @@
}

TEST(RedisBindings, ErrorMessageIsPropagatedOnFailure) {
LDServerLazyLoadRedisResult result;

Check warning on line 24 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:24:33 [cppcoreguidelines-init-variables]

variable 'result' is not initialized
ASSERT_FALSE(
LDServerLazyLoadRedisSource_New("totally not a URI", "foo", &result));
// Note: this test might begin failing if the Redis++ library ever returns
Expand All @@ -21,8 +31,84 @@
}

TEST(RedisBindings, SourcePointerIsNullptrOnFailure) {
LDServerLazyLoadRedisResult result;

Check warning on line 34 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:34:33 [cppcoreguidelines-init-variables]

variable 'result' is not initialized
ASSERT_FALSE(
LDServerLazyLoadRedisSource_New("totally not a URI", "foo", &result));
ASSERT_EQ(result.source, nullptr);
}

// This is an end-to-end test that uses an actual Redis instance with
// provisioned flag data. The source is passed into the SDK's LazyLoad data
// system, and then AllFlags is used to verify that the data is read back from
// Redis correctly.
TEST(RedisBindings, CanUseInSDKLazyLoadDataSource) {
sw::redis::Redis redis("tcp://localhost:6379");
redis.flushdb();

PrefixedClient client(redis, "testprefix");
Flag flag_a{"foo", 1, false, std::nullopt, {true, false}};
flag_a.offVariation = 0; // variation: true
Flag flag_b{"bar", 1, false, std::nullopt, {true, false}};
flag_b.offVariation = 1; // variation: false

client.PutFlag(flag_a);
client.PutFlag(flag_b);
client.Init();

LDServerLazyLoadRedisResult result;

Check warning on line 58 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:58:33 [cppcoreguidelines-init-variables]

variable 'result' is not initialized
ASSERT_TRUE(LDServerLazyLoadRedisSource_New("tcp://localhost:6379",
"testprefix", &result));

LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123");

LDServerLazyLoadBuilder lazy_builder = LDServerLazyLoadBuilder_New();
LDServerLazyLoadBuilder_SourcePtr(
lazy_builder,
reinterpret_cast<LDServerLazyLoadSourcePtr>(result.source));

Check warning on line 67 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:67:9 [cppcoreguidelines-pro-type-reinterpret-cast]

do not use reinterpret_cast
LDServerConfigBuilder_DataSystem_LazyLoad(cfg_builder, lazy_builder);
LDServerConfigBuilder_Events_Enabled(
cfg_builder, false); // Don't want outbound connection to
// LD in test.

LDServerConfig config;

Check warning on line 73 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:73:20 [cppcoreguidelines-init-variables]

variable 'config' is not initialized
LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config);
ASSERT_TRUE(LDStatus_Ok(status));

LDServerSDK sdk = LDServerSDK_New(config);
LDServerSDK_Start(sdk, LD_NONBLOCKING, nullptr);

LDContextBuilder ctx_builder = LDContextBuilder_New();
LDContextBuilder_AddKind(ctx_builder, "cat", "shadow");
LDContext context = LDContextBuilder_Build(ctx_builder);

LDAllFlagsState state =
LDServerSDK_AllFlagsState(sdk, context, LD_ALLFLAGSSTATE_DEFAULT);

ASSERT_TRUE(LDAllFlagsState_Valid(state));
LDValue all = LDAllFlagsState_Map(state);

ASSERT_EQ(LDValue_Type(all), LDValueType_Object);

std::unordered_map<std::string, launchdarkly::Value> values;

Check warning on line 92 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:92:58 [cppcoreguidelines-init-variables]

variable 'values' is not initialized
LDValue_ObjectIter iter;

Check warning on line 93 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:93:24 [cppcoreguidelines-init-variables]

variable 'iter' is not initialized
for (iter = LDValue_ObjectIter_New(all);
!LDValue_ObjectIter_End(iter); LDValue_ObjectIter_Next(iter)) {
char const* key = LDValue_ObjectIter_Key(iter);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
auto value_ref = reinterpret_cast<launchdarkly::Value const* const>(

Check warning on line 98 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:98:9 [readability-qualified-auto]

'auto value_ref' can be declared as 'const auto *value_ref'
LDValue_ObjectIter_Value(iter));
values.emplace(key, *value_ref);
}

LDValue_ObjectIter_Free(iter);

std::unordered_map<std::string, launchdarkly::Value> expected = {

Check warning on line 105 in libs/server-sdk-redis-source/tests/c_bindings_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/c_bindings_test.cpp:105:58 [cppcoreguidelines-init-variables]

variable 'expected' is not initialized
{"foo", true}, {"bar", false}};
ASSERT_EQ(values, expected);

LDValue_Free(all);
LDAllFlagsState_Free(state);

LDContext_Free(context);
LDServerSDK_Free(sdk);
}
67 changes: 67 additions & 0 deletions libs/server-sdk-redis-source/tests/prefixed_redis_client.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#pragma once

#include <launchdarkly/serialization/json_flag.hpp>
#include <launchdarkly/serialization/json_segment.hpp>

#include <gtest/gtest.h>
#include <redis++.h>

Check failure on line 7 in libs/server-sdk-redis-source/tests/prefixed_redis_client.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/prefixed_redis_client.hpp:7:10 [clang-diagnostic-error]

'redis++.h' file not found
#include <boost/json.hpp>

#include <string>

class PrefixedClient {
public:
PrefixedClient(sw::redis::Redis& client, std::string prefix)
: client_(client), prefix_(std::move(prefix)) {}

void Init() const {
try {
client_.set(Prefixed("$inited"), "true");
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

void PutFlag(launchdarkly::data_model::Flag const& flag) const {
try {
client_.hset(Prefixed("features"), flag.key,
serialize(boost::json::value_from(flag)));
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

void PutDeletedFlag(std::string const& key, std::string const& ts) const {
try {
client_.hset(Prefixed("features"), key, ts);
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

void PutDeletedSegment(std::string const& key,
std::string const& ts) const {
try {
client_.hset(Prefixed("segments"), key, ts);
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

void PutSegment(launchdarkly::data_model::Segment const& segment) const {
try {
client_.hset(Prefixed("segments"), segment.key,
serialize(boost::json::value_from(segment)));
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

private:
std::string Prefixed(std::string const& name) const {
return prefix_ + ":" + name;
}

sw::redis::Redis& client_;
std::string const prefix_;
};
102 changes: 42 additions & 60 deletions libs/server-sdk-redis-source/tests/redis_source_test.cpp
Original file line number Diff line number Diff line change
@@ -1,74 +1,23 @@
#include <gtest/gtest.h>

#include <launchdarkly/server_side/integrations/data_reader/kinds.hpp>
#include <launchdarkly/server_side/integrations/redis/redis_source.hpp>

Check failure on line 4 in libs/server-sdk-redis-source/tests/redis_source_test.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/server-sdk-redis-source/tests/redis_source_test.cpp:4:10 [clang-diagnostic-error]

'launchdarkly/server_side/integrations/redis/redis_source.hpp' file not found

#include <launchdarkly/serialization/json_flag.hpp>
#include <launchdarkly/serialization/json_segment.hpp>
#include <launchdarkly/context_builder.hpp>
#include <launchdarkly/server_side/client.hpp>
#include <launchdarkly/server_side/config/config_builder.hpp>

#include <boost/json.hpp>
#include "prefixed_redis_client.hpp"

#include <redis++.h>

using namespace launchdarkly::server_side::integrations;
using namespace launchdarkly::data_model;

class PrefixedClient {
public:
PrefixedClient(sw::redis::Redis& client, std::string const& prefix)
: client_(client), prefix_(prefix) {}

void Init() const {
try {
client_.set(Prefixed("$inited"), "true");
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

void PutFlag(Flag const& flag) const {
try {
client_.hset(Prefixed("features"), flag.key,
serialize(boost::json::value_from(flag)));
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

void PutDeletedFlag(std::string const& key, std::string const& ts) const {
try {
client_.hset(Prefixed("features"), key, ts);
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

void PutDeletedSegment(std::string const& key,
std::string const& ts) const {
try {
client_.hset(Prefixed("segments"), key, ts);
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}

void PutSegment(Segment const& segment) const {
try {
client_.hset(Prefixed("segments"), segment.key,
serialize(boost::json::value_from(segment)));
} catch (sw::redis::Error const& e) {
FAIL() << e.what();
}
}
#include <boost/json.hpp>

private:
std::string Prefixed(std::string const& name) const {
return prefix_ + ":" + name;
}
#include <unordered_map>

sw::redis::Redis& client_;
std::string const& prefix_;
};
using namespace launchdarkly::server_side::integrations;
using namespace launchdarkly::data_model;
using namespace launchdarkly::server_side;

class RedisTests : public ::testing::Test {
public:
Expand Down Expand Up @@ -412,6 +361,39 @@
std::shared_ptr<ISerializedDataReader> reader = std::move(*maybe_source);
}

TEST_F(RedisTests, CanUseAsSDKLazyLoadDataSource) {
Flag flag_a{"foo", 1, false, std::nullopt, {true, false}};
flag_a.offVariation = 0; // variation: true
Flag flag_b{"bar", 1, false, std::nullopt, {true, false}};
flag_b.offVariation = 1; // variation: false

PutFlag(flag_a);
PutFlag(flag_b);
Init();

auto cfg_builder = ConfigBuilder("sdk-123");
cfg_builder.DataSystem().Method(
config::builders::LazyLoadBuilder().Source(source));
cfg_builder.Events()
.Disable(); // Don't want outbound calls to LD in the test
auto config = cfg_builder.Build();

ASSERT_TRUE(config);

auto client = Client(std::move(*config));
client.StartAsync();

auto const context =
launchdarkly::ContextBuilder().Kind("cat", "shadow").Build();

auto const all_flags = client.AllFlagsState(context);
auto const expected = std::unordered_map<std::string, launchdarkly::Value>{
{"foo", true}, {"bar", false}};

ASSERT_TRUE(all_flags.Valid());
ASSERT_EQ(all_flags.Values(), expected);
}

TEST(RedisErrorTests, InvalidURIs) {
std::vector<std::string> const uris = {"nope, not a redis URI",
"http://foo",
Expand Down
Loading
Loading