Skip to content

Commit 2aca898

Browse files
authored
feat: add LDAllFlagsState_Map C binding (#350)
Adds `LDAllFlagsState_Map(state)`, which returns a map of key/value pairs encoded in an object-type `LDValue`. This is in contrast to accessing individual values in the state.
1 parent 34cafc6 commit 2aca898

File tree

9 files changed

+263
-78
lines changed

9 files changed

+263
-78
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ LDClientSDK_JsonVariationDetail(LDClientSDK sdk,
401401
* @code
402402
* LDValue all_flags = LDClientSDK_AllFlags(sdk);
403403
* LDValue_ObjectIter it;
404-
* for (it = LDValue_CreateObjectIter(all_flags);
404+
* for (it = LDValue_ObjectIter_New(all_flags);
405405
* !LDValue_ObjectIter_End(it); LDValue_ObjectIter_Next(it)) { char
406406
* const* flag_key = LDValue_ObjectIter_Key(it); LDValue flag_val_ref =
407407
* LDValue_ObjectIter_Value(it);

libs/common/include/launchdarkly/detail/c_binding_helpers.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#pragma once
2+
13
#include <launchdarkly/bindings/c/status.h>
24
#include <launchdarkly/error.hpp>
35

libs/server-sdk-redis-source/tests/c_bindings_test.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

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

5+
#include <launchdarkly/bindings/c/context_builder.h>
6+
#include <launchdarkly/server_side/bindings/c/config/builder.h>
7+
#include <launchdarkly/server_side/bindings/c/sdk.h>
8+
9+
#include <launchdarkly/data_model/flag.hpp>
10+
11+
#include "prefixed_redis_client.hpp"
12+
13+
using namespace launchdarkly::data_model;
14+
515
TEST(RedisBindings, SourcePointerIsStoredOnSuccessfulCreation) {
616
LDServerLazyLoadRedisResult result;
717
ASSERT_TRUE(LDServerLazyLoadRedisSource_New("tcp://localhost:1234", "foo",
@@ -26,3 +36,79 @@ TEST(RedisBindings, SourcePointerIsNullptrOnFailure) {
2636
LDServerLazyLoadRedisSource_New("totally not a URI", "foo", &result));
2737
ASSERT_EQ(result.source, nullptr);
2838
}
39+
40+
// This is an end-to-end test that uses an actual Redis instance with
41+
// provisioned flag data. The source is passed into the SDK's LazyLoad data
42+
// system, and then AllFlags is used to verify that the data is read back from
43+
// Redis correctly.
44+
TEST(RedisBindings, CanUseInSDKLazyLoadDataSource) {
45+
sw::redis::Redis redis("tcp://localhost:6379");
46+
redis.flushdb();
47+
48+
PrefixedClient client(redis, "testprefix");
49+
Flag flag_a{"foo", 1, false, std::nullopt, {true, false}};
50+
flag_a.offVariation = 0; // variation: true
51+
Flag flag_b{"bar", 1, false, std::nullopt, {true, false}};
52+
flag_b.offVariation = 1; // variation: false
53+
54+
client.PutFlag(flag_a);
55+
client.PutFlag(flag_b);
56+
client.Init();
57+
58+
LDServerLazyLoadRedisResult result;
59+
ASSERT_TRUE(LDServerLazyLoadRedisSource_New("tcp://localhost:6379",
60+
"testprefix", &result));
61+
62+
LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123");
63+
64+
LDServerLazyLoadBuilder lazy_builder = LDServerLazyLoadBuilder_New();
65+
LDServerLazyLoadBuilder_SourcePtr(
66+
lazy_builder,
67+
reinterpret_cast<LDServerLazyLoadSourcePtr>(result.source));
68+
LDServerConfigBuilder_DataSystem_LazyLoad(cfg_builder, lazy_builder);
69+
LDServerConfigBuilder_Events_Enabled(
70+
cfg_builder, false); // Don't want outbound connection to
71+
// LD in test.
72+
73+
LDServerConfig config;
74+
LDStatus status = LDServerConfigBuilder_Build(cfg_builder, &config);
75+
ASSERT_TRUE(LDStatus_Ok(status));
76+
77+
LDServerSDK sdk = LDServerSDK_New(config);
78+
LDServerSDK_Start(sdk, LD_NONBLOCKING, nullptr);
79+
80+
LDContextBuilder ctx_builder = LDContextBuilder_New();
81+
LDContextBuilder_AddKind(ctx_builder, "cat", "shadow");
82+
LDContext context = LDContextBuilder_Build(ctx_builder);
83+
84+
LDAllFlagsState state =
85+
LDServerSDK_AllFlagsState(sdk, context, LD_ALLFLAGSSTATE_DEFAULT);
86+
87+
ASSERT_TRUE(LDAllFlagsState_Valid(state));
88+
LDValue all = LDAllFlagsState_Map(state);
89+
90+
ASSERT_EQ(LDValue_Type(all), LDValueType_Object);
91+
92+
std::unordered_map<std::string, launchdarkly::Value> values;
93+
LDValue_ObjectIter iter;
94+
for (iter = LDValue_ObjectIter_New(all);
95+
!LDValue_ObjectIter_End(iter); LDValue_ObjectIter_Next(iter)) {
96+
char const* key = LDValue_ObjectIter_Key(iter);
97+
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
98+
auto value_ref = reinterpret_cast<launchdarkly::Value const* const>(
99+
LDValue_ObjectIter_Value(iter));
100+
values.emplace(key, *value_ref);
101+
}
102+
103+
LDValue_ObjectIter_Free(iter);
104+
105+
std::unordered_map<std::string, launchdarkly::Value> expected = {
106+
{"foo", true}, {"bar", false}};
107+
ASSERT_EQ(values, expected);
108+
109+
LDValue_Free(all);
110+
LDAllFlagsState_Free(state);
111+
112+
LDContext_Free(context);
113+
LDServerSDK_Free(sdk);
114+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#pragma once
2+
3+
#include <launchdarkly/serialization/json_flag.hpp>
4+
#include <launchdarkly/serialization/json_segment.hpp>
5+
6+
#include <gtest/gtest.h>
7+
#include <redis++.h>
8+
#include <boost/json.hpp>
9+
10+
#include <string>
11+
12+
class PrefixedClient {
13+
public:
14+
PrefixedClient(sw::redis::Redis& client, std::string prefix)
15+
: client_(client), prefix_(std::move(prefix)) {}
16+
17+
void Init() const {
18+
try {
19+
client_.set(Prefixed("$inited"), "true");
20+
} catch (sw::redis::Error const& e) {
21+
FAIL() << e.what();
22+
}
23+
}
24+
25+
void PutFlag(launchdarkly::data_model::Flag const& flag) const {
26+
try {
27+
client_.hset(Prefixed("features"), flag.key,
28+
serialize(boost::json::value_from(flag)));
29+
} catch (sw::redis::Error const& e) {
30+
FAIL() << e.what();
31+
}
32+
}
33+
34+
void PutDeletedFlag(std::string const& key, std::string const& ts) const {
35+
try {
36+
client_.hset(Prefixed("features"), key, ts);
37+
} catch (sw::redis::Error const& e) {
38+
FAIL() << e.what();
39+
}
40+
}
41+
42+
void PutDeletedSegment(std::string const& key,
43+
std::string const& ts) const {
44+
try {
45+
client_.hset(Prefixed("segments"), key, ts);
46+
} catch (sw::redis::Error const& e) {
47+
FAIL() << e.what();
48+
}
49+
}
50+
51+
void PutSegment(launchdarkly::data_model::Segment const& segment) const {
52+
try {
53+
client_.hset(Prefixed("segments"), segment.key,
54+
serialize(boost::json::value_from(segment)));
55+
} catch (sw::redis::Error const& e) {
56+
FAIL() << e.what();
57+
}
58+
}
59+
60+
private:
61+
std::string Prefixed(std::string const& name) const {
62+
return prefix_ + ":" + name;
63+
}
64+
65+
sw::redis::Redis& client_;
66+
std::string const prefix_;
67+
};

libs/server-sdk-redis-source/tests/redis_source_test.cpp

Lines changed: 42 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,72 +3,21 @@
33
#include <launchdarkly/server_side/integrations/data_reader/kinds.hpp>
44
#include <launchdarkly/server_side/integrations/redis/redis_source.hpp>
55

6-
#include <launchdarkly/serialization/json_flag.hpp>
7-
#include <launchdarkly/serialization/json_segment.hpp>
6+
#include <launchdarkly/context_builder.hpp>
7+
#include <launchdarkly/server_side/client.hpp>
8+
#include <launchdarkly/server_side/config/config_builder.hpp>
89

9-
#include <boost/json.hpp>
10+
#include "prefixed_redis_client.hpp"
1011

1112
#include <redis++.h>
1213

13-
using namespace launchdarkly::server_side::integrations;
14-
using namespace launchdarkly::data_model;
15-
16-
class PrefixedClient {
17-
public:
18-
PrefixedClient(sw::redis::Redis& client, std::string const& prefix)
19-
: client_(client), prefix_(prefix) {}
20-
21-
void Init() const {
22-
try {
23-
client_.set(Prefixed("$inited"), "true");
24-
} catch (sw::redis::Error const& e) {
25-
FAIL() << e.what();
26-
}
27-
}
28-
29-
void PutFlag(Flag const& flag) const {
30-
try {
31-
client_.hset(Prefixed("features"), flag.key,
32-
serialize(boost::json::value_from(flag)));
33-
} catch (sw::redis::Error const& e) {
34-
FAIL() << e.what();
35-
}
36-
}
37-
38-
void PutDeletedFlag(std::string const& key, std::string const& ts) const {
39-
try {
40-
client_.hset(Prefixed("features"), key, ts);
41-
} catch (sw::redis::Error const& e) {
42-
FAIL() << e.what();
43-
}
44-
}
45-
46-
void PutDeletedSegment(std::string const& key,
47-
std::string const& ts) const {
48-
try {
49-
client_.hset(Prefixed("segments"), key, ts);
50-
} catch (sw::redis::Error const& e) {
51-
FAIL() << e.what();
52-
}
53-
}
54-
55-
void PutSegment(Segment const& segment) const {
56-
try {
57-
client_.hset(Prefixed("segments"), segment.key,
58-
serialize(boost::json::value_from(segment)));
59-
} catch (sw::redis::Error const& e) {
60-
FAIL() << e.what();
61-
}
62-
}
14+
#include <boost/json.hpp>
6315

64-
private:
65-
std::string Prefixed(std::string const& name) const {
66-
return prefix_ + ":" + name;
67-
}
16+
#include <unordered_map>
6817

69-
sw::redis::Redis& client_;
70-
std::string const& prefix_;
71-
};
18+
using namespace launchdarkly::server_side::integrations;
19+
using namespace launchdarkly::data_model;
20+
using namespace launchdarkly::server_side;
7221

7322
class RedisTests : public ::testing::Test {
7423
public:
@@ -412,6 +361,39 @@ TEST_F(RedisTests, CanConvertRedisDataSourceToDataReader) {
412361
std::shared_ptr<ISerializedDataReader> reader = std::move(*maybe_source);
413362
}
414363

364+
TEST_F(RedisTests, CanUseAsSDKLazyLoadDataSource) {
365+
Flag flag_a{"foo", 1, false, std::nullopt, {true, false}};
366+
flag_a.offVariation = 0; // variation: true
367+
Flag flag_b{"bar", 1, false, std::nullopt, {true, false}};
368+
flag_b.offVariation = 1; // variation: false
369+
370+
PutFlag(flag_a);
371+
PutFlag(flag_b);
372+
Init();
373+
374+
auto cfg_builder = ConfigBuilder("sdk-123");
375+
cfg_builder.DataSystem().Method(
376+
config::builders::LazyLoadBuilder().Source(source));
377+
cfg_builder.Events()
378+
.Disable(); // Don't want outbound calls to LD in the test
379+
auto config = cfg_builder.Build();
380+
381+
ASSERT_TRUE(config);
382+
383+
auto client = Client(std::move(*config));
384+
client.StartAsync();
385+
386+
auto const context =
387+
launchdarkly::ContextBuilder().Kind("cat", "shadow").Build();
388+
389+
auto const all_flags = client.AllFlagsState(context);
390+
auto const expected = std::unordered_map<std::string, launchdarkly::Value>{
391+
{"foo", true}, {"bar", false}};
392+
393+
ASSERT_TRUE(all_flags.Valid());
394+
ASSERT_EQ(all_flags.Values(), expected);
395+
}
396+
415397
TEST(RedisErrorTests, InvalidURIs) {
416398
std::vector<std::string> const uris = {"nope, not a redis URI",
417399
"http://foo",

0 commit comments

Comments
 (0)