Skip to content

Commit 30378a1

Browse files
authored
feat: generate events for flag prerequisites (#465)
1 parent d73de43 commit 30378a1

File tree

8 files changed

+97
-16
lines changed

8 files changed

+97
-16
lines changed

contract-tests/client-contract-tests/src/main.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ int main(int argc, char* argv[]) {
4646
srv.add_capability("tls:verify-peer");
4747
srv.add_capability("tls:skip-verify-peer");
4848
srv.add_capability("tls:custom-ca");
49-
49+
srv.add_capability("client-prereq-events");
50+
5051
net::signal_set signals{ioc, SIGINT, SIGTERM};
5152

5253
boost::asio::spawn(ioc.get_executor(), [&](auto yield) mutable {

libs/client-sdk/src/client_impl.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,26 @@ EvaluationDetail<T> ClientImpl::VariationInternal(FlagKey const& key,
305305
auto const& flag = *(desc->item);
306306
auto const& detail = flag.Detail();
307307

308+
// The Prerequisites vector represents the evaluated prerequisites of
309+
// this flag. We need to generate events for both this flag and its
310+
// prerequisites (recursively), which is necessary to ensure LaunchDarkly
311+
// analytics functions properly.
312+
//
313+
// We're using JsonVariation because the type of the
314+
// prerequisite is both unknown and irrelevant to emitting the events.
315+
//
316+
// We're passing Value::Null() to match a server-side SDK's behavior when
317+
// evaluating prerequisites.
318+
//
319+
// NOTE: if "hooks" functionality is implemented into this SDK, take care
320+
// that evaluating prerequisites does not trigger hooks. This may require
321+
// refactoring the code below to not use JsonVariation.
322+
if (auto const prereqs = flag.Prerequisites()) {
323+
for (auto const& prereq : *prereqs) {
324+
JsonVariation(prereq, Value::Null());
325+
}
326+
}
327+
308328
if (check_type && default_value.Type() != Value::Type::kNull &&
309329
detail.Value().Type() != default_value.Type()) {
310330
auto error_reason =

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#include "data_source_event_handler.hpp"
22

3+
#include <launchdarkly/detail/serialization/json_primitives.hpp>
34
#include <launchdarkly/encoding/base_64.hpp>
45
#include <launchdarkly/serialization/json_evaluation_result.hpp>
56
#include <launchdarkly/serialization/json_item_descriptor.hpp>
6-
#include <launchdarkly/detail/serialization/json_primitives.hpp>
77
#include <launchdarkly/serialization/value_mapping.hpp>
88

99
#include <boost/core/ignore_unused.hpp>
@@ -12,7 +12,7 @@
1212

1313
#include <utility>
1414

15-
#include "tl/expected.hpp"
15+
#include <tl/expected.hpp>
1616

1717
namespace launchdarkly::client_side::data_sources {
1818

@@ -76,10 +76,10 @@ DataSourceEventHandler::DataSourceEventHandler(
7676
IDataSourceUpdateSink& handler,
7777
Logger const& logger,
7878
DataSourceStatusManager& status_manager)
79-
: context_(context),
80-
handler_(handler),
79+
: handler_(handler),
8180
logger_(logger),
82-
status_manager_(status_manager) {}
81+
status_manager_(status_manager),
82+
context_(context) {}
8383

8484
DataSourceEventHandler::MessageStatus DataSourceEventHandler::HandleMessage(
8585
std::string const& type,

libs/common/include/launchdarkly/data/evaluation_result.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class EvaluationResult {
4848
*/
4949
[[nodiscard]] EvaluationDetailInternal const& Detail() const;
5050

51+
[[nodiscard]] std::optional<std::vector<std::string>> const& Prerequisites()
52+
const;
53+
5154
EvaluationResult(
5255
uint64_t version,
5356
std::optional<uint64_t> flag_version,
@@ -57,6 +60,16 @@ class EvaluationResult {
5760
debug_events_until_date,
5861
EvaluationDetailInternal detail);
5962

63+
EvaluationResult(
64+
uint64_t version,
65+
std::optional<uint64_t> flag_version,
66+
bool track_events,
67+
bool track_reason,
68+
std::optional<std::chrono::time_point<std::chrono::system_clock>>
69+
debug_events_until_date,
70+
EvaluationDetailInternal detail,
71+
std::optional<std::vector<std::string>> prerequisites);
72+
6073
private:
6174
uint64_t version_;
6275
std::optional<uint64_t> flag_version_;
@@ -65,6 +78,7 @@ class EvaluationResult {
6578
std::optional<std::chrono::time_point<std::chrono::system_clock>>
6679
debug_events_until_date_;
6780
EvaluationDetailInternal detail_;
81+
std::optional<std::vector<std::string>> prerequisites_;
6882
};
6983

7084
std::ostream& operator<<(std::ostream& out, EvaluationResult const& result);

libs/common/src/data/evaluation_result.cpp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ EvaluationDetailInternal const& EvaluationResult::Detail() const {
3131
return detail_;
3232
}
3333

34+
std::optional<std::vector<std::string>> const& EvaluationResult::Prerequisites()
35+
const {
36+
return prerequisites_;
37+
}
38+
3439
EvaluationResult::EvaluationResult(
3540
uint64_t version,
3641
std::optional<uint64_t> flag_version,
@@ -39,12 +44,30 @@ EvaluationResult::EvaluationResult(
3944
std::optional<std::chrono::time_point<std::chrono::system_clock>>
4045
debug_events_until_date,
4146
EvaluationDetailInternal detail)
47+
: EvaluationResult(version,
48+
flag_version,
49+
track_events,
50+
track_reason,
51+
debug_events_until_date,
52+
std::move(detail),
53+
{}) {}
54+
55+
EvaluationResult::EvaluationResult(
56+
uint64_t version,
57+
std::optional<uint64_t> flag_version,
58+
bool track_events,
59+
bool track_reason,
60+
std::optional<std::chrono::time_point<std::chrono::system_clock>>
61+
debug_events_until_date,
62+
EvaluationDetailInternal detail,
63+
std::optional<std::vector<std::string>> prerequisites)
4264
: version_(version),
4365
flag_version_(flag_version),
4466
track_events_(track_events),
4567
track_reason_(track_reason),
4668
debug_events_until_date_(debug_events_until_date),
47-
detail_(std::move(detail)) {}
69+
detail_(std::move(detail)),
70+
prerequisites_(std::move(prerequisites)) {}
4871

4972
std::ostream& operator<<(std::ostream& out, EvaluationResult const& result) {
5073
out << "{";
@@ -59,6 +82,14 @@ std::ostream& operator<<(std::ostream& out, EvaluationResult const& result) {
5982
<< std::put_time(std::gmtime(&as_time_t), "%Y-%m-%d %H:%M:%S");
6083
}
6184
out << " detail: " << result.Detail();
85+
if (auto const prerequisites = result.Prerequisites()) {
86+
out << " prerequisites: [";
87+
for (std::size_t i = 0; i < prerequisites->size(); i++) {
88+
out << prerequisites->at(i)
89+
<< (i == prerequisites->size() - 1 ? "" : ", ");
90+
}
91+
out << "]";
92+
}
6293
out << "}";
6394
return out;
6495
}
@@ -69,7 +100,8 @@ bool operator==(EvaluationResult const& lhs, EvaluationResult const& rhs) {
69100
lhs.TrackEvents() == rhs.TrackEvents() &&
70101
lhs.Detail() == rhs.Detail() &&
71102
lhs.DebugEventsUntilDate() == rhs.DebugEventsUntilDate() &&
72-
lhs.FlagVersion() == rhs.FlagVersion();
103+
lhs.FlagVersion() == rhs.FlagVersion() &&
104+
lhs.Prerequisites() == rhs.Prerequisites();
73105
}
74106

75107
bool operator!=(EvaluationResult const& lhs, EvaluationResult const& rhs) {

libs/internal/include/launchdarkly/data_model/sdk_data_set.hpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
#include <launchdarkly/data_model/item_descriptor.hpp>
55
#include <launchdarkly/data_model/segment.hpp>
66

7-
#include <boost/json/value.hpp>
8-
#include <tl/expected.hpp>
9-
10-
#include <optional>
7+
#include <string>
118
#include <unordered_map>
129

1310
namespace launchdarkly::data_model {

libs/internal/include/launchdarkly/serialization/value_mapping.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ template <>
136136
std::optional<uint64_t> ValueAsOpt(boost::json::object::const_iterator iterator,
137137
boost::json::object::const_iterator end);
138138

139+
template <>
140+
std::optional<std::vector<std::string>> ValueAsOpt(
141+
boost::json::object::const_iterator iterator,
142+
boost::json::object::const_iterator end);
143+
139144
template <>
140145
std::optional<std::string> ValueAsOpt(
141146
boost::json::object::const_iterator iterator,

libs/internal/src/serialization/json_evaluation_result.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include <launchdarkly/detail/serialization/json_errors.hpp>
2+
#include <launchdarkly/detail/serialization/json_value.hpp>
23
#include <launchdarkly/serialization/json_evaluation_reason.hpp>
34
#include <launchdarkly/serialization/json_evaluation_result.hpp>
4-
#include <launchdarkly/detail/serialization/json_value.hpp>
55
#include <launchdarkly/serialization/value_mapping.hpp>
66

77
#include <boost/core/ignore_unused.hpp>
@@ -50,6 +50,10 @@ tl::expected<std::optional<EvaluationResult>, JsonError> tag_invoke(
5050
std::chrono::milliseconds{value}};
5151
});
5252

53+
auto* prerequisites_iter = json_obj.find("prerequisites");
54+
auto prerequisites = ValueAsOpt<std::vector<std::string>>(
55+
prerequisites_iter, json_obj.end());
56+
5357
// Evaluation detail is directly de-serialized inline here.
5458
// This is because the shape of the evaluation detail is different
5559
// when deserializing FlagMeta. Primarily `variation` not
@@ -105,7 +109,8 @@ tl::expected<std::optional<EvaluationResult>, JsonError> tag_invoke(
105109
track_events,
106110
track_reason,
107111
debug_events_until_date,
108-
EvaluationDetailInternal(std::move(value), variation, std::nullopt)};
112+
EvaluationDetailInternal(std::move(value), variation, std::nullopt),
113+
prerequisites};
109114
}
110115

111116
void tag_invoke(boost::json::value_from_tag const& unused,
@@ -133,7 +138,14 @@ void tag_invoke(boost::json::value_from_tag const& unused,
133138
"debugEventsUntilDate",
134139
std::chrono::duration_cast<std::chrono::milliseconds>(
135140
evaluation_result.DebugEventsUntilDate()->time_since_epoch())
136-
.count());
141+
.count());
142+
}
143+
144+
if (auto const prerequisites = evaluation_result.Prerequisites()) {
145+
if (!prerequisites->empty()) {
146+
obj.emplace("prerequisites",
147+
boost::json::value_from(prerequisites.value()));
148+
}
137149
}
138150

139151
auto& detail = evaluation_result.Detail();
@@ -149,4 +161,4 @@ void tag_invoke(boost::json::value_from_tag const& unused,
149161
obj.emplace("reason", reason_json);
150162
}
151163
}
152-
} // namespace launchdarkly
164+
} // namespace launchdarkly

0 commit comments

Comments
 (0)