Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
37a89fc
begin adding data model on top of item descriptor changes
cwaldren-ld Jun 13, 2023
c96a77b
refactor location of item descriptor to serialization folder
cwaldren-ld Jun 13, 2023
4497856
add new json primitive tag_invoke implementations
cwaldren-ld Jun 14, 2023
4643a3b
deserialize segment rules and references
cwaldren-ld Jun 21, 2023
0d68e87
more tests
cwaldren-ld Jun 21, 2023
f3cbc85
bunch of refactoring to make detection of omitted/null values clearer
cwaldren-ld Jun 21, 2023
8d12bbe
delete obsolete tag_invokes and replace with generic overload that de…
cwaldren-ld Jun 21, 2023
afa15de
remove unused parsing functions
cwaldren-ld Jun 21, 2023
6a7a37c
re-arranging location of rule clause to dedicated header/impl
cwaldren-ld Jun 22, 2023
c7089e1
remove obsolete tag_invoke for Value
cwaldren-ld Jun 22, 2023
7662a0c
optimizing boost json imports
cwaldren-ld Jun 22, 2023
a11d9ef
add flag deserialization
cwaldren-ld Jun 23, 2023
b227584
add a convenience deserializer for Value
cwaldren-ld Jun 23, 2023
fb792b9
refactoring parse macros to better reflect notion of omitted/present …
cwaldren-ld Jun 26, 2023
d771537
refactor handling of context aware references to dedicated header
cwaldren-ld Jun 27, 2023
84774e7
add macros to keep field names in sync
cwaldren-ld Jun 27, 2023
a0fbc31
more unit tests
cwaldren-ld Jun 27, 2023
df53246
make offVariation a conditional field because it may be omitted
cwaldren-ld Jun 27, 2023
84da161
remove remove_nulls and fix client-side evaluation result deserializa…
cwaldren-ld Jun 30, 2023
99f7ef2
improve segment.hpp import grouping
cwaldren-ld Jun 30, 2023
2db6f96
add a couple blank lines to segment.hpp to improve readability
cwaldren-ld Jun 30, 2023
4df896f
fix doc linebreaks in attribute_reference.hpp
cwaldren-ld Jun 30, 2023
606f3b0
Merge branch 'cw/sc-207649/segment-data-model' into cw/sc-207650/flag…
cwaldren-ld Jun 30, 2023
dc46e2e
massive import reordering/refactor
cwaldren-ld Jun 30, 2023
6a35415
fix imports in value_test.cpp
cwaldren-ld Jun 30, 2023
744fea9
Merge branch 'server-side' into cw/sc-207650/flag-data-model
cwaldren-ld Jun 30, 2023
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
43 changes: 31 additions & 12 deletions contract-tests/sdk-contract-tests/src/client_entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <launchdarkly/context_builder.hpp>
#include <launchdarkly/serialization/json_context.hpp>
#include <launchdarkly/serialization/json_evaluation_reason.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/value.hpp>

