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
1 change: 1 addition & 0 deletions openfeature/memory_provider/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ cc_library(
"//openfeature:provider_status",
"//openfeature:reason",
"//openfeature:resolution_details",
"//openfeature:value",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
],
Expand Down
21 changes: 21 additions & 0 deletions openfeature/memory_provider/in_memory_provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ std::unique_ptr<BoolResolutionDetails> InMemoryProvider::GetBooleanEvaluation(
return Evaluate<bool>(key, default_value, ctx);
}

std::unique_ptr<StringResolutionDetails> InMemoryProvider::GetStringEvaluation(
std::string_view key, std::string_view default_value,
const EvaluationContext& ctx) {
return Evaluate<std::string>(key, std::string(default_value), ctx);
}

std::unique_ptr<IntResolutionDetails> InMemoryProvider::GetIntegerEvaluation(
std::string_view key, int64_t default_value, const EvaluationContext& ctx) {
return Evaluate<int64_t>(key, default_value, ctx);
}

std::unique_ptr<DoubleResolutionDetails> InMemoryProvider::GetDoubleEvaluation(
std::string_view key, double default_value, const EvaluationContext& ctx) {
return Evaluate<double>(key, default_value, ctx);
}

std::unique_ptr<ObjectResolutionDetails> InMemoryProvider::GetObjectEvaluation(
std::string_view key, Value default_value, const EvaluationContext& ctx) {
return Evaluate<Value>(key, default_value, ctx);
}

