From 0c6ba12d240823ca1ec985e3227f8243dbffed19 Mon Sep 17 00:00:00 2001 From: Bee Klimt Date: Tue, 9 Jun 2026 21:50:58 -0700 Subject: [PATCH 1/5] refactor: replace bigSegmentStatus string with EvaluationReason::BigSegmentsStatus enum --- .../launchdarkly/data/evaluation_reason.hpp | 45 +++++++----- libs/common/src/data/evaluation_reason.cpp | 35 ++++++++-- .../serialization/json_evaluation_reason.cpp | 69 +++++++++++++++++-- .../internal/tests/evaluation_reason_test.cpp | 7 +- .../internal/tests/evaluation_result_test.cpp | 10 +-- 5 files changed, 128 insertions(+), 38 deletions(-) diff --git a/libs/common/include/launchdarkly/data/evaluation_reason.hpp b/libs/common/include/launchdarkly/data/evaluation_reason.hpp index 75a6fa282..a91e6db6a 100644 --- a/libs/common/include/launchdarkly/data/evaluation_reason.hpp +++ b/libs/common/include/launchdarkly/data/evaluation_reason.hpp @@ -55,6 +55,25 @@ class EvaluationReason { friend std::ostream& operator<<(std::ostream& out, ErrorKind const& kind); + /** + * Do not change these values. They must remain stable for the C API. + */ + enum class BigSegmentsStatus { + // The query was successful and the segment state is up to date. + kHealthy = 0, + // The query was successful, but the segment state may not be up to + // date. + kStale = 1, + // Big Segments could not be queried because the SDK configuration did + // not include a Big Segment store. + kNotConfigured = 2, + // The query failed, for instance due to a database error. + kStoreError = 3, + }; + + friend std::ostream& operator<<(std::ostream& out, + BigSegmentsStatus const& status); + /** * @return The general category of the reason. */ @@ -66,6 +85,12 @@ class EvaluationReason { */ [[nodiscard]] std::optional ErrorKind() const; + /** + * The validity of the Big Segment information used in this evaluation, or + * std::nullopt if the evaluation did not query any Big Segments. + */ + [[nodiscard]] std::optional BigSegmentsStatus() const; + /** * The index of the matched rule (0 for the first), if the kind was * `"RULE_MATCH"`. @@ -93,29 +118,13 @@ class EvaluationReason { */ [[nodiscard]] bool InExperiment() const; - /** - * Describes the validity of Big Segment information, if and only if the - * flag evaluation required querying at least one Big Segment. - * - * - `"HEALTHY"`: The Big Segment query involved in the flag evaluation was - * successful, and the segment state is considered up to date. - * - `"STALE"`: The Big Segment query involved in the flag evaluation was - * successful, but the segment state may not be up to date - * - `"NOT_CONFIGURED"`: Big Segments could not be queried for the flag - * evaluation because the SDK configuration did not include a Big Segment - * store. - * - `"STORE_ERROR"`: The Big Segment query involved in the flag evaluation - * failed, for instance due to a database error. - */ - [[nodiscard]] std::optional BigSegmentStatus() const; - EvaluationReason(enum Kind kind, std::optional error_kind, std::optional rule_index, std::optional rule_id, std::optional prerequisite_key, bool in_experiment, - std::optional big_segment_status); + std::optional big_segments_status); explicit EvaluationReason(enum ErrorKind error_kind); @@ -165,7 +174,7 @@ class EvaluationReason { std::optional rule_id_; std::optional prerequisite_key_; bool in_experiment_; - std::optional big_segment_status_; + std::optional big_segments_status_; }; bool operator==(EvaluationReason const& lhs, EvaluationReason const& rhs); diff --git a/libs/common/src/data/evaluation_reason.cpp b/libs/common/src/data/evaluation_reason.cpp index 7fb433007..172a1743a 100644 --- a/libs/common/src/data/evaluation_reason.cpp +++ b/libs/common/src/data/evaluation_reason.cpp @@ -27,8 +27,9 @@ bool EvaluationReason::InExperiment() const { return in_experiment_; } -std::optional EvaluationReason::BigSegmentStatus() const { - return big_segment_status_; +std::optional +EvaluationReason::BigSegmentsStatus() const { + return big_segments_status_; } EvaluationReason::EvaluationReason( @@ -38,14 +39,14 @@ EvaluationReason::EvaluationReason( std::optional rule_id, std::optional prerequisite_key, bool in_experiment, - std::optional big_segment_status) + std::optional big_segments_status) : kind_(kind), error_kind_(error_kind), rule_index_(rule_index), rule_id_(std::move(rule_id)), prerequisite_key_(std::move(prerequisite_key)), in_experiment_(in_experiment), - big_segment_status_(std::move(big_segment_status)) {} + big_segments_status_(std::move(big_segments_status)) {} EvaluationReason::EvaluationReason(enum ErrorKind error_kind) : EvaluationReason(Kind::kError, @@ -105,8 +106,8 @@ std::ostream& operator<<(std::ostream& out, EvaluationReason const& reason) { out << " prerequisiteKey: " << reason.prerequisite_key_.value(); } out << " inExperiment: " << reason.in_experiment_; - if (reason.big_segment_status_.has_value()) { - out << " bigSegmentStatus: " << reason.big_segment_status_.value(); + if (reason.big_segments_status_.has_value()) { + out << " bigSegmentsStatus: " << reason.big_segments_status_.value(); } out << "}"; return out; @@ -115,7 +116,7 @@ std::ostream& operator<<(std::ostream& out, EvaluationReason const& reason) { bool operator==(EvaluationReason const& lhs, EvaluationReason const& rhs) { return lhs.Kind() == rhs.Kind() && lhs.ErrorKind() == rhs.ErrorKind() && lhs.InExperiment() == rhs.InExperiment() && - lhs.BigSegmentStatus() == rhs.BigSegmentStatus() && + lhs.BigSegmentsStatus() == rhs.BigSegmentsStatus() && lhs.PrerequisiteKey() == rhs.PrerequisiteKey() && lhs.RuleId() == rhs.RuleId() && lhs.RuleIndex() == rhs.RuleIndex(); } @@ -149,6 +150,26 @@ std::ostream& operator<<(std::ostream& out, return out; } +std::ostream& operator<<( + std::ostream& out, + enum EvaluationReason::BigSegmentsStatus const& status) { + switch (status) { + case EvaluationReason::BigSegmentsStatus::kHealthy: + out << "HEALTHY"; + break; + case EvaluationReason::BigSegmentsStatus::kStale: + out << "STALE"; + break; + case EvaluationReason::BigSegmentsStatus::kNotConfigured: + out << "NOT_CONFIGURED"; + break; + case EvaluationReason::BigSegmentsStatus::kStoreError: + out << "STORE_ERROR"; + break; + } + return out; +} + std::ostream& operator<<(std::ostream& out, enum EvaluationReason::ErrorKind const& kind) { switch (kind) { diff --git a/libs/internal/src/serialization/json_evaluation_reason.cpp b/libs/internal/src/serialization/json_evaluation_reason.cpp index e8173e725..dbf74b2b1 100644 --- a/libs/internal/src/serialization/json_evaluation_reason.cpp +++ b/libs/internal/src/serialization/json_evaluation_reason.cpp @@ -125,6 +125,54 @@ void tag_invoke(boost::json::value_from_tag const& unused, } } +tl::expected tag_invoke( + boost::json::value_to_tag< + tl::expected> const& unused, + boost::json::value const& json_value) { + boost::ignore_unused(unused); + + if (!json_value.is_string()) { + return tl::unexpected(JsonError::kSchemaFailure); + } + auto const& str = json_value.as_string(); + if (str == "HEALTHY") { + return EvaluationReason::BigSegmentsStatus::kHealthy; + } + if (str == "STALE") { + return EvaluationReason::BigSegmentsStatus::kStale; + } + if (str == "NOT_CONFIGURED") { + return EvaluationReason::BigSegmentsStatus::kNotConfigured; + } + if (str == "STORE_ERROR") { + return EvaluationReason::BigSegmentsStatus::kStoreError; + } + return tl::make_unexpected(JsonError::kSchemaFailure); +} + +void tag_invoke(boost::json::value_from_tag const& unused, + boost::json::value& json_value, + enum EvaluationReason::BigSegmentsStatus const& status) { + boost::ignore_unused(unused); + + auto& str = json_value.emplace_string(); + switch (status) { + case EvaluationReason::BigSegmentsStatus::kHealthy: + str = "HEALTHY"; + break; + case EvaluationReason::BigSegmentsStatus::kStale: + str = "STALE"; + break; + case EvaluationReason::BigSegmentsStatus::kNotConfigured: + str = "NOT_CONFIGURED"; + break; + case EvaluationReason::BigSegmentsStatus::kStoreError: + str = "STORE_ERROR"; + break; + } +} + tl::expected tag_invoke( boost::json::value_to_tag> const& unused, @@ -172,9 +220,17 @@ tl::expected tag_invoke( auto in_experiment = ValueOrDefault(in_experiment_iter, json_obj.end(), false); - auto* big_segment_status_iter = json_obj.find("bigSegmentStatus"); - auto big_segment_status = - ValueAsOpt(big_segment_status_iter, json_obj.end()); + auto* big_segments_status_iter = json_obj.find("bigSegmentsStatus"); + std::optional + big_segments_status; + if (big_segments_status_iter != json_obj.end()) { + auto parsed = boost::json::value_to>( + big_segments_status_iter->value()); + if (parsed) { + big_segments_status = *parsed; + } + } return EvaluationReason{*kind, error_kind, @@ -182,7 +238,7 @@ tl::expected tag_invoke( rule_id, prerequisite_key, in_experiment, - big_segment_status}; + big_segments_status}; } return tl::unexpected(JsonError::kSchemaFailure); } @@ -197,8 +253,9 @@ void tag_invoke(boost::json::value_from_tag const& unused, if (auto error_kind = reason.ErrorKind()) { obj.emplace("errorKind", boost::json::value_from(*error_kind)); } - if (auto big_segment_status = reason.BigSegmentStatus()) { - obj.emplace("bigSegmentStatus", *big_segment_status); + if (auto big_segments_status = reason.BigSegmentsStatus()) { + obj.emplace("bigSegmentsStatus", + boost::json::value_from(*big_segments_status)); } if (auto rule_id = reason.RuleId()) { obj.emplace("ruleId", *rule_id); diff --git a/libs/internal/tests/evaluation_reason_test.cpp b/libs/internal/tests/evaluation_reason_test.cpp index 859b7d791..12f03e2e6 100644 --- a/libs/internal/tests/evaluation_reason_test.cpp +++ b/libs/internal/tests/evaluation_reason_test.cpp @@ -18,7 +18,7 @@ TEST(EvaluationReasonsTests, FromJsonAllFields) { "\"ruleId\":\"RULE_ID\"," "\"prerequisiteKey\":\"PREREQ_KEY\"," "\"inExperiment\":true," - "\"bigSegmentStatus\":\"STORE_ERROR\"" + "\"bigSegmentsStatus\":\"STORE_ERROR\"" "}")); EXPECT_EQ(EvaluationReason::Kind::kOff, reason.value().Kind()); @@ -27,7 +27,8 @@ TEST(EvaluationReasonsTests, FromJsonAllFields) { EXPECT_EQ(12, reason.value().RuleIndex()); EXPECT_EQ("RULE_ID", reason.value().RuleId()); EXPECT_EQ("PREREQ_KEY", reason.value().PrerequisiteKey()); - EXPECT_EQ("STORE_ERROR", reason.value().BigSegmentStatus()); + EXPECT_EQ(EvaluationReason::BigSegmentsStatus::kStoreError, + reason.value().BigSegmentsStatus()); EXPECT_TRUE(reason.value().InExperiment()); } @@ -43,7 +44,7 @@ TEST(EvaluationReasonsTests, FromMinimalJson) { EXPECT_EQ(std::nullopt, reason.value().RuleIndex()); EXPECT_EQ(std::nullopt, reason.value().RuleId()); EXPECT_EQ(std::nullopt, reason.value().PrerequisiteKey()); - EXPECT_EQ(std::nullopt, reason.value().BigSegmentStatus()); + EXPECT_EQ(std::nullopt, reason.value().BigSegmentsStatus()); EXPECT_FALSE(reason.value().InExperiment()); } diff --git a/libs/internal/tests/evaluation_result_test.cpp b/libs/internal/tests/evaluation_result_test.cpp index 48956a3af..3d6727295 100644 --- a/libs/internal/tests/evaluation_result_test.cpp +++ b/libs/internal/tests/evaluation_result_test.cpp @@ -32,7 +32,7 @@ TEST(EvaluationResultTests, FromJsonAllFields) { "\"ruleId\":\"RULE_ID\"," "\"prerequisiteKey\":\"PREREQ_KEY\"," "\"inExperiment\":true," - "\"bigSegmentStatus\":\"STORE_ERROR\"" + "\"bigSegmentsStatus\":\"STORE_ERROR\"" "}" "}")); @@ -56,14 +56,16 @@ TEST(EvaluationResultTests, FromJsonAllFields) { EXPECT_EQ(12, val->Detail().Reason()->get().RuleIndex()); EXPECT_EQ("RULE_ID", val->Detail().Reason()->get().RuleId()); EXPECT_EQ("PREREQ_KEY", val->Detail().Reason()->get().PrerequisiteKey()); - EXPECT_EQ("STORE_ERROR", val->Detail().Reason()->get().BigSegmentStatus()); + EXPECT_EQ(EvaluationReason::BigSegmentsStatus::kStoreError, + val->Detail().Reason()->get().BigSegmentsStatus()); EXPECT_TRUE(val->Detail().Reason()->get().InExperiment()); } TEST(EvaluationResultTests, ToJsonAllFields) { EvaluationReason reason(EvaluationReason::Kind::kOff, EvaluationReason::ErrorKind::kMalformedFlag, 12, - "RULE_ID", "PREREQ_KEY", true, "STORE_ERROR"); + "RULE_ID", "PREREQ_KEY", true, + EvaluationReason::BigSegmentsStatus::kStoreError); launchdarkly::EvaluationDetailInternal detail( Value(std::map{{"item", "a"}}), 84, reason); EvaluationResult result(12, 24, true, true, @@ -79,7 +81,7 @@ TEST(EvaluationResultTests, ToJsonAllFields) { "{\"version\":12,\"flagVersion\":24,\"trackEvents\":true," "\"trackReason\":true,\"debugEventsUntilDate\":1680555761,\"value\":{" "\"item\":\"a\"},\"variationIndex\":84,\"reason\":{\"kind\":\"OFF\"," - "\"errorKind\":\"MALFORMED_FLAG\",\"bigSegmentStatus\":\"STORE_ERROR\"," + "\"errorKind\":\"MALFORMED_FLAG\",\"bigSegmentsStatus\":\"STORE_ERROR\"," "\"ruleId\":\"RULE_ID\",\"ruleIndex\":12,\"inExperiment\":true," "\"prerequisiteKey\":\"PREREQ_KEY\"}}", res); From 3fe4d2a76eee47d915b5d421154d24bda7cd70f6 Mon Sep 17 00:00:00 2001 From: Bee Klimt Date: Tue, 9 Jun 2026 22:03:49 -0700 Subject: [PATCH 2/5] fix: surface invalid bigSegmentsStatus as a JSON parse error --- libs/internal/src/serialization/json_evaluation_reason.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/internal/src/serialization/json_evaluation_reason.cpp b/libs/internal/src/serialization/json_evaluation_reason.cpp index dbf74b2b1..93b541008 100644 --- a/libs/internal/src/serialization/json_evaluation_reason.cpp +++ b/libs/internal/src/serialization/json_evaluation_reason.cpp @@ -227,9 +227,10 @@ tl::expected tag_invoke( auto parsed = boost::json::value_to>( big_segments_status_iter->value()); - if (parsed) { - big_segments_status = *parsed; + if (!parsed) { + return tl::make_unexpected(parsed.error()); } + big_segments_status = parsed.value(); } return EvaluationReason{*kind, From 6b6dd8e0ec2f484072f12958dfbb227a5219dd8c Mon Sep 17 00:00:00 2001 From: Bee Klimt Date: Wed, 10 Jun 2026 10:57:29 -0700 Subject: [PATCH 3/5] chore: restore deprecated BigSegmentStatus() accessor as a shim --- .../common/include/launchdarkly/data/evaluation_reason.hpp | 7 +++++++ libs/common/src/data/evaluation_reason.cpp | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/libs/common/include/launchdarkly/data/evaluation_reason.hpp b/libs/common/include/launchdarkly/data/evaluation_reason.hpp index a91e6db6a..0c8f713bc 100644 --- a/libs/common/include/launchdarkly/data/evaluation_reason.hpp +++ b/libs/common/include/launchdarkly/data/evaluation_reason.hpp @@ -91,6 +91,13 @@ class EvaluationReason { */ [[nodiscard]] std::optional BigSegmentsStatus() const; + /** + * Deprecated; use BigSegmentsStatus() instead. Always returns std::nullopt. + */ + [[deprecated("use BigSegmentsStatus()")]] [[nodiscard]] std::optional< + std::string> + BigSegmentStatus() const; + /** * The index of the matched rule (0 for the first), if the kind was * `"RULE_MATCH"`. diff --git a/libs/common/src/data/evaluation_reason.cpp b/libs/common/src/data/evaluation_reason.cpp index 172a1743a..f7d6b04e0 100644 --- a/libs/common/src/data/evaluation_reason.cpp +++ b/libs/common/src/data/evaluation_reason.cpp @@ -32,6 +32,10 @@ EvaluationReason::BigSegmentsStatus() const { return big_segments_status_; } +std::optional EvaluationReason::BigSegmentStatus() const { + return std::nullopt; +} + EvaluationReason::EvaluationReason( enum Kind kind, std::optional error_kind, From b8145ac6c3ae5ce4c0bbef86af29253f209faed1 Mon Sep 17 00:00:00 2001 From: Bee Klimt Date: Wed, 10 Jun 2026 12:19:44 -0700 Subject: [PATCH 4/5] refactor: BigSegmentsStatus uses kNone sentinel; restore deprecated string-typed APIs --- .../launchdarkly/data/evaluation_reason.hpp | 35 +++++-- libs/common/src/data/evaluation_reason.cpp | 91 ++++++++++++++----- .../serialization/json_evaluation_reason.cpp | 12 ++- .../internal/tests/evaluation_reason_test.cpp | 3 +- 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/libs/common/include/launchdarkly/data/evaluation_reason.hpp b/libs/common/include/launchdarkly/data/evaluation_reason.hpp index 0c8f713bc..7c82ba594 100644 --- a/libs/common/include/launchdarkly/data/evaluation_reason.hpp +++ b/libs/common/include/launchdarkly/data/evaluation_reason.hpp @@ -59,16 +59,18 @@ class EvaluationReason { * Do not change these values. They must remain stable for the C API. */ enum class BigSegmentsStatus { + // The evaluation did not query any Big Segments. + kNone = 0, // The query was successful and the segment state is up to date. - kHealthy = 0, + kHealthy = 1, // The query was successful, but the segment state may not be up to // date. - kStale = 1, + kStale = 2, // Big Segments could not be queried because the SDK configuration did // not include a Big Segment store. - kNotConfigured = 2, + kNotConfigured = 3, // The query failed, for instance due to a database error. - kStoreError = 3, + kStoreError = 4, }; friend std::ostream& operator<<(std::ostream& out, @@ -87,12 +89,14 @@ class EvaluationReason { /** * The validity of the Big Segment information used in this evaluation, or - * std::nullopt if the evaluation did not query any Big Segments. + * BigSegmentsStatus::kNone if the evaluation did not query any Big + * Segments. */ - [[nodiscard]] std::optional BigSegmentsStatus() const; + [[nodiscard]] enum BigSegmentsStatus BigSegmentsStatus() const; /** - * Deprecated; use BigSegmentsStatus() instead. Always returns std::nullopt. + * Deprecated; use BigSegmentsStatus() instead. Returns the string passed + * to the deprecated string-typed constructor, or std::nullopt otherwise. */ [[deprecated("use BigSegmentsStatus()")]] [[nodiscard]] std::optional< std::string> @@ -131,7 +135,19 @@ class EvaluationReason { std::optional rule_id, std::optional prerequisite_key, bool in_experiment, - std::optional big_segments_status); + enum BigSegmentsStatus big_segments_status); + + /** + * Deprecated; use the overload taking @ref BigSegmentsStatus instead. + */ + [[deprecated("use the BigSegmentsStatus overload")]] EvaluationReason( + enum Kind kind, + std::optional error_kind, + std::optional rule_index, + std::optional rule_id, + std::optional prerequisite_key, + bool in_experiment, + std::optional big_segment_status); explicit EvaluationReason(enum ErrorKind error_kind); @@ -181,7 +197,8 @@ class EvaluationReason { std::optional rule_id_; std::optional prerequisite_key_; bool in_experiment_; - std::optional big_segments_status_; + enum BigSegmentsStatus big_segments_status_; + std::optional big_segment_status_; }; bool operator==(EvaluationReason const& lhs, EvaluationReason const& rhs); diff --git a/libs/common/src/data/evaluation_reason.cpp b/libs/common/src/data/evaluation_reason.cpp index f7d6b04e0..701b45129 100644 --- a/libs/common/src/data/evaluation_reason.cpp +++ b/libs/common/src/data/evaluation_reason.cpp @@ -27,13 +27,13 @@ bool EvaluationReason::InExperiment() const { return in_experiment_; } -std::optional +enum EvaluationReason::BigSegmentsStatus EvaluationReason::BigSegmentsStatus() const { return big_segments_status_; } std::optional EvaluationReason::BigSegmentStatus() const { - return std::nullopt; + return big_segment_status_; } EvaluationReason::EvaluationReason( @@ -43,51 +43,93 @@ EvaluationReason::EvaluationReason( std::optional rule_id, std::optional prerequisite_key, bool in_experiment, - std::optional big_segments_status) + enum BigSegmentsStatus big_segments_status) : kind_(kind), error_kind_(error_kind), rule_index_(rule_index), rule_id_(std::move(rule_id)), prerequisite_key_(std::move(prerequisite_key)), in_experiment_(in_experiment), - big_segments_status_(std::move(big_segments_status)) {} + big_segments_status_(big_segments_status), + big_segment_status_(std::nullopt) {} + +EvaluationReason::EvaluationReason( + enum Kind kind, + std::optional error_kind, + std::optional rule_index, + std::optional rule_id, + std::optional prerequisite_key, + bool in_experiment, + std::optional big_segment_status) + : kind_(kind), + error_kind_(error_kind), + rule_index_(rule_index), + rule_id_(std::move(rule_id)), + prerequisite_key_(std::move(prerequisite_key)), + in_experiment_(in_experiment), + big_segments_status_(BigSegmentsStatus::kNone), + big_segment_status_(std::move(big_segment_status)) {} EvaluationReason::EvaluationReason(enum ErrorKind error_kind) : EvaluationReason(Kind::kError, error_kind, - std::nullopt, - std::nullopt, - std::nullopt, - false, - std::nullopt) {} + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + /* prerequisite_key= */ std::nullopt, + /* in_experiment= */ false, + BigSegmentsStatus::kNone) {} EvaluationReason EvaluationReason::Off() { - return {Kind::kOff, std::nullopt, std::nullopt, std::nullopt, - std::nullopt, false, std::nullopt}; + return {Kind::kOff, + /* error_kind= */ std::nullopt, + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + /* prerequisite_key= */ std::nullopt, + /* in_experiment= */ false, + BigSegmentsStatus::kNone}; } EvaluationReason EvaluationReason::PrerequisiteFailed( std::string prerequisite_key) { - return { - Kind::kPrerequisiteFailed, std::nullopt, std::nullopt, std::nullopt, - std::move(prerequisite_key), false, std::nullopt}; + return {Kind::kPrerequisiteFailed, + /* error_kind= */ std::nullopt, + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + std::move(prerequisite_key), + /* in_experiment= */ false, + BigSegmentsStatus::kNone}; } EvaluationReason EvaluationReason::TargetMatch() { - return {Kind::kTargetMatch, std::nullopt, std::nullopt, std::nullopt, - std::nullopt, false, std::nullopt}; + return {Kind::kTargetMatch, + /* error_kind= */ std::nullopt, + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + /* prerequisite_key= */ std::nullopt, + /* in_experiment= */ false, + BigSegmentsStatus::kNone}; } EvaluationReason EvaluationReason::Fallthrough(bool in_experiment) { - return {Kind::kFallthrough, std::nullopt, std::nullopt, std::nullopt, - std::nullopt, in_experiment, std::nullopt}; + return {Kind::kFallthrough, + /* error_kind= */ std::nullopt, + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + /* prerequisite_key= */ std::nullopt, + in_experiment, + BigSegmentsStatus::kNone}; } EvaluationReason EvaluationReason::RuleMatch(std::size_t rule_index, std::optional rule_id, bool in_experiment) { - return {Kind::kRuleMatch, std::nullopt, rule_index, std::move(rule_id), - std::nullopt, in_experiment, std::nullopt}; + return {Kind::kRuleMatch, + /* error_kind= */ std::nullopt, + rule_index, + std::move(rule_id), + /* prerequisite_key= */ std::nullopt, + in_experiment, + BigSegmentsStatus::kNone}; } EvaluationReason EvaluationReason::MalformedFlag() { @@ -110,8 +152,9 @@ std::ostream& operator<<(std::ostream& out, EvaluationReason const& reason) { out << " prerequisiteKey: " << reason.prerequisite_key_.value(); } out << " inExperiment: " << reason.in_experiment_; - if (reason.big_segments_status_.has_value()) { - out << " bigSegmentsStatus: " << reason.big_segments_status_.value(); + if (reason.big_segments_status_ != + EvaluationReason::BigSegmentsStatus::kNone) { + out << " bigSegmentsStatus: " << reason.big_segments_status_; } out << "}"; return out; @@ -121,6 +164,7 @@ bool operator==(EvaluationReason const& lhs, EvaluationReason const& rhs) { return lhs.Kind() == rhs.Kind() && lhs.ErrorKind() == rhs.ErrorKind() && lhs.InExperiment() == rhs.InExperiment() && lhs.BigSegmentsStatus() == rhs.BigSegmentsStatus() && + lhs.BigSegmentStatus() == rhs.BigSegmentStatus() && lhs.PrerequisiteKey() == rhs.PrerequisiteKey() && lhs.RuleId() == rhs.RuleId() && lhs.RuleIndex() == rhs.RuleIndex(); } @@ -158,6 +202,9 @@ std::ostream& operator<<( std::ostream& out, enum EvaluationReason::BigSegmentsStatus const& status) { switch (status) { + case EvaluationReason::BigSegmentsStatus::kNone: + out << "NONE"; + break; case EvaluationReason::BigSegmentsStatus::kHealthy: out << "HEALTHY"; break; diff --git a/libs/internal/src/serialization/json_evaluation_reason.cpp b/libs/internal/src/serialization/json_evaluation_reason.cpp index 93b541008..495a55554 100644 --- a/libs/internal/src/serialization/json_evaluation_reason.cpp +++ b/libs/internal/src/serialization/json_evaluation_reason.cpp @@ -158,6 +158,10 @@ void tag_invoke(boost::json::value_from_tag const& unused, auto& str = json_value.emplace_string(); switch (status) { + case EvaluationReason::BigSegmentsStatus::kNone: + // kNone is the "no status" sentinel and shouldn't reach the wire; + // callers guard. If it ever does, emit an empty string. + break; case EvaluationReason::BigSegmentsStatus::kHealthy: str = "HEALTHY"; break; @@ -221,8 +225,7 @@ tl::expected tag_invoke( ValueOrDefault(in_experiment_iter, json_obj.end(), false); auto* big_segments_status_iter = json_obj.find("bigSegmentsStatus"); - std::optional - big_segments_status; + auto big_segments_status = EvaluationReason::BigSegmentsStatus::kNone; if (big_segments_status_iter != json_obj.end()) { auto parsed = boost::json::value_to>( @@ -254,9 +257,10 @@ void tag_invoke(boost::json::value_from_tag const& unused, if (auto error_kind = reason.ErrorKind()) { obj.emplace("errorKind", boost::json::value_from(*error_kind)); } - if (auto big_segments_status = reason.BigSegmentsStatus()) { + auto big_segments_status = reason.BigSegmentsStatus(); + if (big_segments_status != EvaluationReason::BigSegmentsStatus::kNone) { obj.emplace("bigSegmentsStatus", - boost::json::value_from(*big_segments_status)); + boost::json::value_from(big_segments_status)); } if (auto rule_id = reason.RuleId()) { obj.emplace("ruleId", *rule_id); diff --git a/libs/internal/tests/evaluation_reason_test.cpp b/libs/internal/tests/evaluation_reason_test.cpp index 12f03e2e6..680223ef1 100644 --- a/libs/internal/tests/evaluation_reason_test.cpp +++ b/libs/internal/tests/evaluation_reason_test.cpp @@ -44,7 +44,8 @@ TEST(EvaluationReasonsTests, FromMinimalJson) { EXPECT_EQ(std::nullopt, reason.value().RuleIndex()); EXPECT_EQ(std::nullopt, reason.value().RuleId()); EXPECT_EQ(std::nullopt, reason.value().PrerequisiteKey()); - EXPECT_EQ(std::nullopt, reason.value().BigSegmentsStatus()); + EXPECT_EQ(EvaluationReason::BigSegmentsStatus::kNone, + reason.value().BigSegmentsStatus()); EXPECT_FALSE(reason.value().InExperiment()); } From d0e181f5820bc1f506e9bbf940bfcf7cfe7c134d Mon Sep 17 00:00:00 2001 From: Bee Klimt Date: Wed, 10 Jun 2026 12:35:59 -0700 Subject: [PATCH 5/5] chore: migrate in-tree callers off deprecated bigSegmentStatus APIs --- .../launchdarkly/data/evaluation_reason.hpp | 3 +++ libs/common/src/data/evaluation_reason.cpp | 2 +- .../tests/bindings/evaluation_detail_test.cpp | 18 +++++++++++++----- libs/internal/tests/event_summarizer_test.cpp | 16 ++++++++++++---- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/libs/common/include/launchdarkly/data/evaluation_reason.hpp b/libs/common/include/launchdarkly/data/evaluation_reason.hpp index 7c82ba594..28a608c14 100644 --- a/libs/common/include/launchdarkly/data/evaluation_reason.hpp +++ b/libs/common/include/launchdarkly/data/evaluation_reason.hpp @@ -190,6 +190,9 @@ class EvaluationReason { friend std::ostream& operator<<(std::ostream& out, EvaluationReason const& reason); + friend bool operator==(EvaluationReason const& lhs, + EvaluationReason const& rhs); + private: enum Kind kind_; std::optional error_kind_; diff --git a/libs/common/src/data/evaluation_reason.cpp b/libs/common/src/data/evaluation_reason.cpp index 701b45129..a60ab8905 100644 --- a/libs/common/src/data/evaluation_reason.cpp +++ b/libs/common/src/data/evaluation_reason.cpp @@ -164,7 +164,7 @@ bool operator==(EvaluationReason const& lhs, EvaluationReason const& rhs) { return lhs.Kind() == rhs.Kind() && lhs.ErrorKind() == rhs.ErrorKind() && lhs.InExperiment() == rhs.InExperiment() && lhs.BigSegmentsStatus() == rhs.BigSegmentsStatus() && - lhs.BigSegmentStatus() == rhs.BigSegmentStatus() && + lhs.big_segment_status_ == rhs.big_segment_status_ && lhs.PrerequisiteKey() == rhs.PrerequisiteKey() && lhs.RuleId() == rhs.RuleId() && lhs.RuleIndex() == rhs.RuleIndex(); } diff --git a/libs/common/tests/bindings/evaluation_detail_test.cpp b/libs/common/tests/bindings/evaluation_detail_test.cpp index 74dc59438..af15a40bb 100644 --- a/libs/common/tests/bindings/evaluation_detail_test.cpp +++ b/libs/common/tests/bindings/evaluation_detail_test.cpp @@ -22,8 +22,12 @@ TEST(EvaluationDetailBindings, EvaluationReasonError) { TEST(EvaluationDetailBindings, EvaluationReasonFallthrough) { auto reason = EvaluationReason(EvaluationReason::Kind::kFallthrough, - std::nullopt, std::nullopt, std::nullopt, - std::nullopt, true, std::nullopt); + /* error_kind= */ std::nullopt, + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + /* prerequisite_key= */ std::nullopt, + /* in_experiment= */ true, + EvaluationReason::BigSegmentsStatus::kNone); auto ld_reason = reinterpret_cast(&reason); ASSERT_EQ(LDEvalReason_Kind(ld_reason), LD_EVALREASON_FALLTHROUGH); @@ -52,9 +56,13 @@ TEST(EvaluationDetailBindings, EvaluationDetailError) { TEST(EvaluationDetailBindings, EvaluationDetailSuccess) { auto detail = CEvaluationDetail(EvaluationDetail( true, 42, - EvaluationReason(EvaluationReason::Kind::kFallthrough, std::nullopt, - std::nullopt, std::nullopt, std::nullopt, true, - std::nullopt))); + EvaluationReason(EvaluationReason::Kind::kFallthrough, + /* error_kind= */ std::nullopt, + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + /* prerequisite_key= */ std::nullopt, + /* in_experiment= */ true, + EvaluationReason::BigSegmentsStatus::kNone))); auto ld_detail = reinterpret_cast(&detail); diff --git a/libs/internal/tests/event_summarizer_test.cpp b/libs/internal/tests/event_summarizer_test.cpp index 2c685254f..d6791d8cf 100644 --- a/libs/internal/tests/event_summarizer_test.cpp +++ b/libs/internal/tests/event_summarizer_test.cpp @@ -124,8 +124,13 @@ static FeatureEventParams FeatureEventFromParams(EvaluationParams params, params.feature_version, params.feature_variation, launchdarkly::EvaluationReason( - launchdarkly::EvaluationReason::Kind::kFallthrough, std::nullopt, - std::nullopt, std::nullopt, std::nullopt, false, std::nullopt), + launchdarkly::EvaluationReason::Kind::kFallthrough, + /* error_kind= */ std::nullopt, + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + /* prerequisite_key= */ std::nullopt, + /* in_experiment= */ false, + launchdarkly::EvaluationReason::BigSegmentsStatus::kNone), false, std::nullopt, }; @@ -356,8 +361,11 @@ TEST(SummarizerTests, MissingFlagCreatesCounterUsingDefaultValue) { EvaluationReason(EvaluationReason::Kind::kError, EvaluationReason::ErrorKind::kFlagNotFound, - std::nullopt, std::nullopt, std::nullopt, false, - std::nullopt), + /* rule_index= */ std::nullopt, + /* rule_id= */ std::nullopt, + /* prerequisite_key= */ std::nullopt, + /* in_experiment= */ false, + EvaluationReason::BigSegmentsStatus::kNone), false, std::nullopt,