Skip to content
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
3 changes: 2 additions & 1 deletion contract-tests/client-contract-tests/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "server.hpp"

Check failure on line 1 in contract-tests/client-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/client-contract-tests/src/main.cpp:1:10 [clang-diagnostic-error]

'server.hpp' file not found

#include <launchdarkly/logging/console_backend.hpp>

Expand All @@ -18,7 +18,7 @@
using launchdarkly::LogLevel;

int main(int argc, char* argv[]) {
launchdarkly::Logger logger{

Check warning on line 21 in contract-tests/client-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/client-contract-tests/src/main.cpp:21:26 [cppcoreguidelines-init-variables]

variable 'logger' is not initialized
std::make_unique<ConsoleBackend>("client-contract-tests")};

std::string const default_port = "8123";
Expand All @@ -31,8 +31,8 @@
try {
net::io_context ioc{1};

auto p = boost::lexical_cast<unsigned short>(port);

Check warning on line 34 in contract-tests/client-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/client-contract-tests/src/main.cpp:34:14 [readability-identifier-length]

variable name 'p' is too short, expected at least 3 characters
server srv(ioc, "0.0.0.0", p, logger);

Check warning on line 35 in contract-tests/client-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/client-contract-tests/src/main.cpp:35:16 [cppcoreguidelines-init-variables]

variable 'srv' is not initialized

srv.add_capability("client-side");
srv.add_capability("mobile");
Expand All @@ -46,7 +46,8 @@
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 {
Expand Down
20 changes: 20 additions & 0 deletions libs/client-sdk/src/client_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@
std::unordered_map<Client::FlagKey, Value> result;
for (auto& [key, descriptor] : flag_manager_.Store().GetAll()) {
if (descriptor->item) {
result.try_emplace(key, descriptor->item->Detail().Value());

Check warning on line 214 in libs/client-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/client-sdk/src/client_impl.cpp:214:37 [bugprone-unchecked-optional-access]

unchecked access to optional value
}
}
return result;
Expand Down Expand Up @@ -246,7 +246,7 @@
}

template <typename T>
EvaluationDetail<T> ClientImpl::VariationInternal(FlagKey const& key,

Check warning on line 249 in libs/client-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/client-sdk/src/client_impl.cpp:249:51 [bugprone-easily-swappable-parameters]

2 adjacent parameters of 'VariationInternal' of convertible types are easily swapped by mistake
Value default_value,
bool check_type,
bool detailed) {
Expand Down Expand Up @@ -302,9 +302,29 @@

LD_ASSERT(desc->item);

auto const& flag = *(desc->item);

Check warning on line 305 in libs/client-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/client-sdk/src/client_impl.cpp:305:25 [bugprone-unchecked-optional-access]

unchecked access to optional value
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 =
Expand Down
10 changes: 5 additions & 5 deletions libs/client-sdk/src/data_sources/data_source_event_handler.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include "data_source_event_handler.hpp"

#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/encoding/base_64.hpp>
#include <launchdarkly/serialization/json_evaluation_result.hpp>
#include <launchdarkly/serialization/json_item_descriptor.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

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

#include <utility>

#include "tl/expected.hpp"
#include <tl/expected.hpp>

namespace launchdarkly::client_side::data_sources {

Expand Down Expand Up @@ -43,7 +43,7 @@
if (result.has_value() && result.value().has_value() &&
key.has_value()) {
return DataSourceEventHandler::PatchData{key.value(),
result.value().value()};

Check warning on line 46 in libs/client-sdk/src/data_sources/data_source_event_handler.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/client-sdk/src/data_sources/data_source_event_handler.cpp:46:54 [bugprone-unchecked-optional-access]

unchecked access to optional value
}
}
return tl::unexpected(JsonError::kSchemaFailure);
Expand Down Expand Up @@ -76,10 +76,10 @@
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,
Expand Down
14 changes: 14 additions & 0 deletions libs/common/include/launchdarkly/data/evaluation_result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class EvaluationResult {
*/
[[nodiscard]] EvaluationDetailInternal const& Detail() const;

[[nodiscard]] std::optional<std::vector<std::string>> const& Prerequisites()
const;

EvaluationResult(
uint64_t version,
std::optional<uint64_t> flag_version,
Expand All @@ -57,6 +60,16 @@ class EvaluationResult {
debug_events_until_date,
EvaluationDetailInternal detail);

EvaluationResult(
uint64_t version,
std::optional<uint64_t> flag_version,
bool track_events,
bool track_reason,
std::optional<std::chrono::time_point<std::chrono::system_clock>>
debug_events_until_date,
EvaluationDetailInternal detail,
std::optional<std::vector<std::string>> prerequisites);

private:
uint64_t version_;
std::optional<uint64_t> flag_version_;
Expand All @@ -65,6 +78,7 @@ class EvaluationResult {
std::optional<std::chrono::time_point<std::chrono::system_clock>>
debug_events_until_date_;
EvaluationDetailInternal detail_;
std::optional<std::vector<std::string>> prerequisites_;
};

std::ostream& operator<<(std::ostream& out, EvaluationResult const& result);
Expand Down
36 changes: 34 additions & 2 deletions libs/common/src/data/evaluation_result.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ EvaluationDetailInternal const& EvaluationResult::Detail() const {
return detail_;
}

std::optional<std::vector<std::string>> const& EvaluationResult::Prerequisites()
const {
return prerequisites_;
}

EvaluationResult::EvaluationResult(
uint64_t version,
std::optional<uint64_t> flag_version,
Expand All @@ -39,12 +44,30 @@ EvaluationResult::EvaluationResult(
std::optional<std::chrono::time_point<std::chrono::system_clock>>
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<uint64_t> flag_version,
bool track_events,
bool track_reason,
std::optional<std::chrono::time_point<std::chrono::system_clock>>
debug_events_until_date,
EvaluationDetailInternal detail,
std::optional<std::vector<std::string>> 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 << "{";
Expand All @@ -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;
}
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
#include <launchdarkly/data_model/item_descriptor.hpp>
#include <launchdarkly/data_model/segment.hpp>

#include <boost/json/value.hpp>
#include <tl/expected.hpp>

#include <optional>
#include <string>
#include <unordered_map>

namespace launchdarkly::data_model {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ template <>
std::optional<uint64_t> ValueAsOpt(boost::json::object::const_iterator iterator,
boost::json::object::const_iterator end);

template <>
std::optional<std::vector<std::string>> ValueAsOpt(
boost::json::object::const_iterator iterator,
boost::json::object::const_iterator end);

template <>
std::optional<std::string> ValueAsOpt(
boost::json::object::const_iterator iterator,
Expand Down
20 changes: 16 additions & 4 deletions libs/internal/src/serialization/json_evaluation_result.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include <launchdarkly/detail/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>
#include <launchdarkly/serialization/json_evaluation_reason.hpp>
#include <launchdarkly/serialization/json_evaluation_result.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

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

auto* prerequisites_iter = json_obj.find("prerequisites");
auto prerequisites = ValueAsOpt<std::vector<std::string>>(
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
Expand Down Expand Up @@ -105,7 +109,8 @@ tl::expected<std::optional<EvaluationResult>, 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,
Expand Down Expand Up @@ -133,7 +138,14 @@ void tag_invoke(boost::json::value_from_tag const& unused,
"debugEventsUntilDate",
std::chrono::duration_cast<std::chrono::milliseconds>(
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();
Expand All @@ -149,4 +161,4 @@ void tag_invoke(boost::json::value_from_tag const& unused,
obj.emplace("reason", reason_json);
}
}
} // namespace launchdarkly
} // namespace launchdarkly
Loading