Expand Down Expand Up @@ -65,8 +66,12 @@ static void BuildContextFromParams(launchdarkly::ContextBuilder& builder,

if (single.custom) {
for (auto const& [key, value] : *single.custom) {
attrs.Set(key, boost::json::value_to<launchdarkly::Value>(
boost::json::parse(value.dump())));
auto maybe_attr = boost::json::value_to<
tl::expected<launchdarkly::Value, launchdarkly::JsonError>>(
boost::json::parse(value.dump()));
if (maybe_attr) {
attrs.Set(key, *maybe_attr);
}
}
}
}
Expand Down Expand Up @@ -126,9 +131,16 @@ tl::expected<nlohmann::json, std::string> ContextConvert(

tl::expected<nlohmann::json, std::string> ClientEntity::Custom(
CustomEventParams const& params) {
auto data = params.data ? boost::json::value_to<launchdarkly::Value>(
boost::json::parse(params.data->dump()))
: launchdarkly::Value::Null();
auto data =
params.data
? boost::json::value_to<
tl::expected<launchdarkly::Value, launchdarkly::JsonError>>(
boost::json::parse(params.data->dump()))
: launchdarkly::Value::Null();

if (!data) {
return tl::make_unexpected("couldn't parse custom event data");
}

if (params.omitNullData.value_or(false) && !params.metricValue &&
!params.data) {
Expand All @@ -137,11 +149,11 @@ tl::expected<nlohmann::json, std::string> ClientEntity::Custom(
}

if (!params.metricValue) {
client_->Track(params.eventKey, std::move(data));
client_->Track(params.eventKey, std::move(*data));
return nlohmann::json{};
}

client_->Track(params.eventKey, std::move(data), *params.metricValue);
client_->Track(params.eventKey, std::move(*data), *params.metricValue);
return nlohmann::json{};
}

Expand Down Expand Up @@ -204,15 +216,19 @@ tl::expected<nlohmann::json, std::string> ClientEntity::EvaluateDetail(
}
case ValueType::Any:
case ValueType::Unspecified: {
auto fallback = boost::json::value_to<launchdarkly::Value>(
auto maybe_fallback = boost::json::value_to<
tl::expected<launchdarkly::Value, launchdarkly::JsonError>>(
boost::json::parse(defaultVal.dump()));
if (!maybe_fallback) {
return tl::make_unexpected("unable to parse fallback value");
}

/* This switcharoo from nlohmann/json to boost/json to Value, then
* back is because we're using nlohmann/json for the test harness
* protocol, but boost::json in the SDK. We could swap over to
* boost::json entirely here to remove the awkwardness. */

auto detail = client_->JsonVariationDetail(key, fallback);
auto detail = client_->JsonVariationDetail(key, *maybe_fallback);

auto serialized =
boost::json::serialize(boost::json::value_from(*detail));
Expand Down Expand Up @@ -264,15 +280,18 @@ tl::expected<nlohmann::json, std::string> ClientEntity::Evaluate(
}
case ValueType::Any:
case ValueType::Unspecified: {
auto fallback = boost::json::value_to<launchdarkly::Value>(
auto maybe_fallback = boost::json::value_to<
tl::expected<launchdarkly::Value, launchdarkly::JsonError>>(
boost::json::parse(defaultVal.dump()));

if (!maybe_fallback) {
return tl::make_unexpected("unable to parse fallback value");
}
/* This switcharoo from nlohmann/json to boost/json to Value, then
* back is because we're using nlohmann/json for the test harness
* protocol, but boost::json in the SDK. We could swap over to
* boost::json entirely here to remove the awkwardness. */

auto evaluation = client_->JsonVariation(key, fallback);
auto evaluation = client_->JsonVariation(key, *maybe_fallback);

auto serialized =
boost::json::serialize(boost::json::value_from(evaluation));
Expand Down
4 changes: 4 additions & 0 deletions libs/common/src/attribute_reference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ std::string EscapeLiteral(std::string const& literal,
}

AttributeReference::AttributeReference(std::string str, bool literal) {
if (str.empty()) {
valid_ = false;
return;
}
if (literal) {
components_.push_back(str);
// Literal starting with a '/' needs to be converted to an attribute
Expand Down
10 changes: 5 additions & 5 deletions libs/common/tests/value_test.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#include <gtest/gtest.h>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/value.hpp>

#include <boost/json.hpp>

#include <map>
#include <sstream>
#include <string>
#include <vector>

// NOLINTBEGIN cppcoreguidelines-avoid-magic-numbers

using BoostValue = boost::json::value;
Expand Down
10 changes: 5 additions & 5 deletions libs/internal/include/launchdarkly/context_filter.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#pragma once

#include <string>
#include <unordered_set>
#include <vector>
#include <launchdarkly/attribute_reference.hpp>
#include <launchdarkly/context.hpp>

#include <boost/json.hpp>

#include <launchdarkly/attribute_reference.hpp>
#include <launchdarkly/context.hpp>
#include <string>
#include <unordered_set>
#include <vector>

namespace launchdarkly {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include <launchdarkly/attribute_reference.hpp>
#include <string>

namespace launchdarkly::data_model {

/**
* The JSON data conditionally contains Attribute References (which are capable
* of addressing arbitrarily nested attributes in contexts) or Attribute Names,
* which are names of top-level attributes in contexts.
*
* In order to distinguish these two cases, inspection of a context kind field
* is necessary. The presence or absence of that field determines whether the
* data is an Attribute Reference or Attribute Name.
*
* Because this logic is needed in (3) places, it is factored out into this
* type. To use it, call
* boost::json::value_from<tl::expected<ContextAwareReference<T>,
* JsonError>>(json_value), where T is any type that defines the following:
* - kContextFieldName: name of the field containing the context kind
* - kReferenceFieldName: name of the field containing the attribute reference
* or attribute name'
*
* To ensure the field names don't go out of sync with the declared member
* variables, use the two macros defined below.
* @tparam Fields
*/
template <typename T, typename = void>
struct ContextAwareReference {
static_assert(
std::is_same<char const* const,
decltype(T::kContextFieldName)>::value &&
std::is_same<char const* const,
decltype(T::kReferenceFieldName)>::value,
"T must define kContextFieldName and kReferenceFieldName as constexpr "
"static const char*");
};

template <typename FieldNames>
struct ContextAwareReference<
FieldNames,
typename std::enable_if<
std::is_same<char const* const,
decltype(FieldNames::kContextFieldName)>::value &&
std::is_same<char const* const,
decltype(FieldNames::kReferenceFieldName)>::value>::type> {
using fields = FieldNames;
std::string contextKind;
AttributeReference reference;
};

#define DEFINE_CONTEXT_KIND_FIELD(name) \
std::string name; \
constexpr static const char* kContextFieldName = #name;

#define DEFINE_ATTRIBUTE_REFERENCE_FIELD(name) \
AttributeReference name; \
constexpr static const char* kReferenceFieldName = #name;

} // namespace launchdarkly::data_model
87 changes: 87 additions & 0 deletions libs/internal/include/launchdarkly/data_model/flag.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#pragma once

#include <launchdarkly/data_model/context_aware_reference.hpp>
#include <launchdarkly/data_model/rule_clause.hpp>
#include <launchdarkly/value.hpp>

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

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

namespace launchdarkly::data_model {

struct Flag {
using ContextKind = std::string;

struct Rollout {
enum class Kind {
kUnrecognized = 0,
kExperiment = 1,
kRollout = 2,
};

struct WeightedVariation {
std::uint64_t variation;
std::uint64_t weight;
bool untracked;
};

std::vector<WeightedVariation> variations;

Kind kind;
std::optional<std::int64_t> seed;

DEFINE_ATTRIBUTE_REFERENCE_FIELD(bucketBy)
DEFINE_CONTEXT_KIND_FIELD(contextKind)
};

using Variation = std::uint64_t;
using VariationOrRollout = std::variant<Variation, Rollout>;

struct Prerequisite {
std::string key;
std::uint64_t variation;
};

struct Target {
std::vector<std::string> values;
std::uint64_t variation;
ContextKind contextKind;
};

struct Rule {
std::vector<Clause> clauses;
VariationOrRollout variationOrRollout;

bool trackEvents;
std::optional<std::string> id;
};

struct ClientSideAvailability {
bool usingMobileKey;
bool usingEnvironmentId;
};

std::string key;
std::uint64_t version;
bool on;
VariationOrRollout fallthrough;
std::vector<Value> variations;

std::vector<Prerequisite> prerequisites;
std::vector<Target> targets;
std::vector<Target> contextTargets;
std::vector<Rule> rules;
std::optional<std::uint64_t> offVariation;
bool clientSide;
ClientSideAvailability clientSideAvailability;
std::optional<std::string> salt;
bool trackEvents;
bool trackEventsFallthrough;
std::optional<std::uint64_t> debugEventsUntilDate;
};
} // namespace launchdarkly::data_model
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#pragma once

#include <boost/json.hpp>
#include <launchdarkly/serialization/json_errors.hpp>

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

#include <memory>
#include <optional>
#include <ostream>
#include <tl/expected.hpp>
#include <unordered_map>

namespace launchdarkly::data_model {
Expand Down
38 changes: 38 additions & 0 deletions libs/internal/include/launchdarkly/data_model/rule_clause.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include <launchdarkly/attribute_reference.hpp>
#include <launchdarkly/value.hpp>

#include <optional>
#include <string>
#include <vector>

namespace launchdarkly::data_model {
struct Clause {
enum class Op {
kUnrecognized, /* didn't match any known operators */
kIn,
kStartsWith,
kEndsWith,
kMatches,
kContains,
kLessThan,
kLessThanOrEqual,
kGreaterThan,
kGreaterThanOrEqual,
kBefore,
kAfter,
kSemVerEqual,
kSemVerLessThan,
kSemVerGreaterThan,
kSegmentMatch
};

Op op;
std::vector<Value> values;
bool negate;

DEFINE_CONTEXT_KIND_FIELD(contextKind)
DEFINE_ATTRIBUTE_REFERENCE_FIELD(attribute)
};
} // namespace launchdarkly::data_model
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#pragma once

#include <boost/json/value.hpp>
#include <launchdarkly/data_model/item_descriptor.hpp>
#include <launchdarkly/data_model/segment.hpp>
#include <optional>

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

#include <optional>
#include <unordered_map>

namespace launchdarkly::data_model {
Expand Down
Loading