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
6 changes: 3 additions & 3 deletions .github/workflows/client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ jobs:
# Inform the test harness of test service's port.
test_service_port: ${{ env.TEST_SERVICE_PORT }}
extra_params: '-skip-from ./contract-tests/sdk-contract-tests/test-suppressions.txt'
build-test:
Copy link
Member Author

Choose a reason for hiding this comment

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

I changed the build names to it is easier to scan them in the PR checks.

build-test-client:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/ci
with:
cmake_target: launchdarkly-cpp-client
build-test-mac:
build-test-client-mac:
runs-on: macos-12
steps:
- run: |
Expand All @@ -52,7 +52,7 @@ jobs:
with:
cmake_target: launchdarkly-cpp-client
platform_version: 12
build-test-windows:
build-test-client-windows:
runs-on: windows-2022
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- '**.md'

jobs:
build-test:
build-test-common:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/internal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- '**.md'

jobs:
build-test:
build-test-internal:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- '**.md'

jobs:
build-test:
build-test-sse:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
Expand Down
5 changes: 3 additions & 2 deletions libs/internal/include/launchdarkly/data_model/flag.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ struct Flag {
Weight weight;
bool untracked;

WeightedVariation() = default;
WeightedVariation();

WeightedVariation(Variation index, Weight weight);
static WeightedVariation Untracked(Variation index, Weight weight);

Expand All @@ -48,7 +49,7 @@ struct Flag {
DEFINE_CONTEXT_KIND_FIELD(contextKind)

Rollout() = default;
Rollout(std::vector<WeightedVariation>);
explicit Rollout(std::vector<WeightedVariation>);
Copy link
Member Author

Choose a reason for hiding this comment

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

To prevent infinite template recursion.

};

using VariationOrRollout = std::variant<Variation, Rollout>;
Expand Down
42 changes: 42 additions & 0 deletions libs/internal/include/launchdarkly/serialization/json_flag.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,46 @@ tl::expected<std::optional<data_model::Flag>, JsonError> tag_invoke(
tl::expected<std::optional<data_model::Flag>, JsonError>> const& unused,
boost::json::value const& json_value);

// Serializers need to be in launchdarkly::data_model for ADL.
namespace data_model {

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Rollout const& rollout);

void tag_invoke(
boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::VariationOrRollout const& variation_or_rollout);

void tag_invoke(
boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Rollout::WeightedVariation const& weighted_variation);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Rollout::Kind const& kind);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Prerequisite const& prerequisite);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Target const& target);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Rule const& rule);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::ClientSideAvailability const& availability);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag const& flag);

} // namespace data_model
} // namespace launchdarkly
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,17 @@ tl::expected<data_model::Clause::Op, JsonError> tag_invoke(
tl::expected<data_model::Clause::Op, JsonError>> const& unused,
boost::json::value const& json_value);

// Serialization needs to be in launchdarkly::data_model for ADL.
namespace data_model {

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Clause const& clause);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Clause::Op const& op);

} // namespace data_model

} // namespace launchdarkly
17 changes: 17 additions & 0 deletions libs/internal/include/launchdarkly/serialization/json_segment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,21 @@ tl::expected<std::optional<data_model::Segment::Rule>, JsonError> tag_invoke(
JsonError>> const& unused,
boost::json::value const& json_value);

// Serializers need to be in launchdarkly::data_model for ADL.
namespace data_model {

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Segment const& segment);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Segment::Target const& target);

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Segment::Rule const& rule);

} // namespace data_model

} // namespace launchdarkly
14 changes: 14 additions & 0 deletions libs/internal/include/launchdarkly/serialization/value_mapping.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,18 @@ template <>
std::string ValueOrDefault(boost::json::object::const_iterator iterator,
boost::json::object::const_iterator end,
std::string default_value);

template <typename T>
void WriteMinimal(boost::json::object& obj,
std::string const& key, // No copy when not used.
std::optional<T> val) {
if (val.has_value()) {
obj.emplace(key, val.value());
}
}

void WriteMinimal(boost::json::object& obj,
std::string const& key, // No copy when not used.
bool val);

} // namespace launchdarkly
2 changes: 2 additions & 0 deletions libs/internal/src/data_model/flag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Flag::Rollout::WeightedVariation Flag::Rollout::WeightedVariation::Untracked(
Flag::Weight weight_) {
return {variation_, weight_, true};
}
Flag::Rollout::WeightedVariation::WeightedVariation()
: variation(0), weight(0), untracked(false) {}

Flag::Rollout::Rollout(std::vector<WeightedVariation> variations_)
: variations(std::move(variations_)),
Expand Down
154 changes: 154 additions & 0 deletions libs/internal/src/serialization/json_flag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,158 @@ tag_invoke(boost::json::value_to_tag<
return std::make_optional(variation);
}

