Skip to content

Commit 15199f1

Browse files
feat: Implement flag manager. (#20)
Co-authored-by: Casey Waldren <[email protected]> fix build on mac (#22)
1 parent e5eaf22 commit 15199f1

File tree

16 files changed

+1321
-17
lines changed

16 files changed

+1321
-17
lines changed

CONTRIBUTING.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ class BarBaz;
3434
DoTheFoo();
3535
Bar();
3636
```
37+
38+
**Methods**
39+
```c++
40+
struct Thing {
41+
uint64_t Size(); // Getter
42+
void DoTheFoo();
43+
};
44+
45+
```
46+
3747
**Constants**
3848
```c++
3949
const int kFooBarBaz = 1;
@@ -58,6 +68,19 @@ In C++:
5868
file_name.cpp
5969
file_name.hpp
6070
```
71+
72+
**Divergence: getters**
73+
74+
```c++
75+
struct Thing {
76+
// Good
77+
uint64_t Size();
78+
79+
//Bad
80+
uint64_t size();
81+
};
82+
```
83+
6184
### C++ Standard
6285

6386
C++17.

libs/client-sdk/include/launchdarkly/client_side/data_source_update_sink.hpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#pragma once
22

3-
#include <map>
3+
#include <optional>
4+
#include <ostream>
45
#include <string>
6+
#include <unordered_map>
57

68
#include "config/detail/service_endpoints.hpp"
79
#include "context.hpp"
@@ -43,8 +45,8 @@ struct ItemDescriptor {
4345
*/
4446
class IDataSourceUpdateSink {
4547
public:
46-
virtual void init(std::map<std::string, ItemDescriptor> data) = 0;
47-
virtual void upsert(std::string key, ItemDescriptor) = 0;
48+
virtual void Init(std::unordered_map<std::string, ItemDescriptor> data) = 0;
49+
virtual void Upsert(std::string key, ItemDescriptor item) = 0;
4850

4951
// We could add this if we want to support data source status.
5052
// virtual void status(<something>)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#pragma once
2+
3+
#include <boost/signals2.hpp>
4+
#include "value.hpp"
5+
6+
namespace launchdarkly::client_side::flag_manager::detail {
7+
/**
8+
* A notification, for the consumer of the SDK, that a flag value has
9+
* changed.
10+
*/
11+
class FlagValueChangeEvent {
12+
public:
13+
/**
14+
* The name of the flag that changed.
15+
* @return The name of the flag.
16+
*/
17+
[[nodiscard]] std::string const& FlagName() const;
18+
19+
/**
20+
* Get the new value. If there was not an new value, because the flag was
21+
* deleted, then the Value will be of a null type. Check the Deleted method
22+
* to see if a flag was deleted.
23+
*
24+
* @return The new value.
25+
*/
26+
[[nodiscard]] Value const& NewValue() const;
27+
28+
/**
29+
* Get the old value. If there was not an old value, for instance a newly
30+
* created flag, then the Value will be of a null type.
31+
*
32+
* @return The new value.
33+
*/
34+
[[nodiscard]] Value const& OldValue() const;
35+
36+
/**
37+
* Will be true if the flag was deleted. In which case the NewValue will
38+
* be of a null type.
39+
*
40+
* @return True if the flag was deleted.
41+
*/
42+
[[nodiscard]] bool Deleted() const;
43+
44+
/**
45+
* Construct a flag changed event with a new and old value.
46+
*
47+
* This is a change event for a flag that has not been Deleted.
48+
*
49+
* @param new_value The new value.
50+
* @param old_value The old value.
51+
*/
52+
FlagValueChangeEvent(std::string name, Value new_value, Value old_value);
53+
FlagValueChangeEvent(std::string name, Value old_value);
54+
55+
private:
56+
Value new_value_;
57+
Value old_value_;
58+
bool deleted_;
59+
std::string flag_name_;
60+
};
61+
62+
} // namespace launchdarkly::client_side::flag_manager::detail
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
#include <mutex>
4+
#include <string>
5+
#include <unordered_map>
6+
7+
#include "launchdarkly/client_side/data_source_update_sink.hpp"
8+
9+
namespace launchdarkly::client_side::flag_manager::detail {
10+
11+
class FlagManager {
12+
public:
13+
void Init(std::unordered_map<std::string, ItemDescriptor> const& data);
14+
void Upsert(std::string const& key, ItemDescriptor item);
15+
16+
/**
17+
* Attempts to get a flag by key from the current flags.
18+
*
19+
* @param flag_key The flag to get.
20+
* @return A shared_ptr to the value if present. A null shared_ptr if the
21+
* item is not present.
22+
*/
23+
std::shared_ptr<ItemDescriptor> Get(std::string const& flag_key) const;
24+
25+
/**
26+
* Gets all the current flags.
27+
*
28+
* @return All of the current flags.
29+
*/
30+
std::unordered_map<std::string, std::shared_ptr<ItemDescriptor>> GetAll()
31+
const;
32+
33+
private:
34+
std::unordered_map<std::string, std::shared_ptr<ItemDescriptor>> data_;
35+
mutable std::mutex data_mutex_;
36+
};
37+
38+
} // namespace launchdarkly::client_side::flag_manager::detail
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#pragma once
2+
3+
#include <boost/signals2.hpp>
4+
5+
#include "launchdarkly/client_side/flag_manager/detail/flag_change_event.hpp"
6+
#include "value.hpp"
7+
8+
namespace launchdarkly::client_side::flag_manager::detail {
9+
10+
/**
11+
* Represents the connection of a listener to a IFlagNotifier.
12+
* Disconnecting the connection will cause the listener to stop receiving
13+
* events.
14+
*/
15+
class IConnection {
16+
public:
17+
virtual void Disconnect() = 0;
18+
19+
virtual ~IConnection() = default;
20+
IConnection(IConnection const& item) = delete;
21+
IConnection(IConnection&& item) = delete;
22+
IConnection& operator=(IConnection const&) = delete;
23+
IConnection& operator=(IConnection&&) = delete;
24+
25+
protected:
26+
IConnection() = default;
27+
};
28+
29+
/**
30+
* Interface to allow listening for flag changes. Notification events should
31+
* be distributed after the store has been updated. Meaning that the "new"
32+
* value will correspond to the current value in the store, and the "old" value
33+
* will be what the value was before the update.
34+
*/
35+
class IFlagNotifier {
36+
public:
37+
// The FlagValueChangeEvent is in a shared pointer so that all handlers
38+
// can use the same instance, and the lifetime is tied to how the consumer
39+
// uses the event.
40+
using ChangeHandler =
41+
std::function<void(std::shared_ptr<FlagValueChangeEvent>)>;
42+
43+
/**
44+
* Listen for changes for the specific flag.
45+
* @param key The flag to listen to changes for.
46+
* @param signal The handler for the changes.
47+
* @return A connection which can be used to stop listening.
48+
*/
49+
virtual std::unique_ptr<IConnection> OnFlagChange(
50+
std::string const& key,
51+
ChangeHandler handler) = 0;
52+
53+
virtual ~IFlagNotifier() = default;
54+
IFlagNotifier(IFlagNotifier const& item) = delete;
55+
IFlagNotifier(IFlagNotifier&& item) = delete;
56+
IFlagNotifier& operator=(IFlagNotifier const&) = delete;
57+
IFlagNotifier& operator=(IFlagNotifier&&) = delete;
58+
59+
protected:
60+
IFlagNotifier() = default;
61+
};
62+
63+
} // namespace launchdarkly::client_side::flag_manager::detail
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#pragma once
2+
3+
#include <memory>
4+
#include <mutex>
5+
#include <string>
6+
#include <unordered_map>
7+
8+
#include "launchdarkly/client_side/data_source_update_sink.hpp"
9+
#include "launchdarkly/client_side/flag_manager/detail/flag_change_event.hpp"
10+
#include "launchdarkly/client_side/flag_manager/detail/flag_manager.hpp"
11+
#include "launchdarkly/client_side/flag_manager/detail/flag_notifier.hpp"
12+
13+
namespace launchdarkly::client_side::flag_manager::detail {
14+
15+
class FlagUpdater : public IDataSourceUpdateSink, public IFlagNotifier {
16+
public:
17+
FlagUpdater(FlagManager& flag_manager);
18+
void Init(std::unordered_map<std::string, ItemDescriptor> data) override;
19+
void Upsert(std::string key, ItemDescriptor item) override;
20+
21+
/**
22+
* Listen for changes for the specific flag.
23+
* @param key The flag to listen to.
24+
* @param handler The handler to signal when the flag changes.
25+
* @return A Connection which can be used to stop listening for changes
26+
* to the flag using this handler.
27+
*/
28+
std::unique_ptr<IConnection> OnFlagChange(
29+
std::string const& key,
30+
std::function<void(std::shared_ptr<FlagValueChangeEvent>)> handler)
31+
override;
32+
33+
private:
34+
class Connection : public IConnection {
35+
public:
36+
friend class FlagUpdater;
37+
Connection(boost::signals2::connection connection);
38+
void Disconnect() override;
39+
40+
private:
41+
boost::signals2::connection connection_;
42+
};
43+
44+
bool HasListeners() const;
45+
46+
FlagManager& flag_manager_;
47+
std::unordered_map<
48+
std::string,
49+
boost::signals2::signal<void(std::shared_ptr<FlagValueChangeEvent>)>>
50+
signals_;
51+
52+
// Recursive mutex so that has_listeners can non-conditionally lock
53+
// the mutex. Otherwise a pre-condition for the call would be holding
54+
// the mutex, which is more difficult to keep consistent over the code
55+
// lifetime.
56+
mutable std::recursive_mutex signal_mutex_;
57+
void DispatchEvent(FlagValueChangeEvent event);
58+
};
59+
60+
} // namespace launchdarkly::client_side::flag_manager::detail

libs/client-sdk/src/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
file(GLOB HEADER_LIST CONFIGURE_DEPENDS "${LaunchDarklyCPPClient_SOURCE_DIR}/include/launchdarkly/*.hpp")
33

44
# Automatic library: static or dynamic based on user config.
5-
add_library(${LIBNAME} api.cpp ${HEADER_LIST} data_sources/streaming_data_source.cpp data_sources/base_64.cpp data_sources/streaming_data_handler.cpp data_source_update_sink.cpp)
5+
add_library(${LIBNAME} api.cpp ${HEADER_LIST} data_sources/streaming_data_source.cpp data_sources/base_64.cpp data_sources/streaming_data_handler.cpp data_source_update_sink.cpp flag_manager/flag_manager.cpp flag_manager/flag_updater.cpp flag_manager/flag_change_event.cpp)
66

77
target_link_libraries(${LIBNAME} launchdarkly::common launchdarkly::sse)
88

libs/client-sdk/src/data_sources/streaming_data_handler.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "serialization/value_mapping.hpp"
66

77
#include <boost/json.hpp>
8+
#include <unordered_map>
89
#include <utility>
910

1011
#include "tl/expected.hpp"
@@ -14,10 +15,11 @@ namespace launchdarkly::client_side {
1415
// ItemDescriptor.
1516

1617
static tl::expected<
17-
std::map<std::string, launchdarkly::client_side::ItemDescriptor>,
18+
std::unordered_map<std::string, launchdarkly::client_side::ItemDescriptor>,
1819
JsonError>
1920
tag_invoke(boost::json::value_to_tag<tl::expected<
20-
std::map<std::string, launchdarkly::client_side::ItemDescriptor>,
21+
std::unordered_map<std::string,
22+
launchdarkly::client_side::ItemDescriptor>,
2123
JsonError>> const& unused,
2224
boost::json::value const& json_value) {
2325
boost::ignore_unused(unused);
@@ -26,7 +28,7 @@ tag_invoke(boost::json::value_to_tag<tl::expected<
2628
return tl::unexpected(JsonError::kSchemaFailure);
2729
}
2830
auto const& obj = json_value.as_object();
29-
std::map<std::string, launchdarkly::client_side::ItemDescriptor>
31+
std::unordered_map<std::string, launchdarkly::client_side::ItemDescriptor>
3032
descriptors;
3133
for (auto const& pair : obj) {
3234
auto eval_result =
@@ -102,12 +104,12 @@ StreamingDataHandler::MessageStatus StreamingDataHandler::handle_message(
102104
LD_LOG(logger_, LogLevel::kError) << "Could not parse PUT message";
103105
return StreamingDataHandler::MessageStatus::kInvalidMessage;
104106
}
105-
auto res = boost::json::value_to<
106-
tl::expected<std::map<std::string, ItemDescriptor>, JsonError>>(
107+
auto res = boost::json::value_to<tl::expected<
108+
std::unordered_map<std::string, ItemDescriptor>, JsonError>>(
107109
parsed);
108110

109111
if (res.has_value()) {
110-
handler_->init(res.value());
112+
handler_->Init(res.value());
111113
return StreamingDataHandler::MessageStatus::kMessageHandled;
112114
}
113115
LD_LOG(logger_, LogLevel::kError)
@@ -125,7 +127,7 @@ StreamingDataHandler::MessageStatus StreamingDataHandler::handle_message(
125127
auto res = boost::json::value_to<
126128
tl::expected<StreamingDataHandler::PatchData, JsonError>>(parsed);
127129
if (res.has_value()) {
128-
handler_->upsert(
130+
handler_->Upsert(
129131
res.value().key,
130132
launchdarkly::client_side::ItemDescriptor(res.value().flag));
131133
return StreamingDataHandler::MessageStatus::kMessageHandled;
@@ -146,7 +148,7 @@ StreamingDataHandler::MessageStatus StreamingDataHandler::handle_message(
146148
tl::expected<StreamingDataHandler::DeleteData, JsonError>>(
147149
boost::json::parse(event.data()));
148150
if (res.has_value()) {
149-
handler_->upsert(res.value().key,
151+
handler_->Upsert(res.value().key,
150152
ItemDescriptor(res.value().version));
151153
return StreamingDataHandler::MessageStatus::kMessageHandled;
152154
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include "launchdarkly/client_side/flag_manager/detail/flag_change_event.hpp"
2+
3+
namespace launchdarkly::client_side::flag_manager::detail {
4+
5+
std::string const& FlagValueChangeEvent::FlagName() const {
6+
return flag_name_;
7+
}
8+
9+
Value const& FlagValueChangeEvent::NewValue() const {
10+
return new_value_;
11+
}
12+
13+
Value const& FlagValueChangeEvent::OldValue() const {
14+
return old_value_;
15+
}
16+
17+
bool FlagValueChangeEvent::Deleted() const {
18+
return deleted_;
19+
}
20+
21+
FlagValueChangeEvent::FlagValueChangeEvent(std::string name,
22+
Value new_value,
23+
Value old_value)
24+
: flag_name_(std::move(name)),
25+
new_value_(std::move(new_value)),
26+
old_value_(std::move(old_value)),
27+
deleted_(false) {}
28+
29+
FlagValueChangeEvent::FlagValueChangeEvent(std::string name, Value old_value)
30+
: flag_name_(std::move(name)),
31+
old_value_(std::move(old_value)),
32+
deleted_(true) {}
33+
34+
} // namespace launchdarkly::client_side::flag_manager::detail

0 commit comments

Comments
 (0)