diff --git a/contract-tests/client-contract-tests/src/main.cpp b/contract-tests/client-contract-tests/src/main.cpp index 3c4fc4883..adebae774 100644 --- a/contract-tests/client-contract-tests/src/main.cpp +++ b/contract-tests/client-contract-tests/src/main.cpp @@ -46,7 +46,8 @@ int main(int argc, char* argv[]) { srv.add_capability("tls:verify-peer"); srv.add_capability("tls:skip-verify-peer"); srv.add_capability("tls:custom-ca"); - + srv.add_capability("client-prereq-events"); + net::signal_set signals{ioc, SIGINT, SIGTERM}; boost::asio::spawn(ioc.get_executor(), [&](auto yield) mutable { diff --git a/libs/client-sdk/src/client_impl.cpp b/libs/client-sdk/src/client_impl.cpp index d99bd72a5..a37f5fc78 100644 --- a/libs/client-sdk/src/client_impl.cpp +++ b/libs/client-sdk/src/client_impl.cpp @@ -305,6 +305,26 @@ EvaluationDetail ClientImpl::VariationInternal(FlagKey const& key, auto const& flag = *(desc->item); auto const& detail = flag.Detail(); + // The Prerequisites vector represents the evaluated prerequisites of + // this flag. We need to generate events for both this flag and its + // prerequisites (recursively), which is necessary to ensure LaunchDarkly + // analytics functions properly. + // + // We're using JsonVariation because the type of the + // prerequisite is both unknown and irrelevant to emitting the events. + // + // We're passing Value::Null() to match a server-side SDK's behavior when + // evaluating prerequisites. + // + // NOTE: if "hooks" functionality is implemented into this SDK, take care + // that evaluating prerequisites does not trigger hooks. This may require + // refactoring the code below to not use JsonVariation. + if (auto const prereqs = flag.Prerequisites()) { + for (auto const& prereq : *prereqs) { + JsonVariation(prereq, Value::Null()); + } + } + if (check_type && default_value.Type() != Value::Type::kNull && detail.Value().Type() != default_value.Type()) { auto error_reason = diff --git a/libs/client-sdk/src/data_sources/data_source_event_handler.cpp b/libs/client-sdk/src/data_sources/data_source_event_handler.cpp index 80838cf4d..3280f15dc 100644 --- a/libs/client-sdk/src/data_sources/data_source_event_handler.cpp +++ b/libs/client-sdk/src/data_sources/data_source_event_handler.cpp @@ -1,9 +1,9 @@ #include "data_source_event_handler.hpp" +#include #include #include #include -#include #include #include @@ -12,7 +12,7 @@ #include -#include "tl/expected.hpp" +#include namespace launchdarkly::client_side::data_sources { @@ -76,10 +76,10 @@ DataSourceEventHandler::DataSourceEventHandler( IDataSourceUpdateSink& handler, Logger const& logger, DataSourceStatusManager& status_manager) - : context_(context), - handler_(handler), + : handler_(handler), logger_(logger), - status_manager_(status_manager) {} + status_manager_(status_manager), + context_(context) {} DataSourceEventHandler::MessageStatus DataSourceEventHandler::HandleMessage( std::string const& type, diff --git a/libs/common/include/launchdarkly/data/evaluation_result.hpp b/libs/common/include/launchdarkly/data/evaluation_result.hpp index 52aa598ca..b130f776d 100644 --- a/libs/common/include/launchdarkly/data/evaluation_result.hpp +++ b/libs/common/include/launchdarkly/data/evaluation_result.hpp @@ -48,6 +48,9 @@ class EvaluationResult { */ [[nodiscard]] EvaluationDetailInternal const& Detail() const; + [[nodiscard]] std::optional> const& Prerequisites() + const; + EvaluationResult( uint64_t version, std::optional flag_version, @@ -57,6 +60,16 @@ class EvaluationResult { debug_events_until_date, EvaluationDetailInternal detail); + EvaluationResult( + uint64_t version, + std::optional flag_version, + bool track_events, + bool track_reason, + std::optional> + debug_events_until_date, + EvaluationDetailInternal detail, + std::optional> prerequisites); + private: uint64_t version_; std::optional flag_version_; @@ -65,6 +78,7 @@ class EvaluationResult { std::optional> debug_events_until_date_; EvaluationDetailInternal detail_; + std::optional> prerequisites_; }; std::ostream& operator<<(std::ostream& out, EvaluationResult const& result); diff --git a/libs/common/src/data/evaluation_result.cpp b/libs/common/src/data/evaluation_result.cpp index 298b49b0b..bb013f678 100644 --- a/libs/common/src/data/evaluation_result.cpp +++ b/libs/common/src/data/evaluation_result.cpp @@ -31,6 +31,11 @@ EvaluationDetailInternal const& EvaluationResult::Detail() const { return detail_; } +std::optional> const& EvaluationResult::Prerequisites() + const { + return prerequisites_; +} + EvaluationResult::EvaluationResult( uint64_t version, std::optional flag_version, @@ -39,12 +44,30 @@ EvaluationResult::EvaluationResult( std::optional> debug_events_until_date, EvaluationDetailInternal detail) + : EvaluationResult(version, + flag_version, + track_events, + track_reason, + debug_events_until_date, + std::move(detail), + {}) {} + +EvaluationResult::EvaluationResult( + uint64_t version, + std::optional flag_version, + bool track_events, + bool track_reason, + std::optional> + debug_events_until_date, + EvaluationDetailInternal detail, + std::optional> prerequisites) : version_(version), flag_version_(flag_version), track_events_(track_events), track_reason_(track_reason), debug_events_until_date_(debug_events_until_date), - detail_(std::move(detail)) {} + detail_(std::move(detail)), + prerequisites_(std::move(prerequisites)) {} std::ostream& operator<<(std::ostream& out, EvaluationResult const& result) { out << "{"; @@ -59,6 +82,14 @@ std::ostream& operator<<(std::ostream& out, EvaluationResult const& result) { << std::put_time(std::gmtime(&as_time_t), "%Y-%m-%d %H:%M:%S"); } out << " detail: " << result.Detail(); + if (auto const prerequisites = result.Prerequisites()) { + out << " prerequisites: ["; + for (std::size_t i = 0; i < prerequisites->size(); i++) { + out << prerequisites->at(i) + << (i == prerequisites->size() - 1 ? "" : ", "); + } + out << "]"; + } out << "}"; return out; } @@ -69,7 +100,8 @@ bool operator==(EvaluationResult const& lhs, EvaluationResult const& rhs) { lhs.TrackEvents() == rhs.TrackEvents() && lhs.Detail() == rhs.Detail() && lhs.DebugEventsUntilDate() == rhs.DebugEventsUntilDate() && - lhs.FlagVersion() == rhs.FlagVersion(); + lhs.FlagVersion() == rhs.FlagVersion() && + lhs.Prerequisites() == rhs.Prerequisites(); } bool operator!=(EvaluationResult const& lhs, EvaluationResult const& rhs) { diff --git a/libs/internal/include/launchdarkly/data_model/sdk_data_set.hpp b/libs/internal/include/launchdarkly/data_model/sdk_data_set.hpp index 6fb24a228..0328f80be 100644 --- a/libs/internal/include/launchdarkly/data_model/sdk_data_set.hpp +++ b/libs/internal/include/launchdarkly/data_model/sdk_data_set.hpp @@ -4,10 +4,7 @@ #include #include -#include -#include - -#include +#include #include namespace launchdarkly::data_model { diff --git a/libs/internal/include/launchdarkly/serialization/value_mapping.hpp b/libs/internal/include/launchdarkly/serialization/value_mapping.hpp index 8bc68a5af..69ad964fe 100644 --- a/libs/internal/include/launchdarkly/serialization/value_mapping.hpp +++ b/libs/internal/include/launchdarkly/serialization/value_mapping.hpp @@ -136,6 +136,11 @@ template <> std::optional ValueAsOpt(boost::json::object::const_iterator iterator, boost::json::object::const_iterator end); +template <> +std::optional> ValueAsOpt( + boost::json::object::const_iterator iterator, + boost::json::object::const_iterator end); + template <> std::optional ValueAsOpt( boost::json::object::const_iterator iterator, diff --git a/libs/internal/src/serialization/json_evaluation_result.cpp b/libs/internal/src/serialization/json_evaluation_result.cpp index 72fdff2bd..d1429e5fd 100644 --- a/libs/internal/src/serialization/json_evaluation_result.cpp +++ b/libs/internal/src/serialization/json_evaluation_result.cpp @@ -1,7 +1,7 @@ #include +#include #include #include -#include #include #include @@ -50,6 +50,10 @@ tl::expected, JsonError> tag_invoke( std::chrono::milliseconds{value}}; }); + auto* prerequisites_iter = json_obj.find("prerequisites"); + auto prerequisites = ValueAsOpt>( + prerequisites_iter, json_obj.end()); + // Evaluation detail is directly de-serialized inline here. // This is because the shape of the evaluation detail is different // when deserializing FlagMeta. Primarily `variation` not @@ -105,7 +109,8 @@ tl::expected, JsonError> tag_invoke( track_events, track_reason, debug_events_until_date, - EvaluationDetailInternal(std::move(value), variation, std::nullopt)}; + EvaluationDetailInternal(std::move(value), variation, std::nullopt), + prerequisites}; } void tag_invoke(boost::json::value_from_tag const& unused, @@ -133,7 +138,14 @@ void tag_invoke(boost::json::value_from_tag const& unused, "debugEventsUntilDate", std::chrono::duration_cast( evaluation_result.DebugEventsUntilDate()->time_since_epoch()) - .count()); + .count()); + } + + if (auto const prerequisites = evaluation_result.Prerequisites()) { + if (!prerequisites->empty()) { + obj.emplace("prerequisites", + boost::json::value_from(prerequisites.value())); + } } auto& detail = evaluation_result.Detail(); @@ -149,4 +161,4 @@ void tag_invoke(boost::json::value_from_tag const& unused, obj.emplace("reason", reason_json); } } -} // namespace launchdarkly +} // namespace launchdarkly