namespace data_model {
void tag_invoke(
boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Rollout::WeightedVariation const& weighted_variation) {
auto& obj = json_value.emplace_object();
obj.emplace("variation", weighted_variation.variation);
obj.emplace("weight", weighted_variation.weight);

WriteMinimal(obj, "untracked", weighted_variation.untracked);
}

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Rollout::Kind const& kind) {
switch (kind) {
case Flag::Rollout::Kind::kUnrecognized:
// TODO: Should we be preserving the original string.
Copy link
Member Author

Choose a reason for hiding this comment

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

I imagine all strongly typed SDKs have this problem. So I will check some others. I am not super happy with it. (Though generally I don't like having to serialize, but it doesn't seem like there is any real alternative.)

break;
case Flag::Rollout::Kind::kExperiment:
json_value.emplace_string() = "experiment";
break;
case Flag::Rollout::Kind::kRollout:
json_value.emplace_string() = "rollout";
break;
}
}

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Rollout const& rollout) {
auto& obj = json_value.emplace_object();

obj.emplace("variations", boost::json::value_from(rollout.variations));
if (rollout.kind != Flag::Rollout::Kind::kUnrecognized) {
// TODO: Should we be preserving the original string and putting it in.
obj.emplace("kind", boost::json::value_from(rollout.kind));
}
WriteMinimal(obj, "seed", rollout.seed);
obj.emplace("bucketBy", rollout.bucketBy.RedactionName());
obj.emplace("contextKind", rollout.contextKind.t);
}

void tag_invoke(
boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::VariationOrRollout const& variation_or_rollout) {
auto& obj = json_value.emplace_object();
std::visit(
[&obj](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, data_model::Flag::Rollout>) {
obj.emplace("rollout", boost::json::value_from(arg));
} else if constexpr (std::is_same_v<T,
data_model::Flag::Variation>) {
obj.emplace("variation", arg);
}
},
variation_or_rollout);
}

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Prerequisite const& prerequisite) {
auto& obj = json_value.emplace_object();
obj.emplace("key", prerequisite.key);
obj.emplace("variation", prerequisite.variation);
}

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Target const& target) {
auto& obj = json_value.emplace_object();
obj.emplace("values", boost::json::value_from(target.values));
obj.emplace("variation", target.variation);
obj.emplace("contextKind", target.contextKind.t);
}

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::ClientSideAvailability const& availability) {
auto& obj = json_value.emplace_object();
WriteMinimal(obj, "usingEnvironmentId", availability.usingEnvironmentId);
WriteMinimal(obj, "usingMobileKey", availability.usingMobileKey);
}

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag::Rule const& rule) {
auto& obj = json_value.emplace_object();
WriteMinimal(obj, "trackEvents", rule.trackEvents);
WriteMinimal(obj, "id", rule.id);
std::visit(
[&obj](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, data_model::Flag::Rollout>) {
obj.emplace("rollout", boost::json::value_from(arg));
} else if constexpr (std::is_same_v<T,
data_model::Flag::Variation>) {
obj.emplace("variation", arg);
}
},
rule.variationOrRollout);
obj.emplace("clauses", boost::json::value_from(rule.clauses));
}

// The "targets" array in a flag cannot have a contextKind, so this intermediate
// representation allows the flag data model to use Flag::Target, but still
// serialize a user target correctly.
struct UserTarget {
std::vector<std::string> values;
std::uint64_t variation;
UserTarget(data_model::Flag::Target const& target)
: values(target.values), variation(target.variation) {}
};

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
UserTarget const& target) {
auto& obj = json_value.emplace_object();
obj.emplace("values", boost::json::value_from(target.values));
obj.emplace("variation", target.variation);
}

void tag_invoke(boost::json::value_from_tag const& unused,
boost::json::value& json_value,
data_model::Flag const& flag) {
auto& obj = json_value.emplace_object();
WriteMinimal(obj, "trackEvents", flag.trackEvents);
WriteMinimal(obj, "clientSide", flag.clientSide);
WriteMinimal(obj, "on", flag.on);
WriteMinimal(obj, "trackEventsFallthrough", flag.trackEventsFallthrough);
WriteMinimal(obj, "debugEventsUntilDate", flag.debugEventsUntilDate);
WriteMinimal(obj, "salt", flag.salt);
WriteMinimal(obj, "offVariation", flag.offVariation);
obj.emplace("key", flag.key);
obj.emplace("version", flag.version);
obj.emplace("variations", boost::json::value_from(flag.variations));
obj.emplace("rules", boost::json::value_from(flag.rules));
obj.emplace("prerequisites", boost::json::value_from(flag.prerequisites));
obj.emplace("fallthrough", boost::json::value_from(flag.fallthrough));
obj.emplace("clientSideAvailability",
boost::json::value_from(flag.clientSideAvailability));
obj.emplace("contextTargets", boost::json::value_from(flag.contextTargets));

std::vector<UserTarget> user_targets;
for (auto const& target : flag.targets) {
user_targets.emplace_back(target);
}
obj.emplace("targets", boost::json::value_from(user_targets));
}

} // namespace data_model

} // namespace launchdarkly
Loading