From 23d1adc2a58153e1a133a13fd74c3060c956b944 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Mon, 11 May 2026 14:20:52 +0000 Subject: [PATCH 1/5] Fix provider exceptions that crash the caller Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 2 + openfeature/client_api.h | 9 +- .../memory_provider/in_memory_provider.cpp | 29 ++-- .../memory_provider/in_memory_provider.h | 11 +- openfeature/noop_provider.cpp | 28 ++-- openfeature/noop_provider.h | 11 +- openfeature/provider.h | 34 +++-- test/e2e/BUILD | 4 +- test/e2e/context_storing_provider.cpp | 10 +- test/e2e/context_storing_provider.h | 32 ++-- .../in_memory_provider_test.cpp | 143 +++++++++++++----- test/mocks/mock_feature_provider.h | 15 +- test/noop_provider_test.cpp | 21 ++- 13 files changed, 230 insertions(+), 119 deletions(-) diff --git a/openfeature/BUILD b/openfeature/BUILD index 9acee9a..c03031a 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -124,6 +124,7 @@ cc_library( ":provider", ":resolution_details", ":value", + "@abseil-cpp//absl/status:statusor", ], ) @@ -165,6 +166,7 @@ cc_library( include_prefix = "openfeature", deps = [ "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", ":evaluation_context", ":metadata", ":resolution_details", diff --git a/openfeature/client_api.h b/openfeature/client_api.h index cb4f338..46d4145 100644 --- a/openfeature/client_api.h +++ b/openfeature/client_api.h @@ -128,7 +128,14 @@ std::unique_ptr ClientAPI::EvaluateFlag( } EvaluationContext merged_context = MergeContexts(ctx); - return provider_call(provider, merged_context); + auto result = provider_call(provider, merged_context); + + if (!result.ok()) { + return std::make_unique( + default_value, Reason::kError, std::nullopt, FlagMetadata(), + ErrorCode::kGeneral, std::string(result.status().message())); + } + return std::move(*result); } } // namespace openfeature diff --git a/openfeature/memory_provider/in_memory_provider.cpp b/openfeature/memory_provider/in_memory_provider.cpp index 30b1885..1640170 100644 --- a/openfeature/memory_provider/in_memory_provider.cpp +++ b/openfeature/memory_provider/in_memory_provider.cpp @@ -50,29 +50,36 @@ void InMemoryProvider::UpdateFlag(std::string key, std::any new_flag) { flags_.insert_or_assign(std::move(key), std::move(new_flag)); } -std::unique_ptr InMemoryProvider::GetBooleanEvaluation( - std::string_view key, bool default_value, const EvaluationContext& ctx) { +absl::StatusOr> +InMemoryProvider::GetBooleanEvaluation(std::string_view key, bool default_value, + const EvaluationContext& ctx) { return Evaluate(key, default_value, ctx); } -std::unique_ptr InMemoryProvider::GetStringEvaluation( - std::string_view key, std::string_view default_value, - const EvaluationContext& ctx) { +absl::StatusOr> +InMemoryProvider::GetStringEvaluation(std::string_view key, + std::string_view default_value, + const EvaluationContext& ctx) { return Evaluate(key, std::string(default_value), ctx); } -std::unique_ptr InMemoryProvider::GetIntegerEvaluation( - std::string_view key, int64_t default_value, const EvaluationContext& ctx) { +absl::StatusOr> +InMemoryProvider::GetIntegerEvaluation(std::string_view key, + int64_t default_value, + const EvaluationContext& ctx) { return Evaluate(key, default_value, ctx); } -std::unique_ptr InMemoryProvider::GetDoubleEvaluation( - std::string_view key, double default_value, const EvaluationContext& ctx) { +absl::StatusOr> +InMemoryProvider::GetDoubleEvaluation(std::string_view key, + double default_value, + const EvaluationContext& ctx) { return Evaluate(key, default_value, ctx); } -std::unique_ptr InMemoryProvider::GetObjectEvaluation( - std::string_view key, Value default_value, const EvaluationContext& ctx) { +absl::StatusOr> +InMemoryProvider::GetObjectEvaluation(std::string_view key, Value default_value, + const EvaluationContext& ctx) { return Evaluate(key, default_value, ctx); } diff --git a/openfeature/memory_provider/in_memory_provider.h b/openfeature/memory_provider/in_memory_provider.h index e89fc7e..374452d 100644 --- a/openfeature/memory_provider/in_memory_provider.h +++ b/openfeature/memory_provider/in_memory_provider.h @@ -9,6 +9,7 @@ #include #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" #include "openfeature/provider.h" @@ -42,23 +43,23 @@ class InMemoryProvider : public FeatureProvider { // will be added to the configuration. void UpdateFlag(std::string key, std::any new_flag); - std::unique_ptr GetBooleanEvaluation( + absl::StatusOr> GetBooleanEvaluation( std::string_view key, bool default_value, const EvaluationContext& ctx) override; - std::unique_ptr GetStringEvaluation( + absl::StatusOr> GetStringEvaluation( std::string_view key, std::string_view default_value, const EvaluationContext& ctx) override; - std::unique_ptr GetIntegerEvaluation( + absl::StatusOr> GetIntegerEvaluation( std::string_view key, int64_t default_value, const EvaluationContext& ctx) override; - std::unique_ptr GetDoubleEvaluation( + absl::StatusOr> GetDoubleEvaluation( std::string_view key, double default_value, const EvaluationContext& ctx) override; - std::unique_ptr GetObjectEvaluation( + absl::StatusOr> GetObjectEvaluation( std::string_view key, Value default_value, const EvaluationContext& ctx) override; diff --git a/openfeature/noop_provider.cpp b/openfeature/noop_provider.cpp index b5b617b..5b067f0 100644 --- a/openfeature/noop_provider.cpp +++ b/openfeature/noop_provider.cpp @@ -4,38 +4,42 @@ namespace openfeature { Metadata NoopProvider::GetMetadata() const { return Metadata{name_}; } -std::unique_ptr NoopProvider::GetBooleanEvaluation( - std::string_view flag, bool default_value, const EvaluationContext& ctx) { +absl::StatusOr> +NoopProvider::GetBooleanEvaluation(std::string_view flag, bool default_value, + const EvaluationContext& ctx) { return std::make_unique( default_value, Reason::kDefault, "default-variant", FlagMetadata(), std::nullopt, ""); } -std::unique_ptr NoopProvider::GetStringEvaluation( - std::string_view flag, std::string_view default_value, - const EvaluationContext& ctx) { +absl::StatusOr> +NoopProvider::GetStringEvaluation(std::string_view flag, + std::string_view default_value, + const EvaluationContext& ctx) { return std::make_unique( std::string(default_value), Reason::kDefault, "default-variant", FlagMetadata(), std::nullopt, ""); } -std::unique_ptr NoopProvider::GetIntegerEvaluation( - std::string_view flag, int64_t default_value, - const EvaluationContext& ctx) { +absl::StatusOr> +NoopProvider::GetIntegerEvaluation(std::string_view flag, int64_t default_value, + const EvaluationContext& ctx) { return std::make_unique( default_value, Reason::kDefault, "default-variant", FlagMetadata(), std::nullopt, ""); } -std::unique_ptr NoopProvider::GetDoubleEvaluation( - std::string_view flag, double default_value, const EvaluationContext& ctx) { +absl::StatusOr> +NoopProvider::GetDoubleEvaluation(std::string_view flag, double default_value, + const EvaluationContext& ctx) { return std::make_unique( default_value, Reason::kDefault, "default-variant", FlagMetadata(), std::nullopt, ""); } -std::unique_ptr NoopProvider::GetObjectEvaluation( - std::string_view flag, Value default_value, const EvaluationContext& ctx) { +absl::StatusOr> +NoopProvider::GetObjectEvaluation(std::string_view flag, Value default_value, + const EvaluationContext& ctx) { return std::make_unique( default_value, Reason::kDefault, "default-variant", FlagMetadata(), std::nullopt, ""); diff --git a/openfeature/noop_provider.h b/openfeature/noop_provider.h index 7ccd6f8..a8ebf6b 100644 --- a/openfeature/noop_provider.h +++ b/openfeature/noop_provider.h @@ -5,6 +5,7 @@ #include #include +#include "absl/status/statusor.h" #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" #include "openfeature/provider.h" @@ -23,27 +24,27 @@ class NoopProvider : public FeatureProvider { Metadata GetMetadata() const override; // BooleanEvaluation returns a boolean flag. - std::unique_ptr GetBooleanEvaluation( + absl::StatusOr> GetBooleanEvaluation( std::string_view flag, bool default_value, const EvaluationContext& ctx) override; // StringResolutionDetails returns a string flag. - std::unique_ptr GetStringEvaluation( + absl::StatusOr> GetStringEvaluation( std::string_view flag, std::string_view default_value, const EvaluationContext& ctx) override; // IntResolutionDetails returns an integer flag. - std::unique_ptr GetIntegerEvaluation( + absl::StatusOr> GetIntegerEvaluation( std::string_view flag, int64_t default_value, const EvaluationContext& ctx) override; // DoubleResolutionDetails returns a double flag. - std::unique_ptr GetDoubleEvaluation( + absl::StatusOr> GetDoubleEvaluation( std::string_view flag, double default_value, const EvaluationContext& ctx) override; // ObjectResolutionDetails returns an object flag. - std::unique_ptr GetObjectEvaluation( + absl::StatusOr> GetObjectEvaluation( std::string_view flag, Value default_value, const EvaluationContext& ctx) override; diff --git a/openfeature/provider.h b/openfeature/provider.h index 3324e03..50fcdcb 100644 --- a/openfeature/provider.h +++ b/openfeature/provider.h @@ -5,6 +5,7 @@ #include #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" #include "openfeature/resolution_details.h" @@ -15,26 +16,29 @@ namespace openfeature { // FeatureProvider interface defines a set of functions that can be called // in order to evaluate a flag. This should be implemented by flag management // systems. +// Implementations must NOT throw C++ exceptions. All errors +// must be reported by returning an absl::Status inside the absl::StatusOr. +// Throwing an exception will cause abnormal termination of the application. // https://openfeature.dev/specification/sections/providers#21-feature-provider-interface class FeatureProvider { public: virtual ~FeatureProvider() = default; virtual Metadata GetMetadata() const = 0; - virtual std::unique_ptr GetBooleanEvaluation( - std::string_view flag, bool default_value, - const EvaluationContext& ctx) = 0; - virtual std::unique_ptr GetStringEvaluation( - std::string_view flag, std::string_view default_value, - const EvaluationContext& ctx) = 0; - virtual std::unique_ptr GetIntegerEvaluation( - std::string_view flag, int64_t default_value, - const EvaluationContext& ctx) = 0; - virtual std::unique_ptr GetDoubleEvaluation( - std::string_view flag, double default_value, - const EvaluationContext& ctx) = 0; - virtual std::unique_ptr GetObjectEvaluation( - std::string_view flag, Value default_value, - const EvaluationContext& ctx) = 0; + virtual absl::StatusOr> + GetBooleanEvaluation(std::string_view flag, bool default_value, + const EvaluationContext& ctx) = 0; + virtual absl::StatusOr> + GetStringEvaluation(std::string_view flag, std::string_view default_value, + const EvaluationContext& ctx) = 0; + virtual absl::StatusOr> + GetIntegerEvaluation(std::string_view flag, int64_t default_value, + const EvaluationContext& ctx) = 0; + virtual absl::StatusOr> + GetDoubleEvaluation(std::string_view flag, double default_value, + const EvaluationContext& ctx) = 0; + virtual absl::StatusOr> + GetObjectEvaluation(std::string_view flag, Value default_value, + const EvaluationContext& ctx) = 0; virtual absl::Status Init(const EvaluationContext& ctx) { return absl::OkStatus(); } diff --git a/test/e2e/BUILD b/test/e2e/BUILD index 64b4272..d975437 100644 --- a/test/e2e/BUILD +++ b/test/e2e/BUILD @@ -23,7 +23,9 @@ cc_library( "//openfeature:reason", "//openfeature:value", "//openfeature:resolution_details", - "@googletest//:gtest", + "@googletest//:gtest", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", ], ) diff --git a/test/e2e/context_storing_provider.cpp b/test/e2e/context_storing_provider.cpp index f9e7496..882f59c 100644 --- a/test/e2e/context_storing_provider.cpp +++ b/test/e2e/context_storing_provider.cpp @@ -14,7 +14,7 @@ openfeature::Metadata ContextStoringProvider::GetMetadata() const { return openfeature::Metadata{"ContextStoringProvider"}; } -std::unique_ptr +absl::StatusOr> ContextStoringProvider::GetBooleanEvaluation( std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) { @@ -25,7 +25,7 @@ ContextStoringProvider::GetBooleanEvaluation( openfeature::FlagMetadata{}, std::nullopt, ""); } -std::unique_ptr +absl::StatusOr> ContextStoringProvider::GetStringEvaluation( std::string_view key, std::string_view default_value, const openfeature::EvaluationContext& ctx) { @@ -36,7 +36,7 @@ ContextStoringProvider::GetStringEvaluation( openfeature::FlagMetadata{}, std::nullopt, ""); } -std::unique_ptr +absl::StatusOr> ContextStoringProvider::GetIntegerEvaluation( std::string_view key, int64_t default_value, const openfeature::EvaluationContext& ctx) { @@ -47,7 +47,7 @@ ContextStoringProvider::GetIntegerEvaluation( openfeature::FlagMetadata{}, std::nullopt, ""); } -std::unique_ptr +absl::StatusOr> ContextStoringProvider::GetDoubleEvaluation( std::string_view key, double default_value, const openfeature::EvaluationContext& ctx) { @@ -58,7 +58,7 @@ ContextStoringProvider::GetDoubleEvaluation( openfeature::FlagMetadata{}, std::nullopt, ""); } -std::unique_ptr +absl::StatusOr> ContextStoringProvider::GetObjectEvaluation( std::string_view key, const openfeature::Value default_value, const openfeature::EvaluationContext& ctx) { diff --git a/test/e2e/context_storing_provider.h b/test/e2e/context_storing_provider.h index 4422f41..f75b3bc 100644 --- a/test/e2e/context_storing_provider.h +++ b/test/e2e/context_storing_provider.h @@ -5,6 +5,8 @@ #include #include +#include "absl/status/status.h" +#include "absl/status/statusor.h" #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" #include "openfeature/provider.h" @@ -22,25 +24,25 @@ class ContextStoringProvider : public openfeature::FeatureProvider { openfeature::Metadata GetMetadata() const override; - std::unique_ptr GetBooleanEvaluation( - std::string_view key, bool default_value, - const openfeature::EvaluationContext& ctx) override; + absl::StatusOr> + GetBooleanEvaluation(std::string_view key, bool default_value, + const openfeature::EvaluationContext& ctx) override; - std::unique_ptr GetStringEvaluation( - std::string_view key, std::string_view default_value, - const openfeature::EvaluationContext& ctx) override; + absl::StatusOr> + GetStringEvaluation(std::string_view key, std::string_view default_value, + const openfeature::EvaluationContext& ctx) override; - std::unique_ptr GetIntegerEvaluation( - std::string_view key, int64_t default_value, - const openfeature::EvaluationContext& ctx) override; + absl::StatusOr> + GetIntegerEvaluation(std::string_view key, int64_t default_value, + const openfeature::EvaluationContext& ctx) override; - std::unique_ptr GetDoubleEvaluation( - std::string_view key, double default_value, - const openfeature::EvaluationContext& ctx) override; + absl::StatusOr> + GetDoubleEvaluation(std::string_view key, double default_value, + const openfeature::EvaluationContext& ctx) override; - std::unique_ptr GetObjectEvaluation( - std::string_view key, openfeature::Value default_value, - const openfeature::EvaluationContext& ctx) override; + absl::StatusOr> + GetObjectEvaluation(std::string_view key, openfeature::Value default_value, + const openfeature::EvaluationContext& ctx) override; }; } // namespace openfeature_e2e diff --git a/test/memory_provider/in_memory_provider_test.cpp b/test/memory_provider/in_memory_provider_test.cpp index ed0cf0b..cbdd0c0 100644 --- a/test/memory_provider/in_memory_provider_test.cpp +++ b/test/memory_provider/in_memory_provider_test.cpp @@ -46,8 +46,10 @@ TEST_F(InMemoryProviderTest, EvaluationFailsWhenNotReady) { InMemoryProvider provider({}); // Evaluating without calling Init(). - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("any_flag", false, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; ASSERT_NE(res, nullptr); EXPECT_EQ(res->GetReason(), Reason::kError); @@ -59,24 +61,31 @@ TEST_F(InMemoryProviderTest, InitAndShutdownUpdateStateCorrectly) { InMemoryProvider provider({}); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("any_flag", false, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; // After initialization, the state is Ready, but flag is missing. EXPECT_EQ(res->GetErrorCode(), ErrorCode::kFlagNotFound); EXPECT_TRUE(provider.Shutdown().ok()); - res = provider.GetBooleanEvaluation("any_flag", false, empty_ctx_); + absl::StatusOr> res_or2 = + provider.GetBooleanEvaluation("any_flag", false, empty_ctx_); + ASSERT_TRUE(res_or2.ok()); + const std::unique_ptr& res2 = *res_or2; - EXPECT_EQ(res->GetErrorCode(), ErrorCode::kProviderNotReady); + EXPECT_EQ(res2->GetErrorCode(), ErrorCode::kProviderNotReady); } TEST_F(InMemoryProviderTest, FlagNotFound) { InMemoryProvider provider({}); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("missing", true, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; EXPECT_TRUE(res->GetValue()); EXPECT_EQ(res->GetReason(), Reason::kError); @@ -92,8 +101,10 @@ TEST_F(InMemoryProviderTest, FlagTypeMismatch) { provider.UpdateFlag("str_flag", str_flag); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("str_flag", false, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; EXPECT_FALSE(res->GetValue()); EXPECT_EQ(res->GetReason(), Reason::kError); @@ -106,8 +117,10 @@ TEST_F(InMemoryProviderTest, DisabledFlagReturnsDisabledReason) { CreateFlag({{"on", true}}, "on", nullptr, true)); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("disabled_flag", false, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; EXPECT_FALSE(res->GetValue()); // fallback to default param. EXPECT_EQ(res->GetReason(), Reason::kDisabled); @@ -120,8 +133,10 @@ TEST_F(InMemoryProviderTest, StaticEvaluationSuccess) { CreateFlag({{"on", true}, {"off", false}}, "on")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("static_flag", false, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; EXPECT_TRUE(res->GetValue()); EXPECT_EQ(res->GetReason(), Reason::kStatic); @@ -140,8 +155,10 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorSuccess) { CreateFlag({{"on", true}, {"off", false}}, "on", evaluator)); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("dyn_flag", true, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; EXPECT_FALSE(res->GetValue()); EXPECT_EQ(res->GetReason(), Reason::kTargetingMatch); @@ -159,8 +176,10 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorFailureFallsBackToDefaultVariant) { CreateFlag({{"on", true}, {"off", false}}, "off", evaluator)); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("dyn_flag", true, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; EXPECT_FALSE(res->GetValue()); EXPECT_EQ(res->GetReason(), Reason::kDefault); @@ -175,8 +194,11 @@ TEST_F(InMemoryProviderTest, FallbackFailsIfVariantTypeMismatch) { provider.UpdateFlag("missing_variant_flag", bad_flag); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("missing_variant_flag", true, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; + EXPECT_TRUE(res->GetValue()); EXPECT_EQ(res->GetReason(), Reason::kError); EXPECT_EQ(res->GetErrorCode(), ErrorCode::kParseError); @@ -190,8 +212,10 @@ TEST_F(InMemoryProviderTest, FallbackFailsIfVariantMissing) { provider.UpdateFlag("missing_variant_flag", bad_flag); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetBooleanEvaluation("missing_variant_flag", true, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; EXPECT_TRUE(res->GetValue()); EXPECT_EQ(res->GetReason(), Reason::kError); @@ -206,16 +230,22 @@ TEST_F(InMemoryProviderTest, UpdateFlagReplacesAndAddsNew) { EXPECT_TRUE(provider.Init(empty_ctx_).ok()); provider.UpdateFlag("flag1", CreateFlag({{"on", true}}, "on")); - EXPECT_TRUE( - provider.GetBooleanEvaluation("flag1", false, empty_ctx_)->GetValue()); + absl::StatusOr> res1 = + provider.GetBooleanEvaluation("flag1", false, empty_ctx_); + ASSERT_TRUE(res1.ok()); + EXPECT_TRUE((*res1)->GetValue()); provider.UpdateFlag("flag1", CreateFlag({{"off", false}}, "off")); - EXPECT_FALSE( - provider.GetBooleanEvaluation("flag1", true, empty_ctx_)->GetValue()); + absl::StatusOr> res2 = + provider.GetBooleanEvaluation("flag1", true, empty_ctx_); + ASSERT_TRUE(res2.ok()); + EXPECT_FALSE((*res2)->GetValue()); provider.UpdateFlag("new_flag", CreateFlag({{"added", true}}, "added")); - EXPECT_TRUE( - provider.GetBooleanEvaluation("new_flag", false, empty_ctx_)->GetValue()); + absl::StatusOr> res3 = + provider.GetBooleanEvaluation("new_flag", false, empty_ctx_); + ASSERT_TRUE(res3.ok()); + EXPECT_TRUE((*res3)->GetValue()); } TEST_F(InMemoryProviderTest, UpdateFlagsAddsAndOverwritesExisting) { @@ -225,10 +255,15 @@ TEST_F(InMemoryProviderTest, UpdateFlagsAddsAndOverwritesExisting) { InMemoryProvider provider(std::move(initial)); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - EXPECT_TRUE( - provider.GetBooleanEvaluation("flag1", false, empty_ctx_)->GetValue()); - EXPECT_FALSE(provider.GetBooleanEvaluation("common_flag", true, empty_ctx_) - ->GetValue()); + absl::StatusOr> res1 = + provider.GetBooleanEvaluation("flag1", false, empty_ctx_); + ASSERT_TRUE(res1.ok()); + EXPECT_TRUE((*res1)->GetValue()); + + absl::StatusOr> res2 = + provider.GetBooleanEvaluation("common_flag", true, empty_ctx_); + ASSERT_TRUE(res2.ok()); + EXPECT_FALSE((*res2)->GetValue()); std::unordered_map updated; updated["flag2"] = CreateFlag({{"off", false}}, "off"); @@ -238,22 +273,29 @@ TEST_F(InMemoryProviderTest, UpdateFlagsAddsAndOverwritesExisting) { // flag1 should still exist and retain its value as it was not in // `updated_flags_map` - std::unique_ptr flag1_res = + absl::StatusOr> flag1_res_or = provider.GetBooleanEvaluation("flag1", false, empty_ctx_); + ASSERT_TRUE(flag1_res_or.ok()); + const std::unique_ptr& flag1_res = *flag1_res_or; EXPECT_TRUE(flag1_res->GetValue()); EXPECT_EQ(flag1_res->GetReason(), Reason::kStatic); EXPECT_THAT(flag1_res->GetVariant(), Optional(std::string("on"))); // flag2 should now exist and be evaluated to its new value - std::unique_ptr flag2_res = + absl::StatusOr> flag2_res_or = provider.GetBooleanEvaluation("flag2", true, empty_ctx_); + ASSERT_TRUE(flag2_res_or.ok()); + const std::unique_ptr& flag2_res = *flag2_res_or; EXPECT_FALSE(flag2_res->GetValue()); EXPECT_EQ(flag2_res->GetReason(), Reason::kStatic); EXPECT_THAT(flag2_res->GetVariant(), Optional(std::string("off"))); // common_flag should be updated to true - std::unique_ptr common_flag_res = + absl::StatusOr> common_flag_res_or = provider.GetBooleanEvaluation("common_flag", false, empty_ctx_); + ASSERT_TRUE(common_flag_res_or.ok()); + const std::unique_ptr& common_flag_res = + *common_flag_res_or; EXPECT_TRUE(common_flag_res->GetValue()); EXPECT_EQ(common_flag_res->GetReason(), Reason::kStatic); EXPECT_THAT(common_flag_res->GetVariant(), Optional(std::string("updated"))); @@ -264,8 +306,11 @@ TEST_F(InMemoryProviderTest, NoDefaultVariantAndEvaluatorFailsOrMissing) { provider1.UpdateFlag("no_default_no_evaluator", CreateFlag({{"v1", true}}, std::nullopt, nullptr)); EXPECT_TRUE(provider1.Init(empty_ctx_).ok()); - std::unique_ptr res1 = provider1.GetBooleanEvaluation( - "no_default_no_evaluator", true, empty_ctx_); + absl::StatusOr> res1_or = + provider1.GetBooleanEvaluation("no_default_no_evaluator", true, + empty_ctx_); + ASSERT_TRUE(res1_or.ok()); + const std::unique_ptr& res1 = *res1_or; EXPECT_TRUE(res1->GetValue()); EXPECT_EQ(res1->GetReason(), Reason::kDefault); EXPECT_FALSE(res1->GetVariant().has_value()); @@ -279,8 +324,11 @@ TEST_F(InMemoryProviderTest, NoDefaultVariantAndEvaluatorFailsOrMissing) { "no_default_failing_evaluator", CreateFlag({{"v1", true}}, std::nullopt, failing_evaluator)); EXPECT_TRUE(provider2.Init(empty_ctx_).ok()); - std::unique_ptr res2 = provider2.GetBooleanEvaluation( - "no_default_failing_evaluator", false, empty_ctx_); + absl::StatusOr> res2_or = + provider2.GetBooleanEvaluation("no_default_failing_evaluator", false, + empty_ctx_); + ASSERT_TRUE(res2_or.ok()); + const std::unique_ptr& res2 = *res2_or; EXPECT_FALSE(res2->GetValue()); EXPECT_EQ(res2->GetReason(), Reason::kDefault); EXPECT_FALSE(res2->GetVariant().has_value()); @@ -310,8 +358,10 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorUsesContext) { EvaluationContext admin_ctx = EvaluationContext::Builder().WithAttribute("user_is_admin", true).build(); - std::unique_ptr res_admin = + absl::StatusOr> res_admin_or = provider.GetBooleanEvaluation("admin_flag", false, admin_ctx); + ASSERT_TRUE(res_admin_or.ok()); + const std::unique_ptr& res_admin = *res_admin_or; ASSERT_NE(res_admin, nullptr); EXPECT_TRUE(res_admin->GetValue()); EXPECT_EQ(res_admin->GetReason(), Reason::kTargetingMatch); @@ -320,16 +370,21 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorUsesContext) { EvaluationContext non_admin_ctx = EvaluationContext::Builder() .WithAttribute("user_is_admin", false) .build(); - std::unique_ptr res_non_admin = + absl::StatusOr> res_non_admin_or = provider.GetBooleanEvaluation("admin_flag", true, non_admin_ctx); + ASSERT_TRUE(res_non_admin_or.ok()); + const std::unique_ptr& res_non_admin = + *res_non_admin_or; ASSERT_NE(res_non_admin, nullptr); EXPECT_FALSE(res_non_admin->GetValue()); EXPECT_EQ(res_non_admin->GetReason(), Reason::kTargetingMatch); EXPECT_FALSE(res_non_admin->GetErrorCode().has_value()); - std::unique_ptr res_no_attr = + absl::StatusOr> res_no_attr_or = provider.GetBooleanEvaluation("admin_flag", true, empty_ctx_); - ASSERT_THAT(res_no_attr, testing::NotNull()); + ASSERT_TRUE(res_no_attr_or.ok()); + const std::unique_ptr& res_no_attr = *res_no_attr_or; + ASSERT_NE(res_no_attr, nullptr); EXPECT_FALSE(res_no_attr->GetValue()); EXPECT_EQ(res_no_attr->GetReason(), Reason::kTargetingMatch); EXPECT_FALSE(res_no_attr->GetErrorCode().has_value()); @@ -338,8 +393,11 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorUsesContext) { EvaluationContext::Builder() .WithAttribute("user_is_admin", std::string("true")) .build(); - std::unique_ptr res_wrong_type = + absl::StatusOr> res_wrong_type_or = provider.GetBooleanEvaluation("admin_flag", true, wrong_type_ctx); + ASSERT_TRUE(res_wrong_type_or.ok()); + const std::unique_ptr& res_wrong_type = + *res_wrong_type_or; ASSERT_NE(res_wrong_type, nullptr); EXPECT_FALSE(res_wrong_type->GetValue()); EXPECT_EQ(res_wrong_type->GetReason(), Reason::kDefault); @@ -354,9 +412,10 @@ TEST_F(InMemoryProviderTest, StringEvaluationSuccess) { CreateFlag({{"v1", "hello"}, {"v2", "world"}}, "v2")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetStringEvaluation("string_flag", "default", empty_ctx_); - + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; ASSERT_NE(res, nullptr); EXPECT_EQ(res->GetValue(), "world"); EXPECT_EQ(res->GetReason(), Reason::kStatic); @@ -369,8 +428,10 @@ TEST_F(InMemoryProviderTest, IntegerEvaluationSuccess) { CreateFlag({{"v1", 100}, {"v2", 200}}, "v1")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetIntegerEvaluation("int_flag", 0, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; ASSERT_NE(res, nullptr); EXPECT_EQ(res->GetValue(), 100); @@ -384,8 +445,10 @@ TEST_F(InMemoryProviderTest, DoubleEvaluationSuccess) { CreateFlag({{"v1", 3.14}, {"v2", 2.71}}, "v2")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetDoubleEvaluation("double_flag", 0.0, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; ASSERT_NE(res, nullptr); EXPECT_DOUBLE_EQ(res->GetValue(), 2.71); @@ -399,8 +462,10 @@ TEST_F(InMemoryProviderTest, ObjectEvaluationSuccess) { CreateFlag({{"v1", Value()}}, "v1")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); - std::unique_ptr res = + absl::StatusOr> res_or = provider.GetObjectEvaluation("object_flag", Value(), empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; ASSERT_NE(res, nullptr); EXPECT_EQ(res->GetReason(), Reason::kStatic); diff --git a/test/mocks/mock_feature_provider.h b/test/mocks/mock_feature_provider.h index 67e7f78..74e7ad7 100644 --- a/test/mocks/mock_feature_provider.h +++ b/test/mocks/mock_feature_provider.h @@ -11,23 +11,28 @@ namespace openfeature { class MockFeatureProvider : public FeatureProvider { public: MOCK_METHOD(Metadata, GetMetadata, (), (const, override)); - MOCK_METHOD(std::unique_ptr, GetBooleanEvaluation, + MOCK_METHOD(absl::StatusOr>, + GetBooleanEvaluation, (std::string_view flag, bool default_value, const EvaluationContext& ctx), (override)); - MOCK_METHOD(std::unique_ptr, GetStringEvaluation, + MOCK_METHOD(absl::StatusOr>, + GetStringEvaluation, (std::string_view flag, std::string_view default_value, const EvaluationContext& ctx), (override)); - MOCK_METHOD(std::unique_ptr, GetIntegerEvaluation, + MOCK_METHOD(absl::StatusOr>, + GetIntegerEvaluation, (std::string_view flag, int64_t default_value, const EvaluationContext& ctx), (override)); - MOCK_METHOD(std::unique_ptr, GetDoubleEvaluation, + MOCK_METHOD(absl::StatusOr>, + GetDoubleEvaluation, (std::string_view flag, double default_value, const EvaluationContext& ctx), (override)); - MOCK_METHOD(std::unique_ptr, GetObjectEvaluation, + MOCK_METHOD(absl::StatusOr>, + GetObjectEvaluation, (std::string_view flag, Value default_value, const EvaluationContext& ctx), (override)); diff --git a/test/noop_provider_test.cpp b/test/noop_provider_test.cpp index aa15187..4cb82f2 100644 --- a/test/noop_provider_test.cpp +++ b/test/noop_provider_test.cpp @@ -37,9 +37,12 @@ class NoopProviderBooleanTest : public NoopProviderTest, TEST_P(NoopProviderBooleanTest, BooleanEvaluationShouldReturnDefaultValue) { const bool defaultValue = GetParam(); - const std::unique_ptr details = + absl::StatusOr> result = provider_.GetBooleanEvaluation("my-bool-flag", defaultValue, ctx_); + ASSERT_TRUE(result.ok()); + const std::unique_ptr& details = *result; + EXPECT_EQ(details->GetValue(), defaultValue); EXPECT_EQ(details->GetReason(), Reason::kDefault); EXPECT_EQ(details->GetVariant(), "default-variant"); @@ -59,8 +62,10 @@ class NoopProviderStringTest TEST_P(NoopProviderStringTest, StringEvaluationShouldReturnDefaultValue) { const std::string defaultValue = GetParam(); - const std::unique_ptr details = + absl::StatusOr> result = provider_.GetStringEvaluation("my-string-flag", defaultValue, ctx_); + ASSERT_TRUE(result.ok()); + const std::unique_ptr& details = *result; EXPECT_EQ(details->GetValue(), defaultValue); EXPECT_EQ(details->GetReason(), Reason::kDefault); @@ -81,8 +86,10 @@ class NoopProviderIntegerTest : public NoopProviderTest, TEST_P(NoopProviderIntegerTest, IntegerEvaluationShouldReturnDefaultValue) { const int64_t defaultValue = GetParam(); - const std::unique_ptr details = + absl::StatusOr> result = provider_.GetIntegerEvaluation("my-int-flag", defaultValue, ctx_); + ASSERT_TRUE(result.ok()); + const std::unique_ptr& details = *result; EXPECT_EQ(details->GetValue(), defaultValue); EXPECT_EQ(details->GetReason(), Reason::kDefault); @@ -102,8 +109,10 @@ class NoopProviderDoubleTest : public NoopProviderTest, TEST_P(NoopProviderDoubleTest, DoubleEvaluationShouldReturnDefaultValue) { const double defaultValue = GetParam(); - const std::unique_ptr details = + absl::StatusOr> result = provider_.GetDoubleEvaluation("my-double-flag", defaultValue, ctx_); + ASSERT_TRUE(result.ok()); + const std::unique_ptr& details = *result; EXPECT_DOUBLE_EQ(details->GetValue(), defaultValue); EXPECT_EQ(details->GetReason(), Reason::kDefault); @@ -129,8 +138,10 @@ TEST_F(NoopProviderTest, ObjectEvaluationShouldReturnDefaultValue) { const Value defaultValue(default_struct); - const std::unique_ptr details = + absl::StatusOr> result = provider_.GetObjectEvaluation("my-object-flag", defaultValue, ctx_); + ASSERT_TRUE(result.ok()); + const std::unique_ptr& details = *result; EXPECT_EQ(details->GetValue(), defaultValue); EXPECT_EQ(details->GetReason(), Reason::kDefault); From 0547963e0e26046171d6d9d11dc421e871abeb4a Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 12 May 2026 07:03:07 +0000 Subject: [PATCH 2/5] Catch exeptions thrown during provider evaluation. Signed-off-by: NeaguGeorgiana23 --- openfeature/client_api.h | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/openfeature/client_api.h b/openfeature/client_api.h index 46d4145..e682a1c 100644 --- a/openfeature/client_api.h +++ b/openfeature/client_api.h @@ -128,16 +128,27 @@ std::unique_ptr ClientAPI::EvaluateFlag( } EvaluationContext merged_context = MergeContexts(ctx); - auto result = provider_call(provider, merged_context); - if (!result.ok()) { + try { + auto result = provider_call(provider, merged_context); + + if (!result.ok() || *result == nullptr) { + return std::make_unique( + default_value, Reason::kError, std::nullopt, FlagMetadata(), + ErrorCode::kGeneral, std::string(result.status().message())); + } + return std::move(*result); + } catch (const std::exception& e) { + return std::make_unique( + default_value, Reason::kError, std::nullopt, FlagMetadata(), + ErrorCode::kGeneral, + std::string("Exception during evaluation: ") + e.what()); + } catch (...) { return std::make_unique( default_value, Reason::kError, std::nullopt, FlagMetadata(), - ErrorCode::kGeneral, std::string(result.status().message())); + ErrorCode::kGeneral, "Unknown exception during evaluation"); } - return std::move(*result); } - } // namespace openfeature #endif // CPP_SDK_INCLUDE_OPENFEATURE_CLIENT_API_H_ From 523ae4b329cd4c5dcfb333311b2c9cfc658b6bf1 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 12 May 2026 07:19:55 +0000 Subject: [PATCH 3/5] Fix linter errors. Signed-off-by: NeaguGeorgiana23 --- openfeature/memory_provider/in_memory_provider.cpp | 6 +++--- openfeature/memory_provider/in_memory_provider.h | 4 ++-- test/memory_provider/in_memory_provider_test.cpp | 14 ++++++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/openfeature/memory_provider/in_memory_provider.cpp b/openfeature/memory_provider/in_memory_provider.cpp index 1640170..411a481 100644 --- a/openfeature/memory_provider/in_memory_provider.cpp +++ b/openfeature/memory_provider/in_memory_provider.cpp @@ -105,14 +105,14 @@ std::unique_ptr> InMemoryProvider::Evaluate( } std::string key_str{key}; - auto it = flags_.find(key_str); - if (it == flags_.end()) { + auto flag_it = flags_.find(key_str); + if (flag_it == flags_.end()) { return std::make_unique>( default_value, Reason::kError, std::nullopt, FlagMetadata{}, ErrorCode::kFlagNotFound, "Flag " + key_str + " not found"); } - const Flag* flag = std::any_cast>(&it->second); + const Flag* flag = std::any_cast>(&flag_it->second); if (!flag) { return std::make_unique>( diff --git a/openfeature/memory_provider/in_memory_provider.h b/openfeature/memory_provider/in_memory_provider.h index 374452d..8624da4 100644 --- a/openfeature/memory_provider/in_memory_provider.h +++ b/openfeature/memory_provider/in_memory_provider.h @@ -24,9 +24,9 @@ namespace openfeature { // evaluation based on the provided EvaluationContext. class InMemoryProvider : public FeatureProvider { public: - InMemoryProvider(std::unordered_map flags); + explicit InMemoryProvider(std::unordered_map flags); - ~InMemoryProvider() = default; + ~InMemoryProvider() override = default; Metadata GetMetadata() const override; diff --git a/test/memory_provider/in_memory_provider_test.cpp b/test/memory_provider/in_memory_provider_test.cpp index cbdd0c0..e7a97b3 100644 --- a/test/memory_provider/in_memory_provider_test.cpp +++ b/test/memory_provider/in_memory_provider_test.cpp @@ -423,9 +423,12 @@ TEST_F(InMemoryProviderTest, StringEvaluationSuccess) { } TEST_F(InMemoryProviderTest, IntegerEvaluationSuccess) { + constexpr int64_t kVariantValue1 = 100; + constexpr int64_t kVariantValue2 = 200; + InMemoryProvider provider({}); provider.UpdateFlag("int_flag", - CreateFlag({{"v1", 100}, {"v2", 200}}, "v1")); + CreateFlag({{"v1", kVariantValue1}, {"v2", kVariantValue2}}, "v1")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); absl::StatusOr> res_or = @@ -434,15 +437,18 @@ TEST_F(InMemoryProviderTest, IntegerEvaluationSuccess) { const std::unique_ptr& res = *res_or; ASSERT_NE(res, nullptr); - EXPECT_EQ(res->GetValue(), 100); + EXPECT_EQ(res->GetValue(), kVariantValue1); EXPECT_EQ(res->GetReason(), Reason::kStatic); EXPECT_THAT(res->GetVariant(), Optional(std::string("v1"))); } TEST_F(InMemoryProviderTest, DoubleEvaluationSuccess) { + constexpr double kVariantValue1 = 3.14; + constexpr double kVariantValue2 = 2.71; + InMemoryProvider provider({}); provider.UpdateFlag("double_flag", - CreateFlag({{"v1", 3.14}, {"v2", 2.71}}, "v2")); + CreateFlag({{"v1", kVariantValue1}, {"v2", kVariantValue2}}, "v2")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); absl::StatusOr> res_or = @@ -451,7 +457,7 @@ TEST_F(InMemoryProviderTest, DoubleEvaluationSuccess) { const std::unique_ptr& res = *res_or; ASSERT_NE(res, nullptr); - EXPECT_DOUBLE_EQ(res->GetValue(), 2.71); + EXPECT_DOUBLE_EQ(res->GetValue(), kVariantValue2); EXPECT_EQ(res->GetReason(), Reason::kStatic); EXPECT_THAT(res->GetVariant(), Optional(std::string("v2"))); } From 669049d63ca6cae0b853b36ff4e4ca0601f32c67 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 12 May 2026 07:50:00 +0000 Subject: [PATCH 4/5] Fix linter errors. Signed-off-by: NeaguGeorgiana23 --- .../memory_provider/in_memory_provider.cpp | 5 +- .../memory_provider/in_memory_provider.h | 4 +- .../in_memory_provider_test.cpp | 184 ++++++++++-------- test/noop_provider_test.cpp | 41 ++-- 4 files changed, 133 insertions(+), 101 deletions(-) diff --git a/openfeature/memory_provider/in_memory_provider.cpp b/openfeature/memory_provider/in_memory_provider.cpp index 411a481..a48df05 100644 --- a/openfeature/memory_provider/in_memory_provider.cpp +++ b/openfeature/memory_provider/in_memory_provider.cpp @@ -15,7 +15,7 @@ static constexpr std::string_view kName = "InMemoryProvider"; InMemoryProvider::InMemoryProvider( std::unordered_map flags) - : flags_(std::move(flags)), status_(ProviderStatus::kNotReady) {} + : flags_(std::move(flags)) {} Metadata InMemoryProvider::GetMetadata() const { return Metadata{std::string(kName)}; @@ -85,7 +85,8 @@ InMemoryProvider::GetObjectEvaluation(std::string_view key, Value default_value, template std::unique_ptr> InMemoryProvider::Evaluate( - std::string_view key, T default_value, const EvaluationContext& ctx) { + std::string_view key, const T& default_value, + const EvaluationContext& ctx) { std::shared_lock lock(mutex_); if (status_ != ProviderStatus::kReady) { diff --git a/openfeature/memory_provider/in_memory_provider.h b/openfeature/memory_provider/in_memory_provider.h index 8624da4..386d7cf 100644 --- a/openfeature/memory_provider/in_memory_provider.h +++ b/openfeature/memory_provider/in_memory_provider.h @@ -66,11 +66,11 @@ class InMemoryProvider : public FeatureProvider { private: template std::unique_ptr> Evaluate(std::string_view key, - T default_value, + const T& default_value, const EvaluationContext& ctx); std::unordered_map flags_; - ProviderStatus status_; + ProviderStatus status_ = ProviderStatus::kNotReady; mutable std::shared_mutex mutex_; }; diff --git a/test/memory_provider/in_memory_provider_test.cpp b/test/memory_provider/in_memory_provider_test.cpp index e7a97b3..d63adfc 100644 --- a/test/memory_provider/in_memory_provider_test.cpp +++ b/test/memory_provider/in_memory_provider_test.cpp @@ -301,108 +301,127 @@ TEST_F(InMemoryProviderTest, UpdateFlagsAddsAndOverwritesExisting) { EXPECT_THAT(common_flag_res->GetVariant(), Optional(std::string("updated"))); } -TEST_F(InMemoryProviderTest, NoDefaultVariantAndEvaluatorFailsOrMissing) { - InMemoryProvider provider1({}); - provider1.UpdateFlag("no_default_no_evaluator", - CreateFlag({{"v1", true}}, std::nullopt, nullptr)); - EXPECT_TRUE(provider1.Init(empty_ctx_).ok()); - absl::StatusOr> res1_or = - provider1.GetBooleanEvaluation("no_default_no_evaluator", true, - empty_ctx_); - ASSERT_TRUE(res1_or.ok()); - const std::unique_ptr& res1 = *res1_or; - EXPECT_TRUE(res1->GetValue()); - EXPECT_EQ(res1->GetReason(), Reason::kDefault); - EXPECT_FALSE(res1->GetVariant().has_value()); +TEST_F(InMemoryProviderTest, NoDefaultVariantAndNoEvaluatorReturnsDefault) { + InMemoryProvider provider({}); + provider.UpdateFlag("no_default_no_evaluator", + CreateFlag({{"v1", true}}, std::nullopt, nullptr)); + EXPECT_TRUE(provider.Init(empty_ctx_).ok()); + + absl::StatusOr> res_or = + provider.GetBooleanEvaluation("no_default_no_evaluator", true, + empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; + + EXPECT_TRUE(res->GetValue()); + EXPECT_EQ(res->GetReason(), Reason::kDefault); + EXPECT_FALSE(res->GetVariant().has_value()); +} +TEST_F(InMemoryProviderTest, + NoDefaultVariantAndFailingEvaluatorReturnsDefault) { auto failing_evaluator = [](const Flag&, const EvaluationContext&) -> absl::StatusOr { return absl::InvalidArgumentError("Evaluator explicitly failed"); }; - InMemoryProvider provider2({}); - provider2.UpdateFlag( + + InMemoryProvider provider({}); + provider.UpdateFlag( "no_default_failing_evaluator", CreateFlag({{"v1", true}}, std::nullopt, failing_evaluator)); - EXPECT_TRUE(provider2.Init(empty_ctx_).ok()); - absl::StatusOr> res2_or = - provider2.GetBooleanEvaluation("no_default_failing_evaluator", false, - empty_ctx_); - ASSERT_TRUE(res2_or.ok()); - const std::unique_ptr& res2 = *res2_or; - EXPECT_FALSE(res2->GetValue()); - EXPECT_EQ(res2->GetReason(), Reason::kDefault); - EXPECT_FALSE(res2->GetVariant().has_value()); + EXPECT_TRUE(provider.Init(empty_ctx_).ok()); + + absl::StatusOr> res_or = + provider.GetBooleanEvaluation("no_default_failing_evaluator", false, + empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; + + EXPECT_FALSE(res->GetValue()); + EXPECT_EQ(res->GetReason(), Reason::kDefault); + EXPECT_FALSE(res->GetVariant().has_value()); } -TEST_F(InMemoryProviderTest, ContextEvaluatorUsesContext) { - auto context_aware_evaluator = - [](const Flag&, - const EvaluationContext& ctx) -> absl::StatusOr { - const std::any* val_ptr = ctx.GetValue("user_is_admin"); - if (val_ptr != nullptr) { - try { - return std::any_cast(*val_ptr); - } catch (const std::bad_any_cast& e) { +class ContextAwareProviderTest : public InMemoryProviderTest { + protected: + void SetUp() override { + auto context_aware_evaluator = + [](const Flag&, + const EvaluationContext& ctx) -> absl::StatusOr { + const std::any* val_ptr = ctx.GetValue("user_is_admin"); + if (val_ptr != nullptr) { + // Exception-free casting using pointer overload + const bool* casted = std::any_cast(val_ptr); + if (casted) { + return *casted; + } return absl::InvalidArgumentError( "Context attribute 'user_is_admin' is not of type bool"); } - } - return false; - }; + return false; + }; - InMemoryProvider provider({}); - provider.UpdateFlag("admin_flag", - CreateFlag({{"on", true}, {"off", false}}, "off", - context_aware_evaluator)); - EXPECT_TRUE(provider.Init(empty_ctx_).ok()); + provider_.UpdateFlag("admin_flag", + CreateFlag({{"on", true}, {"off", false}}, "off", + context_aware_evaluator)); + EXPECT_TRUE(provider_.Init(empty_ctx_).ok()); + } + + InMemoryProvider provider_{{}}; +}; +TEST_F(ContextAwareProviderTest, AdminUserMatchesTargeting) { EvaluationContext admin_ctx = EvaluationContext::Builder().WithAttribute("user_is_admin", true).build(); - absl::StatusOr> res_admin_or = - provider.GetBooleanEvaluation("admin_flag", false, admin_ctx); - ASSERT_TRUE(res_admin_or.ok()); - const std::unique_ptr& res_admin = *res_admin_or; - ASSERT_NE(res_admin, nullptr); - EXPECT_TRUE(res_admin->GetValue()); - EXPECT_EQ(res_admin->GetReason(), Reason::kTargetingMatch); - EXPECT_FALSE(res_admin->GetErrorCode().has_value()); + absl::StatusOr> res_or = + provider_.GetBooleanEvaluation("admin_flag", false, admin_ctx); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; + ASSERT_NE(res, nullptr); + EXPECT_TRUE(res->GetValue()); + EXPECT_EQ(res->GetReason(), Reason::kTargetingMatch); + EXPECT_FALSE(res->GetErrorCode().has_value()); +} +TEST_F(ContextAwareProviderTest, NonAdminUserMatchesTargeting) { EvaluationContext non_admin_ctx = EvaluationContext::Builder() .WithAttribute("user_is_admin", false) .build(); - absl::StatusOr> res_non_admin_or = - provider.GetBooleanEvaluation("admin_flag", true, non_admin_ctx); - ASSERT_TRUE(res_non_admin_or.ok()); - const std::unique_ptr& res_non_admin = - *res_non_admin_or; - ASSERT_NE(res_non_admin, nullptr); - EXPECT_FALSE(res_non_admin->GetValue()); - EXPECT_EQ(res_non_admin->GetReason(), Reason::kTargetingMatch); - EXPECT_FALSE(res_non_admin->GetErrorCode().has_value()); - - absl::StatusOr> res_no_attr_or = - provider.GetBooleanEvaluation("admin_flag", true, empty_ctx_); - ASSERT_TRUE(res_no_attr_or.ok()); - const std::unique_ptr& res_no_attr = *res_no_attr_or; - ASSERT_NE(res_no_attr, nullptr); - EXPECT_FALSE(res_no_attr->GetValue()); - EXPECT_EQ(res_no_attr->GetReason(), Reason::kTargetingMatch); - EXPECT_FALSE(res_no_attr->GetErrorCode().has_value()); + absl::StatusOr> res_or = + provider_.GetBooleanEvaluation("admin_flag", true, non_admin_ctx); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; + ASSERT_NE(res, nullptr); + EXPECT_FALSE(res->GetValue()); + EXPECT_EQ(res->GetReason(), Reason::kTargetingMatch); + EXPECT_FALSE(res->GetErrorCode().has_value()); +} + +TEST_F(ContextAwareProviderTest, MissingAttributeDefaultsToFalse) { + absl::StatusOr> res_or = + provider_.GetBooleanEvaluation("admin_flag", true, empty_ctx_); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; + ASSERT_NE(res, nullptr); + EXPECT_FALSE(res->GetValue()); + EXPECT_EQ(res->GetReason(), Reason::kTargetingMatch); + EXPECT_FALSE(res->GetErrorCode().has_value()); +} +TEST_F(ContextAwareProviderTest, WrongAttributeTypeFallsBackToDefaultVariant) { EvaluationContext wrong_type_ctx = EvaluationContext::Builder() .WithAttribute("user_is_admin", std::string("true")) .build(); - absl::StatusOr> res_wrong_type_or = - provider.GetBooleanEvaluation("admin_flag", true, wrong_type_ctx); - ASSERT_TRUE(res_wrong_type_or.ok()); - const std::unique_ptr& res_wrong_type = - *res_wrong_type_or; - ASSERT_NE(res_wrong_type, nullptr); - EXPECT_FALSE(res_wrong_type->GetValue()); - EXPECT_EQ(res_wrong_type->GetReason(), Reason::kDefault); - EXPECT_THAT(res_wrong_type->GetVariant(), Optional(std::string("off"))); - EXPECT_FALSE(res_wrong_type->GetErrorCode().has_value()); + absl::StatusOr> res_or = + provider_.GetBooleanEvaluation("admin_flag", true, wrong_type_ctx); + ASSERT_TRUE(res_or.ok()); + const std::unique_ptr& res = *res_or; + ASSERT_NE(res, nullptr); + EXPECT_FALSE(res->GetValue()); + EXPECT_EQ(res->GetReason(), Reason::kDefault); + EXPECT_THAT(res->GetVariant(), Optional(std::string("off"))); + EXPECT_FALSE(res->GetErrorCode().has_value()); } TEST_F(InMemoryProviderTest, StringEvaluationSuccess) { @@ -427,8 +446,9 @@ TEST_F(InMemoryProviderTest, IntegerEvaluationSuccess) { constexpr int64_t kVariantValue2 = 200; InMemoryProvider provider({}); - provider.UpdateFlag("int_flag", - CreateFlag({{"v1", kVariantValue1}, {"v2", kVariantValue2}}, "v1")); + provider.UpdateFlag( + "int_flag", CreateFlag( + {{"v1", kVariantValue1}, {"v2", kVariantValue2}}, "v1")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); absl::StatusOr> res_or = @@ -447,8 +467,10 @@ TEST_F(InMemoryProviderTest, DoubleEvaluationSuccess) { constexpr double kVariantValue2 = 2.71; InMemoryProvider provider({}); - provider.UpdateFlag("double_flag", - CreateFlag({{"v1", kVariantValue1}, {"v2", kVariantValue2}}, "v2")); + provider.UpdateFlag( + "double_flag", + CreateFlag({{"v1", kVariantValue1}, {"v2", kVariantValue2}}, + "v2")); EXPECT_TRUE(provider.Init(empty_ctx_).ok()); absl::StatusOr> res_or = diff --git a/test/noop_provider_test.cpp b/test/noop_provider_test.cpp index 4cb82f2..1c9a8c3 100644 --- a/test/noop_provider_test.cpp +++ b/test/noop_provider_test.cpp @@ -4,7 +4,16 @@ #include "absl/status/status.h" -using namespace openfeature; +using ::openfeature::BoolResolutionDetails; +using ::openfeature::DoubleResolutionDetails; +using ::openfeature::EvaluationContext; +using ::openfeature::IntResolutionDetails; +using ::openfeature::Metadata; +using ::openfeature::NoopProvider; +using ::openfeature::ObjectResolutionDetails; +using ::openfeature::Reason; +using ::openfeature::StringResolutionDetails; +using ::openfeature::Value; class NoopProviderTest : public ::testing::Test { protected: @@ -35,15 +44,15 @@ class NoopProviderBooleanTest : public NoopProviderTest, public ::testing::WithParamInterface {}; TEST_P(NoopProviderBooleanTest, BooleanEvaluationShouldReturnDefaultValue) { - const bool defaultValue = GetParam(); + const bool default_value = GetParam(); absl::StatusOr> result = - provider_.GetBooleanEvaluation("my-bool-flag", defaultValue, ctx_); + provider_.GetBooleanEvaluation("my-bool-flag", default_value, ctx_); ASSERT_TRUE(result.ok()); const std::unique_ptr& details = *result; - EXPECT_EQ(details->GetValue(), defaultValue); + EXPECT_EQ(details->GetValue(), default_value); EXPECT_EQ(details->GetReason(), Reason::kDefault); EXPECT_EQ(details->GetVariant(), "default-variant"); EXPECT_FALSE(details->GetErrorCode().has_value()); @@ -60,14 +69,14 @@ class NoopProviderStringTest // Test to verify the string evaluation returns the default value. TEST_P(NoopProviderStringTest, StringEvaluationShouldReturnDefaultValue) { - const std::string defaultValue = GetParam(); + const std::string& default_value = GetParam(); absl::StatusOr> result = - provider_.GetStringEvaluation("my-string-flag", defaultValue, ctx_); + provider_.GetStringEvaluation("my-string-flag", default_value, ctx_); ASSERT_TRUE(result.ok()); const std::unique_ptr& details = *result; - EXPECT_EQ(details->GetValue(), defaultValue); + EXPECT_EQ(details->GetValue(), default_value); EXPECT_EQ(details->GetReason(), Reason::kDefault); EXPECT_EQ(details->GetVariant(), "default-variant"); EXPECT_FALSE(details->GetErrorCode().has_value()); @@ -84,14 +93,14 @@ class NoopProviderIntegerTest : public NoopProviderTest, // Test to verify the integer evaluation returns the default value. TEST_P(NoopProviderIntegerTest, IntegerEvaluationShouldReturnDefaultValue) { - const int64_t defaultValue = GetParam(); + const int64_t default_value = GetParam(); absl::StatusOr> result = - provider_.GetIntegerEvaluation("my-int-flag", defaultValue, ctx_); + provider_.GetIntegerEvaluation("my-int-flag", default_value, ctx_); ASSERT_TRUE(result.ok()); const std::unique_ptr& details = *result; - EXPECT_EQ(details->GetValue(), defaultValue); + EXPECT_EQ(details->GetValue(), default_value); EXPECT_EQ(details->GetReason(), Reason::kDefault); EXPECT_EQ(details->GetVariant(), "default-variant"); EXPECT_FALSE(details->GetErrorCode().has_value()); @@ -107,14 +116,14 @@ class NoopProviderDoubleTest : public NoopProviderTest, // Test to verify the double evaluation returns the default value. TEST_P(NoopProviderDoubleTest, DoubleEvaluationShouldReturnDefaultValue) { - const double defaultValue = GetParam(); + const double default_value = GetParam(); absl::StatusOr> result = - provider_.GetDoubleEvaluation("my-double-flag", defaultValue, ctx_); + provider_.GetDoubleEvaluation("my-double-flag", default_value, ctx_); ASSERT_TRUE(result.ok()); const std::unique_ptr& details = *result; - EXPECT_DOUBLE_EQ(details->GetValue(), defaultValue); + EXPECT_DOUBLE_EQ(details->GetValue(), default_value); EXPECT_EQ(details->GetReason(), Reason::kDefault); EXPECT_EQ(details->GetVariant(), "default-variant"); EXPECT_FALSE(details->GetErrorCode().has_value()); @@ -136,14 +145,14 @@ TEST_F(NoopProviderTest, ObjectEvaluationShouldReturnDefaultValue) { Value(std::vector{{Value("item1"), Value(1.23)}}); default_struct["a_struct"] = Value(inner_struct); - const Value defaultValue(default_struct); + const Value default_value(default_struct); absl::StatusOr> result = - provider_.GetObjectEvaluation("my-object-flag", defaultValue, ctx_); + provider_.GetObjectEvaluation("my-object-flag", default_value, ctx_); ASSERT_TRUE(result.ok()); const std::unique_ptr& details = *result; - EXPECT_EQ(details->GetValue(), defaultValue); + EXPECT_EQ(details->GetValue(), default_value); EXPECT_EQ(details->GetReason(), Reason::kDefault); EXPECT_EQ(details->GetVariant(), "default-variant"); EXPECT_FALSE(details->GetErrorCode().has_value()); From d9b20ac04964325a2cb81b3a92377f461d634791 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 12 May 2026 07:59:03 +0000 Subject: [PATCH 5/5] Fix linter errors. Signed-off-by: NeaguGeorgiana23 --- test/noop_provider_test.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/noop_provider_test.cpp b/test/noop_provider_test.cpp index 1c9a8c3..747f161 100644 --- a/test/noop_provider_test.cpp +++ b/test/noop_provider_test.cpp @@ -131,18 +131,23 @@ TEST_P(NoopProviderDoubleTest, DoubleEvaluationShouldReturnDefaultValue) { EXPECT_TRUE(details->GetErrorMessage()->empty()); } +constexpr double kPi = 3.14; +constexpr double kNegativeDouble = -100.5; INSTANTIATE_TEST_SUITE_P(DoubleDefaultValues, NoopProviderDoubleTest, - testing::Values(3.14, -100.5, 0.0)); + testing::Values(kPi, kNegativeDouble, 0.0)); TEST_F(NoopProviderTest, ObjectEvaluationShouldReturnDefaultValue) { + constexpr int64_t kDefaultIntValue = 123; + constexpr double kDefaultListItemValue = 1.23; + std::map inner_struct; inner_struct["inner_key"] = Value("inner_value"); std::map default_struct; default_struct["a_bool"] = Value(true); - default_struct["an_int"] = Value(123); + default_struct["an_int"] = Value(kDefaultIntValue); default_struct["a_list"] = - Value(std::vector{{Value("item1"), Value(1.23)}}); + Value(std::vector{{Value("item1"), Value(kDefaultListItemValue)}}); default_struct["a_struct"] = Value(inner_struct); const Value default_value(default_struct);