template <typename T>
std::unique_ptr<ResolutionDetails<T>> InMemoryProvider::Evaluate(
std::string_view key, T default_value, const EvaluationContext& ctx) {
Expand Down
17 changes: 17 additions & 0 deletions openfeature/memory_provider/in_memory_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "openfeature/provider.h"
#include "openfeature/provider_status.h"
#include "openfeature/resolution_details.h"
#include "openfeature/value.h"

namespace openfeature {

Expand Down Expand Up @@ -45,6 +46,22 @@ class InMemoryProvider : public FeatureProvider {
std::string_view key, bool default_value,
const EvaluationContext& ctx) override;

std::unique_ptr<StringResolutionDetails> GetStringEvaluation(
std::string_view key, std::string_view default_value,
const EvaluationContext& ctx) override;

std::unique_ptr<IntResolutionDetails> GetIntegerEvaluation(
std::string_view key, int64_t default_value,
const EvaluationContext& ctx) override;

std::unique_ptr<DoubleResolutionDetails> GetDoubleEvaluation(
std::string_view key, double default_value,
const EvaluationContext& ctx) override;

std::unique_ptr<ObjectResolutionDetails> GetObjectEvaluation(
std::string_view key, Value default_value,
const EvaluationContext& ctx) override;

private:
template <typename T>
std::unique_ptr<ResolutionDetails<T>> Evaluate(std::string_view key,
Expand Down
113 changes: 89 additions & 24 deletions test/memory_provider/in_memory_provider_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "openfeature/memory_provider/flag.h"
#include "openfeature/reason.h"
#include "openfeature/resolution_details.h"
#include "openfeature/value.h"

namespace openfeature {

Expand All @@ -25,13 +26,14 @@ class InMemoryProviderTest : public ::testing::Test {
protected:
EvaluationContext empty_ctx_ = EvaluationContext::Builder().build();

// Helper method to create a basic boolean flag for testing.
Flag<bool> CreateBoolFlag(std::unordered_map<std::string, bool> variants,
std::optional<std::string> default_variant_opt,
Flag<bool>::ContextEvaluator evaluator = nullptr,
bool disabled = false) {
return Flag<bool>(std::move(variants), std::move(default_variant_opt),
std::move(evaluator), FlagMetadata{}, disabled);
// Helper method to create a generic flag for testing.
template <typename T>
Flag<T> CreateFlag(std::unordered_map<std::string, T> variants,
std::optional<std::string> default_variant_opt,
typename Flag<T>::ContextEvaluator evaluator = nullptr,
bool disabled = false) {
return Flag<T>(std::move(variants), std::move(default_variant_opt),
std::move(evaluator), FlagMetadata{}, disabled);
}
};

Expand Down Expand Up @@ -101,7 +103,7 @@ TEST_F(InMemoryProviderTest, FlagTypeMismatch) {
TEST_F(InMemoryProviderTest, DisabledFlagReturnsDisabledReason) {
InMemoryProvider provider({});
provider.UpdateFlag("disabled_flag",
CreateBoolFlag({{"on", true}}, "on", nullptr, true));
CreateFlag<bool>({{"on", true}}, "on", nullptr, true));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

std::unique_ptr<BoolResolutionDetails> res =
Expand All @@ -115,7 +117,7 @@ TEST_F(InMemoryProviderTest, DisabledFlagReturnsDisabledReason) {
TEST_F(InMemoryProviderTest, StaticEvaluationSuccess) {
InMemoryProvider provider({});
provider.UpdateFlag("static_flag",
CreateBoolFlag({{"on", true}, {"off", false}}, "on"));
CreateFlag<bool>({{"on", true}, {"off", false}}, "on"));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

std::unique_ptr<BoolResolutionDetails> res =
Expand All @@ -133,8 +135,9 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorSuccess) {
};

InMemoryProvider provider({});
provider.UpdateFlag("dyn_flag", CreateBoolFlag({{"on", true}, {"off", false}},
"on", evaluator));
provider.UpdateFlag(
"dyn_flag",
CreateFlag<bool>({{"on", true}, {"off", false}}, "on", evaluator));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

std::unique_ptr<BoolResolutionDetails> res =
Expand All @@ -151,8 +154,9 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorFailureFallsBackToDefaultVariant) {
};

InMemoryProvider provider({});
provider.UpdateFlag("dyn_flag", CreateBoolFlag({{"on", true}, {"off", false}},
"off", evaluator));
provider.UpdateFlag(
"dyn_flag",
CreateFlag<bool>({{"on", true}, {"off", false}}, "off", evaluator));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

std::unique_ptr<BoolResolutionDetails> res =
Expand Down Expand Up @@ -201,23 +205,23 @@ TEST_F(InMemoryProviderTest, UpdateFlagReplacesAndAddsNew) {
InMemoryProvider provider({});
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

provider.UpdateFlag("flag1", CreateBoolFlag({{"on", true}}, "on"));
provider.UpdateFlag("flag1", CreateFlag<bool>({{"on", true}}, "on"));
EXPECT_TRUE(
provider.GetBooleanEvaluation("flag1", false, empty_ctx_)->GetValue());

provider.UpdateFlag("flag1", CreateBoolFlag({{"off", false}}, "off"));
provider.UpdateFlag("flag1", CreateFlag<bool>({{"off", false}}, "off"));
EXPECT_FALSE(
provider.GetBooleanEvaluation("flag1", true, empty_ctx_)->GetValue());

provider.UpdateFlag("new_flag", CreateBoolFlag({{"added", true}}, "added"));
provider.UpdateFlag("new_flag", CreateFlag<bool>({{"added", true}}, "added"));
EXPECT_TRUE(
provider.GetBooleanEvaluation("new_flag", false, empty_ctx_)->GetValue());
}

TEST_F(InMemoryProviderTest, UpdateFlagsAddsAndOverwritesExisting) {
std::unordered_map<std::string, std::any> initial;
initial["flag1"] = CreateBoolFlag({{"on", true}}, "on");
initial["common_flag"] = CreateBoolFlag({{"initial", false}}, "initial");
initial["flag1"] = CreateFlag<bool>({{"on", true}}, "on");
initial["common_flag"] = CreateFlag<bool>({{"initial", false}}, "initial");

InMemoryProvider provider(std::move(initial));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());
Expand All @@ -227,8 +231,8 @@ TEST_F(InMemoryProviderTest, UpdateFlagsAddsAndOverwritesExisting) {
->GetValue());

std::unordered_map<std::string, std::any> updated;
updated["flag2"] = CreateBoolFlag({{"off", false}}, "off");
updated["common_flag"] = CreateBoolFlag({{"updated", true}}, "updated");
updated["flag2"] = CreateFlag<bool>({{"off", false}}, "off");
updated["common_flag"] = CreateFlag<bool>({{"updated", true}}, "updated");

provider.UpdateFlags(std::move(updated));

Expand Down Expand Up @@ -258,7 +262,7 @@ TEST_F(InMemoryProviderTest, UpdateFlagsAddsAndOverwritesExisting) {
TEST_F(InMemoryProviderTest, NoDefaultVariantAndEvaluatorFailsOrMissing) {
InMemoryProvider provider1({});
provider1.UpdateFlag("no_default_no_evaluator",
CreateBoolFlag({{"v1", true}}, std::nullopt, nullptr));
CreateFlag<bool>({{"v1", true}}, std::nullopt, nullptr));
EXPECT_TRUE(provider1.Init(empty_ctx_).ok());
std::unique_ptr<BoolResolutionDetails> res1 = provider1.GetBooleanEvaluation(
"no_default_no_evaluator", true, empty_ctx_);
Expand All @@ -273,7 +277,7 @@ TEST_F(InMemoryProviderTest, NoDefaultVariantAndEvaluatorFailsOrMissing) {
InMemoryProvider provider2({});
provider2.UpdateFlag(
"no_default_failing_evaluator",
CreateBoolFlag({{"v1", true}}, std::nullopt, failing_evaluator));
CreateFlag<bool>({{"v1", true}}, std::nullopt, failing_evaluator));
EXPECT_TRUE(provider2.Init(empty_ctx_).ok());
std::unique_ptr<BoolResolutionDetails> res2 = provider2.GetBooleanEvaluation(
"no_default_failing_evaluator", false, empty_ctx_);
Expand All @@ -300,8 +304,8 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorUsesContext) {

InMemoryProvider provider({});
provider.UpdateFlag("admin_flag",
CreateBoolFlag({{"on", true}, {"off", false}}, "off",
context_aware_evaluator));
CreateFlag<bool>({{"on", true}, {"off", false}}, "off",
context_aware_evaluator));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

EvaluationContext admin_ctx =
Expand Down Expand Up @@ -342,4 +346,65 @@ TEST_F(InMemoryProviderTest, ContextEvaluatorUsesContext) {
EXPECT_THAT(res_wrong_type->GetVariant(), Optional(std::string("off")));
EXPECT_FALSE(res_wrong_type->GetErrorCode().has_value());
}

TEST_F(InMemoryProviderTest, StringEvaluationSuccess) {
InMemoryProvider provider({});
provider.UpdateFlag(
"string_flag",
CreateFlag<std::string>({{"v1", "hello"}, {"v2", "world"}}, "v2"));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

std::unique_ptr<StringResolutionDetails> res =
provider.GetStringEvaluation("string_flag", "default", empty_ctx_);

ASSERT_NE(res, nullptr);
EXPECT_EQ(res->GetValue(), "world");
EXPECT_EQ(res->GetReason(), Reason::kStatic);
EXPECT_THAT(res->GetVariant(), Optional(std::string("v2")));
}

TEST_F(InMemoryProviderTest, IntegerEvaluationSuccess) {
InMemoryProvider provider({});
provider.UpdateFlag("int_flag",
CreateFlag<int64_t>({{"v1", 100}, {"v2", 200}}, "v1"));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

std::unique_ptr<IntResolutionDetails> res =
provider.GetIntegerEvaluation("int_flag", 0, empty_ctx_);

ASSERT_NE(res, nullptr);
EXPECT_EQ(res->GetValue(), 100);
EXPECT_EQ(res->GetReason(), Reason::kStatic);
EXPECT_THAT(res->GetVariant(), Optional(std::string("v1")));
}

TEST_F(InMemoryProviderTest, DoubleEvaluationSuccess) {
InMemoryProvider provider({});
provider.UpdateFlag("double_flag",
CreateFlag<double>({{"v1", 3.14}, {"v2", 2.71}}, "v2"));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

std::unique_ptr<DoubleResolutionDetails> res =
provider.GetDoubleEvaluation("double_flag", 0.0, empty_ctx_);

ASSERT_NE(res, nullptr);
EXPECT_DOUBLE_EQ(res->GetValue(), 2.71);
EXPECT_EQ(res->GetReason(), Reason::kStatic);
EXPECT_THAT(res->GetVariant(), Optional(std::string("v2")));
}

TEST_F(InMemoryProviderTest, ObjectEvaluationSuccess) {
InMemoryProvider provider({});
provider.UpdateFlag("object_flag",
CreateFlag<Value>({{"v1", Value()}}, "v1"));
EXPECT_TRUE(provider.Init(empty_ctx_).ok());

std::unique_ptr<ObjectResolutionDetails> res =
provider.GetObjectEvaluation("object_flag", Value(), empty_ctx_);

ASSERT_NE(res, nullptr);
EXPECT_EQ(res->GetReason(), Reason::kStatic);
EXPECT_THAT(res->GetVariant(), Optional(std::string("v1")));
}

} // namespace openfeature