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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <launchdarkly/attribute_reference.hpp>
#include <launchdarkly/data_model/context_kind.hpp>

#include <string>

namespace launchdarkly::data_model {
Expand Down Expand Up @@ -46,16 +48,18 @@ struct ContextAwareReference<
std::is_same<char const* const,
decltype(FieldNames::kReferenceFieldName)>::value>::type> {
using fields = FieldNames;
std::string contextKind;
ContextKind contextKind;
AttributeReference reference;
};

// NOLINTBEGIN cppcoreguidelines-macro-usage
#define DEFINE_CONTEXT_KIND_FIELD(name) \
std::string name; \
ContextKind name; \
constexpr static const char* kContextFieldName = #name;

#define DEFINE_ATTRIBUTE_REFERENCE_FIELD(name) \
AttributeReference name; \
constexpr static const char* kReferenceFieldName = #name;
// NOLINTEND cppcoreguidelines-macro-usage

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

#include <boost/serialization/strong_typedef.hpp>

#include <string>

namespace launchdarkly::data_model {

BOOST_STRONG_TYPEDEF(std::string, ContextKind);
Copy link
Contributor Author

@cwaldren-ld cwaldren-ld Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nice that this exists, but it doesn't support move construction. I'll not worry about that until it proves worrisome (at which point we could roll our own macro easily.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose I could have used this in place of my tag types for flags/segments.


} // namespace launchdarkly::data_model
3 changes: 1 addition & 2 deletions libs/internal/include/launchdarkly/data_model/flag.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

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

Expand All @@ -15,8 +16,6 @@
namespace launchdarkly::data_model {

struct Flag {
using ContextKind = std::string;

struct Rollout {
enum class Kind {
kUnrecognized = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <launchdarkly/attribute_reference.hpp>
#include <launchdarkly/data_model/context_aware_reference.hpp>
#include <launchdarkly/serialization/json_context_kind.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

Expand All @@ -25,15 +26,10 @@ tl::expected<data_model::ContextAwareReference<Fields>, JsonError> tag_invoke(

auto const& obj = json_value.as_object();

std::optional<std::string> kind;
std::optional<data_model::ContextKind> kind;

PARSE_CONDITIONAL_FIELD(kind, obj, Type::fields::kContextFieldName);

if (kind && *kind == "") {
// Empty string is not a valid kind.
return tl::make_unexpected(JsonError::kSchemaFailure);
}

std::string attr_ref_or_name;
PARSE_FIELD_DEFAULT(attr_ref_or_name, obj,
Type::fields::kReferenceFieldName, "key");
Expand All @@ -43,7 +39,8 @@ tl::expected<data_model::ContextAwareReference<Fields>, JsonError> tag_invoke(
AttributeReference::FromReferenceStr(attr_ref_or_name)};
}

return Type{"user", AttributeReference::FromLiteralStr(attr_ref_or_name)};
return Type{data_model::ContextKind("user"),
AttributeReference::FromLiteralStr(attr_ref_or_name)};
}

} // namespace launchdarkly
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <launchdarkly/data_model/context_kind.hpp>
#include <launchdarkly/serialization/json_errors.hpp>

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

#include <optional>

namespace launchdarkly {

tl::expected<std::optional<data_model::ContextKind>, JsonError> tag_invoke(
boost::json::value_to_tag<
tl::expected<std::optional<data_model::ContextKind>, JsonError>> const&
unused,
boost::json::value const& json_value);

} // namespace launchdarkly
1 change: 1 addition & 0 deletions libs/internal/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ add_library(${LIBNAME} OBJECT
serialization/json_primitives.cpp
serialization/json_rule_clause.cpp
serialization/json_flag.cpp
serialization/json_context_kind.cpp
encoding/base_64.cpp
encoding/sha_256.cpp
signals/boost_signal_connection.cpp)
Expand Down
24 changes: 24 additions & 0 deletions libs/internal/src/serialization/json_context_kind.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <launchdarkly/serialization/json_context_kind.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

#include <boost/core/ignore_unused.hpp>

namespace launchdarkly {
tl::expected<std::optional<data_model::ContextKind>, JsonError> tag_invoke(
boost::json::value_to_tag<
tl::expected<std::optional<data_model::ContextKind>, JsonError>> const&
unused,
boost::json::value const& json_value) {
boost::ignore_unused(unused);

REQUIRE_STRING(json_value);
auto const& str = json_value.as_string();

if (str.empty()) {
/* Empty string is not a valid context kind. */
return tl::make_unexpected(JsonError::kSchemaFailure);
}

return data_model::ContextKind(str.c_str());
}
} // namespace launchdarkly
4 changes: 3 additions & 1 deletion libs/internal/src/serialization/json_flag.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <boost/json.hpp>
#include <launchdarkly/serialization/json_context_aware_reference.hpp>
#include <launchdarkly/serialization/json_context_kind.hpp>
#include <launchdarkly/serialization/json_flag.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/json_rule_clause.hpp>
Expand Down Expand Up @@ -100,7 +101,8 @@ tl::expected<std::optional<data_model::Flag::Target>, JsonError> tag_invoke(
data_model::Flag::Target target;
PARSE_FIELD(target.values, obj, "values");
PARSE_FIELD(target.variation, obj, "variation");
PARSE_FIELD_DEFAULT(target.contextKind, obj, "contextKind", "user");
PARSE_FIELD_DEFAULT(target.contextKind, obj, "contextKind",
data_model::ContextKind("user"));
return target;
}

Expand Down
31 changes: 23 additions & 8 deletions libs/internal/tests/data_model_serialization_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <launchdarkly/serialization/json_segment.hpp>

using namespace launchdarkly;
using launchdarkly::data_model::ContextKind;

TEST(SDKDataSetTests, DeserializesEmptyDataSet) {
auto result =
Expand Down Expand Up @@ -87,7 +88,7 @@ TEST(SegmentRuleTests, DeserializesSimpleAttributeReference) {
tl::expected<data_model::Segment::Rule, JsonError>>(boost::json::parse(
R"({"rolloutContextKind" : "foo", "bucketBy" : "bar", "clauses": []})"));
ASSERT_TRUE(result);
ASSERT_EQ(result->rolloutContextKind, "foo");
ASSERT_EQ(result->rolloutContextKind, ContextKind("foo"));
ASSERT_EQ(result->bucketBy, AttributeReference("bar"));
}

Expand All @@ -96,7 +97,7 @@ TEST(SegmentRuleTests, DeserializesPointerAttributeReference) {
tl::expected<data_model::Segment::Rule, JsonError>>(boost::json::parse(
R"({"rolloutContextKind" : "foo", "bucketBy" : "/foo/bar", "clauses": []})"));
ASSERT_TRUE(result);
ASSERT_EQ(result->rolloutContextKind, "foo");
ASSERT_EQ(result->rolloutContextKind, ContextKind("foo"));
ASSERT_EQ(result->bucketBy, AttributeReference("/foo/bar"));
}

Expand All @@ -105,10 +106,17 @@ TEST(SegmentRuleTests, DeserializesEscapedReference) {
tl::expected<data_model::Segment::Rule, JsonError>>(boost::json::parse(
R"({"rolloutContextKind" : "foo", "bucketBy" : "/~1foo~1bar", "clauses": []})"));
ASSERT_TRUE(result);
ASSERT_EQ(result->rolloutContextKind, "foo");
ASSERT_EQ(result->rolloutContextKind, ContextKind("foo"));
ASSERT_EQ(result->bucketBy, AttributeReference("/~1foo~1bar"));
}

TEST(SegmentRuleTests, RejectsEmptyContextKind) {
auto result = boost::json::value_to<
tl::expected<data_model::Segment::Rule, JsonError>>(boost::json::parse(
R"({"rolloutContextKind" : "", "bucketBy" : "/~1foo~1bar", "clauses": []})"));
ASSERT_FALSE(result);
}

TEST(SegmentRuleTests, DeserializesLiteralAttributeName) {
auto result = boost::json::value_to<
tl::expected<data_model::Segment::Rule, JsonError>>(
Expand Down Expand Up @@ -176,6 +184,13 @@ TEST(ClauseTests, DeserializesEscapedReference) {
ASSERT_EQ(result->attribute, AttributeReference("/~1foo~1bar"));
}

TEST(ClauseTests, RejectsEmptyContextKind) {
auto result = boost::json::value_to<
tl::expected<data_model::Clause, JsonError>>(boost::json::parse(
R"({"attribute": "/~1foo~1bar", "op": "in", "values": ["a"], "contextKind" : ""})"));
ASSERT_FALSE(result);
}

TEST(ClauseTests, DeserializesLiteralAttributeName) {
auto result =
boost::json::value_to<tl::expected<data_model::Clause, JsonError>>(
Expand All @@ -192,7 +207,7 @@ TEST(RolloutTests, DeserializesMinimumValid) {
boost::json::parse(R"({})"));
ASSERT_TRUE(result);
ASSERT_EQ(result->kind, data_model::Flag::Rollout::Kind::kRollout);
ASSERT_EQ(result->contextKind, "user");
ASSERT_EQ(result->contextKind, ContextKind("user"));
ASSERT_EQ(result->bucketBy, "key");
}

Expand All @@ -202,7 +217,7 @@ TEST(RolloutTests, DeserializesAllFieldsWithAttributeReference) {
R"({"kind": "experiment", "contextKind": "org", "bucketBy": "/foo/bar", "seed" : 123, "variations" : []})"));
ASSERT_TRUE(result);
ASSERT_EQ(result->kind, data_model::Flag::Rollout::Kind::kExperiment);
ASSERT_EQ(result->contextKind, "org");
ASSERT_EQ(result->contextKind, ContextKind("org"));
ASSERT_EQ(result->bucketBy, "/foo/bar");
ASSERT_EQ(result->seed, 123);
ASSERT_TRUE(result->variations.empty());
Expand All @@ -214,7 +229,7 @@ TEST(RolloutTests, DeserializesAllFieldsWithLiteralAttributeName) {
R"({"kind": "experiment", "bucketBy": "/foo/bar", "seed" : 123, "variations" : []})"));
ASSERT_TRUE(result);
ASSERT_EQ(result->kind, data_model::Flag::Rollout::Kind::kExperiment);
ASSERT_EQ(result->contextKind, "user");
ASSERT_EQ(result->contextKind, ContextKind("user"));
ASSERT_EQ(result->bucketBy, "/~1foo~1bar");
ASSERT_EQ(result->seed, 123);
ASSERT_TRUE(result->variations.empty());
Expand Down Expand Up @@ -278,7 +293,7 @@ TEST(TargetTests, DeserializesMinimumValid) {
tl::expected<data_model::Flag::Target, JsonError>>(
boost::json::parse(R"({})"));
ASSERT_TRUE(result);
ASSERT_EQ(result->contextKind, "user");
ASSERT_EQ(result->contextKind, ContextKind("user"));
ASSERT_EQ(result->variation, 0);
ASSERT_TRUE(result->values.empty());
}
Expand All @@ -295,7 +310,7 @@ TEST(TargetTests, DeserializesAllFields) {
tl::expected<data_model::Flag::Target, JsonError>>(boost::json::parse(
R"({"variation" : 123, "values" : ["a"], "contextKind" : "org"})"));
ASSERT_TRUE(result);
ASSERT_EQ(result->contextKind, "org");
ASSERT_EQ(result->contextKind, ContextKind("org"));
ASSERT_EQ(result->variation, 123);
ASSERT_EQ(result->values.size(), 1);
ASSERT_EQ(result->values[0], "a");
Expand Down
9 changes: 5 additions & 4 deletions libs/server-sdk/tests/dependency_tracker_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ using launchdarkly::server_side::data_store::SegmentDescriptor;
using launchdarkly::AttributeReference;
using launchdarkly::Value;
using launchdarkly::data_model::Clause;
using launchdarkly::data_model::ContextKind;
using launchdarkly::data_model::Flag;
using launchdarkly::data_model::ItemDescriptor;
using launchdarkly::data_model::Segment;
Expand Down Expand Up @@ -197,7 +198,7 @@ TEST(DependencyTrackerTest, UsesSegmentRulesToCalculateDependencies) {

flag_a.rules.push_back(Flag::Rule{std::vector<Clause>{
Clause{Clause::Op::kSegmentMatch, std::vector<Value>{"segmentA"}, false,
"user", AttributeReference()}}});
ContextKind("user"), AttributeReference()}}});

tracker.UpdateDependencies("flagA", FlagDescriptor(flag_a));
tracker.UpdateDependencies("segmentA", SegmentDescriptor(segment_a));
Expand Down Expand Up @@ -231,7 +232,7 @@ TEST(DependencyTrackerTest, TracksSegmentDependencyOfPrerequisite) {

flag_a.rules.push_back(Flag::Rule{std::vector<Clause>{
Clause{Clause::Op::kSegmentMatch, std::vector<Value>{"segmentA"}, false,
"", AttributeReference()}}});
ContextKind(""), AttributeReference()}}});

flag_b.prerequisites.push_back(Flag::Prerequisite{"flagA", 0});

Expand Down Expand Up @@ -270,8 +271,8 @@ TEST(DependencyTrackerTest, HandlesSegmentsDependentOnOtherSegments) {
segment_b.rules.push_back(Segment::Rule{
std::vector<Clause>{Clause{Clause::Op::kSegmentMatch,
std::vector<Value>{"segmentA"}, false,
"user", AttributeReference()}},
std::nullopt, std::nullopt, "", AttributeReference()});
ContextKind("user"), AttributeReference()}},
std::nullopt, std::nullopt, ContextKind(""), AttributeReference()});

tracker.UpdateDependencies("segmentA", SegmentDescriptor(segment_a));
tracker.UpdateDependencies("segmentB", SegmentDescriptor(segment_b));
Expand Down