From 42a776da7900676c95d51ca345f06c8068560337 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 7 Jul 2026 13:19:57 +0000 Subject: [PATCH 01/24] base data structures fot hook implementation. Signed-off-by: NeaguGeorgiana23 --- openfeature/flag_type_value.h | 15 +++++++++++++++ openfeature/hook_hints.h | 14 ++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 openfeature/flag_type_value.h create mode 100644 openfeature/hook_hints.h diff --git a/openfeature/flag_type_value.h b/openfeature/flag_type_value.h new file mode 100644 index 0000000..e52f376 --- /dev/null +++ b/openfeature/flag_type_value.h @@ -0,0 +1,15 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ + +namespace openfeature { + +enum class FlagValueType { + kBoolean, + kString, + kInteger, + kDouble, + kObject +}; + +} // namespace openfeature +#endif // CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ \ No newline at end of file diff --git a/openfeature/hook_hints.h b/openfeature/hook_hints.h new file mode 100644 index 0000000..779bc2a --- /dev/null +++ b/openfeature/hook_hints.h @@ -0,0 +1,14 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_HOOK_HINTS_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_HOOK_HINTS_H_ + +#include +#include +#include + +namespace openfeature { + +using HookHints = std::unordered_map; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_HOOK_HINTS_H_ From e968609ecc3273da48675ac685d794b8be290cf6 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 7 Jul 2026 13:49:42 +0000 Subject: [PATCH 02/24] fix linter Signed-off-by: NeaguGeorgiana23 --- openfeature/flag_type_value.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/openfeature/flag_type_value.h b/openfeature/flag_type_value.h index e52f376..0b4650f 100644 --- a/openfeature/flag_type_value.h +++ b/openfeature/flag_type_value.h @@ -3,13 +3,7 @@ namespace openfeature { -enum class FlagValueType { - kBoolean, - kString, - kInteger, - kDouble, - kObject -}; +enum class FlagValueType { kBoolean, kString, kInteger, kDouble, kObject }; } // namespace openfeature #endif // CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ \ No newline at end of file From 334ec2a64d9a7a0b4362289a2f8d16405b3ce855 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 7 Jul 2026 13:56:50 +0000 Subject: [PATCH 03/24] fix linter Signed-off-by: NeaguGeorgiana23 --- openfeature/flag_type_value.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openfeature/flag_type_value.h b/openfeature/flag_type_value.h index 0b4650f..8b5edfa 100644 --- a/openfeature/flag_type_value.h +++ b/openfeature/flag_type_value.h @@ -1,9 +1,17 @@ #ifndef CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ #define CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ +#include + namespace openfeature { -enum class FlagValueType { kBoolean, kString, kInteger, kDouble, kObject }; +enum class FlagValueType : std::uint8_t { + kBoolean, + kString, + kInteger, + kDouble, + kObject +}; } // namespace openfeature #endif // CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ \ No newline at end of file From 5cc878bf45cdfdf5972ff4e332146655a1936177 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Fri, 10 Jul 2026 11:50:29 +0000 Subject: [PATCH 04/24] Update BUILD file. Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openfeature/BUILD b/openfeature/BUILD index c03031a..b022b80 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -80,6 +80,12 @@ cc_library( include_prefix = "openfeature", ) +cc_library( + name = "flag_type_value", + hdrs = ["flag_type_value.h"], + include_prefix = "openfeature", +) + cc_library( name = "global_context_manager", srcs = ["global_context_manager.cpp"], @@ -90,6 +96,12 @@ cc_library( ], ) +cc_library( + name = "hook_hints", + hdrs = ["hook_hints.h"], + include_prefix = "openfeature", +) + cc_library( name = "metadata", hdrs = ["metadata.h"], From bd6643026a49fba7ac0efa3a87070d50a80c608c Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Fri, 10 Jul 2026 11:56:00 +0000 Subject: [PATCH 05/24] Correct include guards. Signed-off-by: NeaguGeorgiana23 --- openfeature/flag_type_value.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openfeature/flag_type_value.h b/openfeature/flag_type_value.h index 8b5edfa..3a69cba 100644 --- a/openfeature/flag_type_value.h +++ b/openfeature/flag_type_value.h @@ -1,5 +1,5 @@ -#ifndef CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ -#define CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_FLAG_TYPE_VALUE_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_FLAG_TYPE_VALUE_H_ #include @@ -14,4 +14,4 @@ enum class FlagValueType : std::uint8_t { }; } // namespace openfeature -#endif // CPP_SDK_INCLUDE_OPENFEATURE_FLAG_VALUE_TYPE_H_ \ No newline at end of file +#endif // CPP_SDK_INCLUDE_OPENFEATURE_FLAG_TYPE_VALUE_H_ From 53326105fd880254cafdcb7fbf9a21ac81e27ab2 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Fri, 10 Jul 2026 12:18:10 +0000 Subject: [PATCH 06/24] feat: Add HookData class. Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 7 ++ openfeature/hook_data.cpp | 21 +++++ openfeature/hook_data.h | 35 ++++++++ test/BUILD | 9 +++ test/hook_data_test.cpp | 163 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 235 insertions(+) create mode 100644 openfeature/hook_data.cpp create mode 100644 openfeature/hook_data.h create mode 100644 test/hook_data_test.cpp diff --git a/openfeature/BUILD b/openfeature/BUILD index b022b80..4508f65 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -96,6 +96,13 @@ cc_library( ], ) +cc_library( + name = "hook_data", + srcs = ["hook_data.cpp"], + hdrs = ["hook_data.h"], + include_prefix = "openfeature", +) + cc_library( name = "hook_hints", hdrs = ["hook_hints.h"], diff --git a/openfeature/hook_data.cpp b/openfeature/hook_data.cpp new file mode 100644 index 0000000..ca4a16a --- /dev/null +++ b/openfeature/hook_data.cpp @@ -0,0 +1,21 @@ +#include "openfeature/hook_data.h" + +#include +#include +#include + +namespace openfeature { + +void HookData::Set(std::string key, std::any value) { + data_.insert_or_assign(std::move(key), std::move(value)); +} + +const std::any* HookData::Get(const std::string& key) const { + auto it = data_.find(key); + if (it != data_.end()) { + return &it->second; + } + return nullptr; +} + +} // namespace openfeature diff --git a/openfeature/hook_data.h b/openfeature/hook_data.h new file mode 100644 index 0000000..5c5e6f4 --- /dev/null +++ b/openfeature/hook_data.h @@ -0,0 +1,35 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_HOOK_DATA_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_HOOK_DATA_H_ + +#include +#include +#include + +namespace openfeature { + +// HookData provides a way for hooks to maintain state across their execution +// stages. Each hook instance gets its own isolated data store that persists +// only for the duration of a single flag evaluation. +class HookData { + public: + HookData() = default; + + void Set(std::string key, std::any value); + + const std::any* Get(const std::string& key) const; + + template + T* GetAs(const std::string& key) { + auto it = data_.find(key); + if (it != data_.end()) { + return std::any_cast(&it->second); + } + return nullptr; + } + + private: + std::unordered_map data_; +}; + +} // namespace openfeature +#endif // CPP_SDK_INCLUDE_OPENFEATURE_HOOK_DATA_H_ diff --git a/test/BUILD b/test/BUILD index aff340c..d6a472c 100644 --- a/test/BUILD +++ b/test/BUILD @@ -97,3 +97,12 @@ cc_test( "@googletest//:gtest_main", ], ) + +cc_test( + name = "hook_data_test", + srcs = ["hook_data_test.cpp"], + deps = [ + "//openfeature:hook_data", + "@googletest//:gtest_main", + ], +) \ No newline at end of file diff --git a/test/hook_data_test.cpp b/test/hook_data_test.cpp new file mode 100644 index 0000000..d45fd4a --- /dev/null +++ b/test/hook_data_test.cpp @@ -0,0 +1,163 @@ +#include "openfeature/hook_data.h" + +#include + +#include +#include +#include + +namespace openfeature { +namespace { + +constexpr char kNonExistentKey[] = "non_existent_key"; + +constexpr char kIntKey[] = "int_attr"; +constexpr int kIntValue = 42; + +constexpr char kStringKey[] = "string_attr"; +constexpr char kStringValue[] = "hello world"; + +constexpr char kBoolKey[] = "bool_attr"; +constexpr bool kBoolValue = true; + +constexpr char kDoubleKey[] = "double_attr"; +constexpr double kDoubleValue = 3.14159; + +constexpr char kCounterKey[] = "counter"; +constexpr int kInitialCounterValue = 100; +constexpr int kUpdatedCounterValue = 200; + +constexpr char kDynamicKey[] = "dynamic_key"; +constexpr int kDynamicIntValue = 12345; +constexpr char kDynamicStringValue[] = "now a string"; + +constexpr char kMyIntKey[] = "my_int"; +constexpr int kMyIntValue = 99; + +constexpr char kCustomStateKey[] = "custom_state"; +constexpr int kInitialCallCount = 1; +constexpr char kBeforeStageName[] = "before_stage"; + +constexpr char kStageTrackerKey[] = "stage_tracker"; +constexpr char kBeforeStage[] = "before"; +constexpr char kAfterStage[] = "after"; +constexpr int kUpdatedCallCount = 2; + +constexpr char kSharedKey[] = "shared_key"; +constexpr char kSharedDataValue[] = "shared data"; +constexpr int kExpectedUseCount = 2; + +class HookDataTest : public ::testing::Test { + protected: + HookData hook_data_; +}; + +TEST_F(HookDataTest, DefaultConstructorCreatesEmptyData) { + EXPECT_EQ(hook_data_.Get(kNonExistentKey), nullptr); + EXPECT_EQ(hook_data_.GetAs(kNonExistentKey), nullptr); +} + +TEST_F(HookDataTest, SetAndGetPrimitiveTypes) { + hook_data_.Set(kIntKey, kIntValue); + hook_data_.Set(kStringKey, std::string(kStringValue)); + hook_data_.Set(kBoolKey, kBoolValue); + hook_data_.Set(kDoubleKey, kDoubleValue); + + const std::any* int_any = hook_data_.Get(kIntKey); + ASSERT_NE(int_any, nullptr); + EXPECT_EQ(std::any_cast(*int_any), kIntValue); + + int* int_ptr = hook_data_.GetAs(kIntKey); + ASSERT_NE(int_ptr, nullptr); + EXPECT_EQ(*int_ptr, kIntValue); + + std::string* str_ptr = hook_data_.GetAs(kStringKey); + ASSERT_NE(str_ptr, nullptr); + EXPECT_EQ(*str_ptr, kStringValue); + + bool* bool_ptr = hook_data_.GetAs(kBoolKey); + ASSERT_NE(bool_ptr, nullptr); + EXPECT_EQ(*bool_ptr, kBoolValue); + + double* double_ptr = hook_data_.GetAs(kDoubleKey); + ASSERT_NE(double_ptr, nullptr); + EXPECT_DOUBLE_EQ(*double_ptr, kDoubleValue); +} + +TEST_F(HookDataTest, SetOverwritesExistingKeyWithSameType) { + hook_data_.Set(kCounterKey, kInitialCounterValue); + ASSERT_NE(hook_data_.GetAs(kCounterKey), nullptr); + EXPECT_EQ(*hook_data_.GetAs(kCounterKey), kInitialCounterValue); + + hook_data_.Set(kCounterKey, kUpdatedCounterValue); + ASSERT_NE(hook_data_.GetAs(kCounterKey), nullptr); + EXPECT_EQ(*hook_data_.GetAs(kCounterKey), kUpdatedCounterValue); +} + +TEST_F(HookDataTest, SetOverwritesExistingKeyWithDifferentType) { + hook_data_.Set(kDynamicKey, kDynamicIntValue); + EXPECT_NE(hook_data_.GetAs(kDynamicKey), nullptr); + + hook_data_.Set(kDynamicKey, std::string(kDynamicStringValue)); + EXPECT_EQ(hook_data_.GetAs(kDynamicKey), nullptr); + + std::string* str_ptr = hook_data_.GetAs(kDynamicKey); + ASSERT_NE(str_ptr, nullptr); + EXPECT_EQ(*str_ptr, kDynamicStringValue); +} + +TEST_F(HookDataTest, GetAsReturnsNullptrOnTypeMismatch) { + hook_data_.Set(kMyIntKey, kMyIntValue); + + EXPECT_EQ(hook_data_.GetAs(kMyIntKey), nullptr); + EXPECT_EQ(hook_data_.GetAs(kMyIntKey), nullptr); + EXPECT_EQ(hook_data_.GetAs(kMyIntKey), nullptr); +} + +// Custom struct for testing user-defined type storage across hooks. +struct CustomEvaluationState { + int call_count; + std::string step_name; +}; + +TEST_F(HookDataTest, SetAndGetCustomStruct) { + CustomEvaluationState state{kInitialCallCount, kBeforeStageName}; + hook_data_.Set(kCustomStateKey, state); + + CustomEvaluationState* retrieved = + hook_data_.GetAs(kCustomStateKey); + ASSERT_NE(retrieved, nullptr); + EXPECT_EQ(retrieved->call_count, kInitialCallCount); + EXPECT_EQ(retrieved->step_name, kBeforeStageName); +} + +TEST_F(HookDataTest, ModifyStoredValueInPlaceViaGetAs) { + hook_data_.Set(kStageTrackerKey, + CustomEvaluationState{kInitialCallCount, kBeforeStage}); + + CustomEvaluationState* state_ptr = + hook_data_.GetAs(kStageTrackerKey); + ASSERT_NE(state_ptr, nullptr); + state_ptr->call_count++; + state_ptr->step_name = kAfterStage; + + CustomEvaluationState* updated_ptr = + hook_data_.GetAs(kStageTrackerKey); + ASSERT_NE(updated_ptr, nullptr); + EXPECT_EQ(updated_ptr->call_count, kUpdatedCallCount); + EXPECT_EQ(updated_ptr->step_name, kAfterStage); +} + +TEST_F(HookDataTest, SetAndGetSharedPtr) { + auto ptr = std::make_shared(kSharedDataValue); + hook_data_.Set(kSharedKey, ptr); + + auto* retrieved = hook_data_.GetAs>(kSharedKey); + ASSERT_NE(retrieved, nullptr); + ASSERT_NE(*retrieved, nullptr); + EXPECT_EQ(**retrieved, kSharedDataValue); + EXPECT_EQ(retrieved->use_count(), kExpectedUseCount); +} + +} // namespace +} // namespace openfeature From c8fc92df4de87a4d3bff31dfbebf119d7ad66a29 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Fri, 10 Jul 2026 15:49:47 +0000 Subject: [PATCH 07/24] fix linter Signed-off-by: NeaguGeorgiana23 --- openfeature/hook_data.cpp | 6 +++--- openfeature/hook_data.h | 6 +++--- test/hook_data_test.cpp | 34 +++++++++++++++++----------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/openfeature/hook_data.cpp b/openfeature/hook_data.cpp index ca4a16a..954781d 100644 --- a/openfeature/hook_data.cpp +++ b/openfeature/hook_data.cpp @@ -11,9 +11,9 @@ void HookData::Set(std::string key, std::any value) { } const std::any* HookData::Get(const std::string& key) const { - auto it = data_.find(key); - if (it != data_.end()) { - return &it->second; + auto it_key = data_.find(key); + if (it_key != data_.end()) { + return &it_key->second; } return nullptr; } diff --git a/openfeature/hook_data.h b/openfeature/hook_data.h index 5c5e6f4..f32053c 100644 --- a/openfeature/hook_data.h +++ b/openfeature/hook_data.h @@ -20,9 +20,9 @@ class HookData { template T* GetAs(const std::string& key) { - auto it = data_.find(key); - if (it != data_.end()) { - return std::any_cast(&it->second); + auto it_key = data_.find(key); + if (it_key != data_.end()) { + return std::any_cast(&it_key->second); } return nullptr; } diff --git a/test/hook_data_test.cpp b/test/hook_data_test.cpp index d45fd4a..a8ea58c 100644 --- a/test/hook_data_test.cpp +++ b/test/hook_data_test.cpp @@ -9,42 +9,42 @@ namespace openfeature { namespace { -constexpr char kNonExistentKey[] = "non_existent_key"; +constexpr std::string_view kNonExistentKey = "non_existent_key"; -constexpr char kIntKey[] = "int_attr"; +constexpr std::string_view kIntKey = "int_attr"; constexpr int kIntValue = 42; -constexpr char kStringKey[] = "string_attr"; -constexpr char kStringValue[] = "hello world"; +constexpr std::string_view kStringKey = "string_attr"; +constexpr std::string_view kStringValue = "hello world"; -constexpr char kBoolKey[] = "bool_attr"; +constexpr std::string_view kBoolKey = "bool_attr"; constexpr bool kBoolValue = true; -constexpr char kDoubleKey[] = "double_attr"; +constexpr std::string_view kDoubleKey = "double_attr"; constexpr double kDoubleValue = 3.14159; -constexpr char kCounterKey[] = "counter"; +constexpr std::string_view kCounterKey = "counter"; constexpr int kInitialCounterValue = 100; constexpr int kUpdatedCounterValue = 200; -constexpr char kDynamicKey[] = "dynamic_key"; +constexpr std::string_view kDynamicKey = "dynamic_key"; constexpr int kDynamicIntValue = 12345; -constexpr char kDynamicStringValue[] = "now a string"; +constexpr std::string_view kDynamicStringValue = "now a string"; -constexpr char kMyIntKey[] = "my_int"; +constexpr std::string_view kMyIntKey = "my_int"; constexpr int kMyIntValue = 99; -constexpr char kCustomStateKey[] = "custom_state"; +constexpr std::string_view kCustomStateKey = "custom_state"; constexpr int kInitialCallCount = 1; -constexpr char kBeforeStageName[] = "before_stage"; +constexpr std::string_view kBeforeStageName = "before_stage"; -constexpr char kStageTrackerKey[] = "stage_tracker"; -constexpr char kBeforeStage[] = "before"; -constexpr char kAfterStage[] = "after"; +constexpr std::string_view kStageTrackerKey = "stage_tracker"; +constexpr std::string_view kBeforeStage = "before"; +constexpr std::string_view kAfterStage = "after"; constexpr int kUpdatedCallCount = 2; -constexpr char kSharedKey[] = "shared_key"; -constexpr char kSharedDataValue[] = "shared data"; +constexpr std::string_view kSharedKey = "shared_key"; +constexpr std::string_view kSharedDataValue = "shared data"; constexpr int kExpectedUseCount = 2; class HookDataTest : public ::testing::Test { From 3f5f4deed1950d06e844fc214a188ec28f62b16e Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Fri, 10 Jul 2026 16:27:52 +0000 Subject: [PATCH 08/24] fix linter Signed-off-by: NeaguGeorgiana23 --- openfeature/hook_data.cpp | 8 ++++---- openfeature/hook_data.h | 9 +++++---- test/hook_data_test.cpp | 9 ++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openfeature/hook_data.cpp b/openfeature/hook_data.cpp index 954781d..eca7074 100644 --- a/openfeature/hook_data.cpp +++ b/openfeature/hook_data.cpp @@ -6,12 +6,12 @@ namespace openfeature { -void HookData::Set(std::string key, std::any value) { - data_.insert_or_assign(std::move(key), std::move(value)); +void HookData::Set(std::string_view key, std::any value) { + data_.insert_or_assign(std::string(key), std::move(value)); } -const std::any* HookData::Get(const std::string& key) const { - auto it_key = data_.find(key); +const std::any* HookData::Get(std::string_view key) const { + auto it_key = data_.find(std::string(key)); if (it_key != data_.end()) { return &it_key->second; } diff --git a/openfeature/hook_data.h b/openfeature/hook_data.h index f32053c..fb8989a 100644 --- a/openfeature/hook_data.h +++ b/openfeature/hook_data.h @@ -3,6 +3,7 @@ #include #include +#include #include namespace openfeature { @@ -14,13 +15,13 @@ class HookData { public: HookData() = default; - void Set(std::string key, std::any value); + void Set(std::string_view key, std::any value); - const std::any* Get(const std::string& key) const; + const std::any* Get(std::string_view key) const; template - T* GetAs(const std::string& key) { - auto it_key = data_.find(key); + T* GetAs(std::string_view key) { + auto it_key = data_.find(std::string(key)); if (it_key != data_.end()) { return std::any_cast(&it_key->second); } diff --git a/test/hook_data_test.cpp b/test/hook_data_test.cpp index a8ea58c..67e87d4 100644 --- a/test/hook_data_test.cpp +++ b/test/hook_data_test.cpp @@ -7,7 +7,6 @@ #include namespace openfeature { -namespace { constexpr std::string_view kNonExistentKey = "non_existent_key"; @@ -121,7 +120,7 @@ struct CustomEvaluationState { }; TEST_F(HookDataTest, SetAndGetCustomStruct) { - CustomEvaluationState state{kInitialCallCount, kBeforeStageName}; + CustomEvaluationState state{kInitialCallCount, std::string(kBeforeStageName)}; hook_data_.Set(kCustomStateKey, state); CustomEvaluationState* retrieved = @@ -132,8 +131,9 @@ TEST_F(HookDataTest, SetAndGetCustomStruct) { } TEST_F(HookDataTest, ModifyStoredValueInPlaceViaGetAs) { - hook_data_.Set(kStageTrackerKey, - CustomEvaluationState{kInitialCallCount, kBeforeStage}); + hook_data_.Set( + kStageTrackerKey, + CustomEvaluationState{kInitialCallCount, std::string(kBeforeStage)}); CustomEvaluationState* state_ptr = hook_data_.GetAs(kStageTrackerKey); @@ -159,5 +159,4 @@ TEST_F(HookDataTest, SetAndGetSharedPtr) { EXPECT_EQ(retrieved->use_count(), kExpectedUseCount); } -} // namespace } // namespace openfeature From b0f3a154226f2ecd18e86d9624f3bd75348923a2 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Fri, 10 Jul 2026 16:34:44 +0000 Subject: [PATCH 09/24] fix linter Signed-off-by: NeaguGeorgiana23 --- test/hook_data_test.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/hook_data_test.cpp b/test/hook_data_test.cpp index 67e87d4..b535bf8 100644 --- a/test/hook_data_test.cpp +++ b/test/hook_data_test.cpp @@ -70,7 +70,7 @@ TEST_F(HookDataTest, SetAndGetPrimitiveTypes) { ASSERT_NE(int_ptr, nullptr); EXPECT_EQ(*int_ptr, kIntValue); - std::string* str_ptr = hook_data_.GetAs(kStringKey); + auto str_ptr = hook_data_.GetAs(kStringKey); ASSERT_NE(str_ptr, nullptr); EXPECT_EQ(*str_ptr, kStringValue); @@ -78,7 +78,7 @@ TEST_F(HookDataTest, SetAndGetPrimitiveTypes) { ASSERT_NE(bool_ptr, nullptr); EXPECT_EQ(*bool_ptr, kBoolValue); - double* double_ptr = hook_data_.GetAs(kDoubleKey); + auto double_ptr = hook_data_.GetAs(kDoubleKey); ASSERT_NE(double_ptr, nullptr); EXPECT_DOUBLE_EQ(*double_ptr, kDoubleValue); } @@ -100,7 +100,7 @@ TEST_F(HookDataTest, SetOverwritesExistingKeyWithDifferentType) { hook_data_.Set(kDynamicKey, std::string(kDynamicStringValue)); EXPECT_EQ(hook_data_.GetAs(kDynamicKey), nullptr); - std::string* str_ptr = hook_data_.GetAs(kDynamicKey); + auto str_ptr = hook_data_.GetAs(kDynamicKey); ASSERT_NE(str_ptr, nullptr); EXPECT_EQ(*str_ptr, kDynamicStringValue); } @@ -123,8 +123,7 @@ TEST_F(HookDataTest, SetAndGetCustomStruct) { CustomEvaluationState state{kInitialCallCount, std::string(kBeforeStageName)}; hook_data_.Set(kCustomStateKey, state); - CustomEvaluationState* retrieved = - hook_data_.GetAs(kCustomStateKey); + auto retrieved = hook_data_.GetAs(kCustomStateKey); ASSERT_NE(retrieved, nullptr); EXPECT_EQ(retrieved->call_count, kInitialCallCount); EXPECT_EQ(retrieved->step_name, kBeforeStageName); @@ -135,14 +134,12 @@ TEST_F(HookDataTest, ModifyStoredValueInPlaceViaGetAs) { kStageTrackerKey, CustomEvaluationState{kInitialCallCount, std::string(kBeforeStage)}); - CustomEvaluationState* state_ptr = - hook_data_.GetAs(kStageTrackerKey); + auto state_ptr = hook_data_.GetAs(kStageTrackerKey); ASSERT_NE(state_ptr, nullptr); state_ptr->call_count++; state_ptr->step_name = kAfterStage; - CustomEvaluationState* updated_ptr = - hook_data_.GetAs(kStageTrackerKey); + auto updated_ptr = hook_data_.GetAs(kStageTrackerKey); ASSERT_NE(updated_ptr, nullptr); EXPECT_EQ(updated_ptr->call_count, kUpdatedCallCount); EXPECT_EQ(updated_ptr->step_name, kAfterStage); From e9e88524af69ea6ba4bf571b2ef293722e8c05ce Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Fri, 10 Jul 2026 16:39:53 +0000 Subject: [PATCH 10/24] fix linter Signed-off-by: NeaguGeorgiana23 --- test/hook_data_test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/hook_data_test.cpp b/test/hook_data_test.cpp index b535bf8..10c7512 100644 --- a/test/hook_data_test.cpp +++ b/test/hook_data_test.cpp @@ -70,7 +70,7 @@ TEST_F(HookDataTest, SetAndGetPrimitiveTypes) { ASSERT_NE(int_ptr, nullptr); EXPECT_EQ(*int_ptr, kIntValue); - auto str_ptr = hook_data_.GetAs(kStringKey); + auto* str_ptr = hook_data_.GetAs(kStringKey); ASSERT_NE(str_ptr, nullptr); EXPECT_EQ(*str_ptr, kStringValue); @@ -78,7 +78,7 @@ TEST_F(HookDataTest, SetAndGetPrimitiveTypes) { ASSERT_NE(bool_ptr, nullptr); EXPECT_EQ(*bool_ptr, kBoolValue); - auto double_ptr = hook_data_.GetAs(kDoubleKey); + auto* double_ptr = hook_data_.GetAs(kDoubleKey); ASSERT_NE(double_ptr, nullptr); EXPECT_DOUBLE_EQ(*double_ptr, kDoubleValue); } @@ -100,7 +100,7 @@ TEST_F(HookDataTest, SetOverwritesExistingKeyWithDifferentType) { hook_data_.Set(kDynamicKey, std::string(kDynamicStringValue)); EXPECT_EQ(hook_data_.GetAs(kDynamicKey), nullptr); - auto str_ptr = hook_data_.GetAs(kDynamicKey); + auto* str_ptr = hook_data_.GetAs(kDynamicKey); ASSERT_NE(str_ptr, nullptr); EXPECT_EQ(*str_ptr, kDynamicStringValue); } @@ -123,7 +123,7 @@ TEST_F(HookDataTest, SetAndGetCustomStruct) { CustomEvaluationState state{kInitialCallCount, std::string(kBeforeStageName)}; hook_data_.Set(kCustomStateKey, state); - auto retrieved = hook_data_.GetAs(kCustomStateKey); + auto* retrieved = hook_data_.GetAs(kCustomStateKey); ASSERT_NE(retrieved, nullptr); EXPECT_EQ(retrieved->call_count, kInitialCallCount); EXPECT_EQ(retrieved->step_name, kBeforeStageName); @@ -134,12 +134,12 @@ TEST_F(HookDataTest, ModifyStoredValueInPlaceViaGetAs) { kStageTrackerKey, CustomEvaluationState{kInitialCallCount, std::string(kBeforeStage)}); - auto state_ptr = hook_data_.GetAs(kStageTrackerKey); + auto* state_ptr = hook_data_.GetAs(kStageTrackerKey); ASSERT_NE(state_ptr, nullptr); state_ptr->call_count++; state_ptr->step_name = kAfterStage; - auto updated_ptr = hook_data_.GetAs(kStageTrackerKey); + auto* updated_ptr = hook_data_.GetAs(kStageTrackerKey); ASSERT_NE(updated_ptr, nullptr); EXPECT_EQ(updated_ptr->call_count, kUpdatedCallCount); EXPECT_EQ(updated_ptr->step_name, kAfterStage); From e333df0ad37803f54dcd95ac80da42e738446e85 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Mon, 13 Jul 2026 08:50:30 +0000 Subject: [PATCH 11/24] add HookContext structure. Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 15 +++ openfeature/hook_context.cpp | 68 +++++++++++++ openfeature/hook_context.h | 50 ++++++++++ test/BUILD | 12 +++ test/hook_context_test.cpp | 185 +++++++++++++++++++++++++++++++++++ 5 files changed, 330 insertions(+) create mode 100644 openfeature/hook_context.cpp create mode 100644 openfeature/hook_context.h create mode 100644 test/hook_context_test.cpp diff --git a/openfeature/BUILD b/openfeature/BUILD index 4508f65..0cd195d 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -96,6 +96,21 @@ cc_library( ], ) +cc_library( + name = "hook_context", + srcs = ["hook_context.cpp"], + hdrs = ["hook_context.h"], + include_prefix = "openfeature", + deps = [ + ":evaluation_context", + ":flag_metadata", + ":flag_type_value", + ":hook_data", + ":metadata", + ":value", + ], +) + cc_library( name = "hook_data", srcs = ["hook_data.cpp"], diff --git a/openfeature/hook_context.cpp b/openfeature/hook_context.cpp new file mode 100644 index 0000000..47c503c --- /dev/null +++ b/openfeature/hook_context.cpp @@ -0,0 +1,68 @@ +#include "openfeature/hook_context.h" + +#include +#include + +#include "openfeature/evaluation_context.h" +#include "openfeature/flag_metadata.h" +#include "openfeature/flag_type_value.h" +#include "openfeature/hook_data.h" +#include "openfeature/metadata.h" +#include "openfeature/value.h" + +namespace openfeature { + +template +HookContext::HookContext(std::string flag_key, FlagValueType type, + T default_value, EvaluationContext ctx, + Metadata client_metadata, Metadata provider_metadata, + std::shared_ptr hook_data) + : flag_key_(std::move(flag_key)), + type_(type), + default_value_(std::move(default_value)), + ctx_(std::move(ctx)), + client_metadata_(std::move(client_metadata)), + provider_metadata_(std::move(provider_metadata)), + hook_data_(std::move(hook_data)) {} + +template +const std::string& HookContext::GetFlagKey() const { return flag_key_; } + +template +FlagValueType HookContext::GetType() const { return type_; } + +template +const T& HookContext::GetDefaultValue() const { return default_value_; } + +template +const EvaluationContext& HookContext::GetEvaluationContext() const { + return ctx_; +} + +template +void HookContext::SetEvaluationContext(EvaluationContext ctx) { + ctx_ = std::move(ctx); +} + +template +const Metadata& HookContext::GetClientMetadata() const { + return client_metadata_; +} + +template +const Metadata& HookContext::GetProviderMetadata() const { + return provider_metadata_; +} + +template +std::shared_ptr HookContext::GetHookData() const { + return hook_data_; +} + +template class HookContext; +template class HookContext; +template class HookContext; +template class HookContext; +template class HookContext; + +} // namespace openfeature diff --git a/openfeature/hook_context.h b/openfeature/hook_context.h new file mode 100644 index 0000000..935dae9 --- /dev/null +++ b/openfeature/hook_context.h @@ -0,0 +1,50 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_HOOK_CONTEXT_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_HOOK_CONTEXT_H_ + +#include +#include + +#include "openfeature/evaluation_context.h" +#include "openfeature/flag_metadata.h" +#include "openfeature/flag_type_value.h" +#include "openfeature/hook_data.h" +#include "openfeature/metadata.h" +#include "openfeature/value.h" + +namespace openfeature { + +template +class HookContext { + public: + HookContext(std::string flag_key, FlagValueType type, T default_value, + EvaluationContext ctx, Metadata client_metadata, + Metadata provider_metadata, std::shared_ptr hook_data); + + const std::string& GetFlagKey() const; + FlagValueType GetType() const; + const T& GetDefaultValue() const; + const EvaluationContext& GetEvaluationContext() const; + void SetEvaluationContext(EvaluationContext ctx); + const Metadata& GetClientMetadata() const; + const Metadata& GetProviderMetadata() const; + std::shared_ptr GetHookData() const; + + private: + std::string flag_key_; + FlagValueType type_; + T default_value_; + EvaluationContext ctx_; + Metadata client_metadata_; + Metadata provider_metadata_; + std::shared_ptr hook_data_; +}; + +// Type aliases for common types. +using BoolHookContext = HookContext; +using StringHookContext = HookContext; +using IntHookContext = HookContext; +using DoubleHookContext = HookContext; +using ObjectHookContext = HookContext; + +} // namespace openfeature +#endif // CPP_SDK_INCLUDE_OPENFEATURE_HOOK_CONTEXT_H_ diff --git a/test/BUILD b/test/BUILD index d6a472c..ca5df9d 100644 --- a/test/BUILD +++ b/test/BUILD @@ -105,4 +105,16 @@ cc_test( "//openfeature:hook_data", "@googletest//:gtest_main", ], +) + +cc_test( + name = "hook_context_test", + srcs = ["hook_context_test.cpp"], + deps = [ + "//openfeature:evaluation_context", + "//openfeature:hook_context", + "//openfeature:hook_data", + "//openfeature:value", + "@googletest//:gtest_main", + ], ) \ No newline at end of file diff --git a/test/hook_context_test.cpp b/test/hook_context_test.cpp new file mode 100644 index 0000000..16471af --- /dev/null +++ b/test/hook_context_test.cpp @@ -0,0 +1,185 @@ +#include "openfeature/hook_context.h" + +#include + +#include +#include + +#include "openfeature/evaluation_context.h" +#include "openfeature/flag_metadata.h" +#include "openfeature/flag_type_value.h" +#include "openfeature/hook_data.h" +#include "openfeature/metadata.h" +#include "openfeature/value.h" + +namespace openfeature { + +class HookContextTest : public ::testing::Test { + protected: + HookContextTest() + : initial_ctx_(EvaluationContext::Builder() + .WithTargetingKey("initial-user") + .WithAttribute("env", std::string("test")) + .build()), + client_metadata_{"test-client"}, + provider_metadata_{"test-provider"}, + hook_data_(std::make_shared()) {} + + EvaluationContext initial_ctx_; + Metadata client_metadata_; + Metadata provider_metadata_; + std::shared_ptr hook_data_; +}; + +TEST_F(HookContextTest, ConstructorAndAccessorsForBool) { + constexpr FlagValueType kType = FlagValueType::kBoolean; + constexpr bool kDefaultValue = true; + + BoolHookContext hook_ctx("bool-flag", kType, kDefaultValue, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + + EXPECT_EQ(hook_ctx.GetFlagKey(), "bool-flag"); + EXPECT_EQ(hook_ctx.GetType(), kType); + EXPECT_EQ(hook_ctx.GetDefaultValue(), kDefaultValue); + + ASSERT_TRUE(hook_ctx.GetEvaluationContext().GetTargetingKey().has_value()); + EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), + "initial-user"); + + EXPECT_EQ(hook_ctx.GetClientMetadata().name, "test-client"); + EXPECT_EQ(hook_ctx.GetProviderMetadata().name, "test-provider"); + EXPECT_EQ(hook_ctx.GetHookData(), hook_data_); +} + +TEST_F(HookContextTest, ConstructorAndAccessorsForString) { + constexpr FlagValueType kType = FlagValueType::kString; + const std::string kDefaultValue = "default-string"; + + StringHookContext hook_ctx("string-flag", kType, kDefaultValue, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + + EXPECT_EQ(hook_ctx.GetFlagKey(), "string-flag"); + EXPECT_EQ(hook_ctx.GetType(), kType); + EXPECT_EQ(hook_ctx.GetDefaultValue(), kDefaultValue); + + ASSERT_TRUE(hook_ctx.GetEvaluationContext().GetTargetingKey().has_value()); + EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), + "initial-user"); + + EXPECT_EQ(hook_ctx.GetClientMetadata().name, "test-client"); + EXPECT_EQ(hook_ctx.GetProviderMetadata().name, "test-provider"); + EXPECT_EQ(hook_ctx.GetHookData(), hook_data_); +} + +TEST_F(HookContextTest, ConstructorAndAccessorsForInteger) { + constexpr FlagValueType kType = FlagValueType::kInteger; + constexpr int64_t kDefaultValue = 123456789LL; + + IntHookContext hook_ctx("int-flag", kType, kDefaultValue, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + + EXPECT_EQ(hook_ctx.GetFlagKey(), "int-flag"); + EXPECT_EQ(hook_ctx.GetType(), kType); + EXPECT_EQ(hook_ctx.GetDefaultValue(), kDefaultValue); + + ASSERT_TRUE(hook_ctx.GetEvaluationContext().GetTargetingKey().has_value()); + EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), + "initial-user"); + + EXPECT_EQ(hook_ctx.GetClientMetadata().name, "test-client"); + EXPECT_EQ(hook_ctx.GetProviderMetadata().name, "test-provider"); + EXPECT_EQ(hook_ctx.GetHookData(), hook_data_); +} + +TEST_F(HookContextTest, ConstructorAndAccessorsForDouble) { + constexpr FlagValueType kType = FlagValueType::kDouble; + constexpr double kDefaultValue = 3.14159265359; + + DoubleHookContext hook_ctx("double-flag", kType, kDefaultValue, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + + EXPECT_EQ(hook_ctx.GetFlagKey(), "double-flag"); + EXPECT_EQ(hook_ctx.GetType(), kType); + EXPECT_DOUBLE_EQ(hook_ctx.GetDefaultValue(), kDefaultValue); + + ASSERT_TRUE(hook_ctx.GetEvaluationContext().GetTargetingKey().has_value()); + EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), + "initial-user"); + + EXPECT_EQ(hook_ctx.GetClientMetadata().name, "test-client"); + EXPECT_EQ(hook_ctx.GetProviderMetadata().name, "test-provider"); + EXPECT_EQ(hook_ctx.GetHookData(), hook_data_); +} + +TEST_F(HookContextTest, ConstructorAndAccessorsForObject) { + constexpr FlagValueType kType = FlagValueType::kObject; + Value kDefaultValue(std::string("json-or-structure")); + + ObjectHookContext hook_ctx("object-flag", kType, kDefaultValue, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + + EXPECT_EQ(hook_ctx.GetFlagKey(), "object-flag"); + EXPECT_EQ(hook_ctx.GetType(), kType); + ASSERT_TRUE(hook_ctx.GetDefaultValue().IsString()); + EXPECT_EQ(hook_ctx.GetDefaultValue().AsString().value(), "json-or-structure"); + + ASSERT_TRUE(hook_ctx.GetEvaluationContext().GetTargetingKey().has_value()); + EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), + "initial-user"); + + EXPECT_EQ(hook_ctx.GetClientMetadata().name, "test-client"); + EXPECT_EQ(hook_ctx.GetProviderMetadata().name, "test-provider"); + EXPECT_EQ(hook_ctx.GetHookData(), hook_data_); +} + +TEST_F(HookContextTest, SetEvaluationContextUpdatesContext) { + BoolHookContext hook_ctx("flag-key", FlagValueType::kBoolean, false, + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); + + ASSERT_TRUE(hook_ctx.GetEvaluationContext().GetTargetingKey().has_value()); + EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), + "initial-user"); + + EvaluationContext updated_ctx = + EvaluationContext::Builder() + .WithTargetingKey("updated-user") + .WithAttribute("env", std::string("prod")) + .build(); + + hook_ctx.SetEvaluationContext(std::move(updated_ctx)); + + ASSERT_TRUE(hook_ctx.GetEvaluationContext().GetTargetingKey().has_value()); + EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), + "updated-user"); + + const std::any* env_val = hook_ctx.GetEvaluationContext().GetValue("env"); + ASSERT_NE(env_val, nullptr); + EXPECT_EQ(std::any_cast(*env_val), "prod"); +} + +TEST_F(HookContextTest, HookDataSharesStateAndAllowsMutations) { + BoolHookContext hook_ctx("flag-key", FlagValueType::kBoolean, true, + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); + + ASSERT_NE(hook_ctx.GetHookData(), nullptr); + EXPECT_EQ(hook_ctx.GetHookData()->Get("custom_key"), nullptr); + + hook_ctx.GetHookData()->Set("custom_key", std::string("stage_before")); + + auto* retrieved = + hook_ctx.GetHookData()->GetAs("custom_key"); + ASSERT_NE(retrieved, nullptr); + EXPECT_EQ(*retrieved, "stage_before"); +} + +TEST_F(HookContextTest, HandlesNullptrHookData) { + BoolHookContext hook_ctx("flag-key", FlagValueType::kBoolean, false, + initial_ctx_, client_metadata_, provider_metadata_, + nullptr); + + EXPECT_EQ(hook_ctx.GetHookData(), nullptr); +} + +} // namespace openfeature From c59de28b23369b6eea95c22f0b9b0727a4ca2e03 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Mon, 13 Jul 2026 08:59:47 +0000 Subject: [PATCH 12/24] fix linter Signed-off-by: NeaguGeorgiana23 --- openfeature/hook_context.cpp | 15 +++++++++++---- test/hook_context_test.cpp | 12 +++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/openfeature/hook_context.cpp b/openfeature/hook_context.cpp index 47c503c..cabe909 100644 --- a/openfeature/hook_context.cpp +++ b/openfeature/hook_context.cpp @@ -15,7 +15,8 @@ namespace openfeature { template HookContext::HookContext(std::string flag_key, FlagValueType type, T default_value, EvaluationContext ctx, - Metadata client_metadata, Metadata provider_metadata, + Metadata client_metadata, + Metadata provider_metadata, std::shared_ptr hook_data) : flag_key_(std::move(flag_key)), type_(type), @@ -26,13 +27,19 @@ HookContext::HookContext(std::string flag_key, FlagValueType type, hook_data_(std::move(hook_data)) {} template -const std::string& HookContext::GetFlagKey() const { return flag_key_; } +const std::string& HookContext::GetFlagKey() const { + return flag_key_; +} template -FlagValueType HookContext::GetType() const { return type_; } +FlagValueType HookContext::GetType() const { + return type_; +} template -const T& HookContext::GetDefaultValue() const { return default_value_; } +const T& HookContext::GetDefaultValue() const { + return default_value_; +} template const EvaluationContext& HookContext::GetEvaluationContext() const { diff --git a/test/hook_context_test.cpp b/test/hook_context_test.cpp index 16471af..7cce418 100644 --- a/test/hook_context_test.cpp +++ b/test/hook_context_test.cpp @@ -141,11 +141,10 @@ TEST_F(HookContextTest, SetEvaluationContextUpdatesContext) { EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), "initial-user"); - EvaluationContext updated_ctx = - EvaluationContext::Builder() - .WithTargetingKey("updated-user") - .WithAttribute("env", std::string("prod")) - .build(); + EvaluationContext updated_ctx = EvaluationContext::Builder() + .WithTargetingKey("updated-user") + .WithAttribute("env", std::string("prod")) + .build(); hook_ctx.SetEvaluationContext(std::move(updated_ctx)); @@ -168,8 +167,7 @@ TEST_F(HookContextTest, HookDataSharesStateAndAllowsMutations) { hook_ctx.GetHookData()->Set("custom_key", std::string("stage_before")); - auto* retrieved = - hook_ctx.GetHookData()->GetAs("custom_key"); + auto* retrieved = hook_ctx.GetHookData()->GetAs("custom_key"); ASSERT_NE(retrieved, nullptr); EXPECT_EQ(*retrieved, "stage_before"); } From 45ca64a88a7262c953f807396d308e9bfa91be6e Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Mon, 13 Jul 2026 09:12:53 +0000 Subject: [PATCH 13/24] fix linter Signed-off-by: NeaguGeorgiana23 --- test/hook_context_test.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/hook_context_test.cpp b/test/hook_context_test.cpp index 7cce418..7425433 100644 --- a/test/hook_context_test.cpp +++ b/test/hook_context_test.cpp @@ -20,15 +20,12 @@ class HookContextTest : public ::testing::Test { : initial_ctx_(EvaluationContext::Builder() .WithTargetingKey("initial-user") .WithAttribute("env", std::string("test")) - .build()), - client_metadata_{"test-client"}, - provider_metadata_{"test-provider"}, - hook_data_(std::make_shared()) {} + .build()) {} EvaluationContext initial_ctx_; - Metadata client_metadata_; - Metadata provider_metadata_; - std::shared_ptr hook_data_; + Metadata client_metadata_{"test-client"}; + Metadata provider_metadata_{"test-provider"}; + std::shared_ptr hook_data_{std::make_shared()}; }; TEST_F(HookContextTest, ConstructorAndAccessorsForBool) { @@ -53,14 +50,14 @@ TEST_F(HookContextTest, ConstructorAndAccessorsForBool) { TEST_F(HookContextTest, ConstructorAndAccessorsForString) { constexpr FlagValueType kType = FlagValueType::kString; - const std::string kDefaultValue = "default-string"; + const std::string default_value = "default-string"; - StringHookContext hook_ctx("string-flag", kType, kDefaultValue, initial_ctx_, + StringHookContext hook_ctx("string-flag", kType, default_value, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_EQ(hook_ctx.GetFlagKey(), "string-flag"); EXPECT_EQ(hook_ctx.GetType(), kType); - EXPECT_EQ(hook_ctx.GetDefaultValue(), kDefaultValue); + EXPECT_EQ(hook_ctx.GetDefaultValue(), default_value); ASSERT_TRUE(hook_ctx.GetEvaluationContext().GetTargetingKey().has_value()); EXPECT_EQ(hook_ctx.GetEvaluationContext().GetTargetingKey().value(), @@ -113,9 +110,9 @@ TEST_F(HookContextTest, ConstructorAndAccessorsForDouble) { TEST_F(HookContextTest, ConstructorAndAccessorsForObject) { constexpr FlagValueType kType = FlagValueType::kObject; - Value kDefaultValue(std::string("json-or-structure")); + Value default_value(std::string("json-or-structure")); - ObjectHookContext hook_ctx("object-flag", kType, kDefaultValue, initial_ctx_, + ObjectHookContext hook_ctx("object-flag", kType, default_value, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_EQ(hook_ctx.GetFlagKey(), "object-flag"); From 3b93d77d9b1783d3bc255ca49d1911d23e3acf1f Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 14 Jul 2026 09:35:20 +0000 Subject: [PATCH 14/24] add Hooks class. Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 35 ++++ openfeature/base_hook.h | 21 +++ openfeature/flag_evaluation_details.cpp | 42 +++++ openfeature/flag_evaluation_details.h | 48 +++++ openfeature/hook.cpp | 12 ++ openfeature/hook.h | 66 +++++++ test/BUILD | 25 +++ test/flag_evaluation_details_test.cpp | 224 +++++++++++++++++++++++ test/hook_test.cpp | 232 ++++++++++++++++++++++++ 9 files changed, 705 insertions(+) create mode 100644 openfeature/base_hook.h create mode 100644 openfeature/flag_evaluation_details.cpp create mode 100644 openfeature/flag_evaluation_details.h create mode 100644 openfeature/hook.cpp create mode 100644 openfeature/hook.h create mode 100644 test/flag_evaluation_details_test.cpp create mode 100644 test/hook_test.cpp diff --git a/openfeature/BUILD b/openfeature/BUILD index 0cd195d..0f5803e 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -4,6 +4,12 @@ package( default_visibility = ["//visibility:public"], ) +cc_library( + name = "base_hook", + hdrs = ["base_hook.h"], + include_prefix = "openfeature", +) + cc_library( name = "client", hdrs = ["client.h"], @@ -74,6 +80,20 @@ cc_library( ], ) +cc_library( + name = "flag_evaluation_details", + srcs = ["flag_evaluation_details.cpp"], + hdrs = ["flag_evaluation_details.h"], + include_prefix = "openfeature", + deps = [ + ":error_code", + ":flag_metadata", + ":reason", + ":resolution_details", + ":value" + ], +) + cc_library( name = "flag_metadata", hdrs = ["flag_metadata.h"], @@ -124,6 +144,21 @@ cc_library( include_prefix = "openfeature", ) +cc_library( + name = "hook", + srcs = ["hook.cpp"], + hdrs = ["hook.h"], + include_prefix = "openfeature", + deps = [ + ":base_hook", + ":evaluation_context", + ":flag_evaluation_details", + ":hook_context", + ":hook_hints", + ":value", + ], +) + cc_library( name = "metadata", hdrs = ["metadata.h"], diff --git a/openfeature/base_hook.h b/openfeature/base_hook.h new file mode 100644 index 0000000..ef8a505 --- /dev/null +++ b/openfeature/base_hook.h @@ -0,0 +1,21 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_BASE_HOOK_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_BASE_HOOK_H_ + +namespace openfeature { + +// Non-templated base class for all hooks. This will allow storing different +// hook types (e.g., BoolHook, +// StringHook) inside evaluation options. +class BaseHook { + public: + BaseHook() = default; + BaseHook(const BaseHook&) = delete; + BaseHook(BaseHook&&) = default; + BaseHook& operator=(const BaseHook&) = delete; + BaseHook& operator=(BaseHook&&) = default; + virtual ~BaseHook() = default; +}; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_BASE_HOOK_H_ \ No newline at end of file diff --git a/openfeature/flag_evaluation_details.cpp b/openfeature/flag_evaluation_details.cpp new file mode 100644 index 0000000..16a693d --- /dev/null +++ b/openfeature/flag_evaluation_details.cpp @@ -0,0 +1,42 @@ +#include "openfeature/flag_evaluation_details.h" + +#include +#include +#include + +#include "openfeature/error_code.h" +#include "openfeature/flag_metadata.h" +#include "openfeature/reason.h" +#include "openfeature/resolution_details.h" +#include "openfeature/value.h" + +namespace openfeature { + +template +FlagEvaluationDetails::FlagEvaluationDetails( + std::string flag_key, T value, Reason reason, + std::optional variant, const FlagMetadata& flag_metadata, + std::optional error_code, + std::optional error_message) + : ResolutionDetails(std::move(value), reason, std::move(variant), + flag_metadata, error_code, std::move(error_message)), + flag_key_(std::move(flag_key)) {} + +template +FlagEvaluationDetails::FlagEvaluationDetails( + std::string flag_key, const ResolutionDetails& resolution_details) + : ResolutionDetails(resolution_details), + flag_key_(std::move(flag_key)) {} + +template +std::string_view FlagEvaluationDetails::GetFlagKey() const { + return flag_key_; +} + +template class FlagEvaluationDetails; +template class FlagEvaluationDetails; +template class FlagEvaluationDetails; +template class FlagEvaluationDetails; +template class FlagEvaluationDetails; + +} // namespace openfeature diff --git a/openfeature/flag_evaluation_details.h b/openfeature/flag_evaluation_details.h new file mode 100644 index 0000000..94ef1de --- /dev/null +++ b/openfeature/flag_evaluation_details.h @@ -0,0 +1,48 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_FLAG_EVALUATION_DETAILS_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_FLAG_EVALUATION_DETAILS_H_ + +#include +#include +#include + +#include "openfeature/error_code.h" +#include "openfeature/flag_metadata.h" +#include "openfeature/reason.h" +#include "openfeature/resolution_details.h" +#include "openfeature/value.h" + +namespace openfeature { + +template +class FlagEvaluationDetails : public ResolutionDetails { + public: + FlagEvaluationDetails( + std::string flag_key, T value, Reason reason, + std::optional variant, const FlagMetadata& flag_metadata, + std::optional error_code = std::nullopt, + std::optional error_message = std::nullopt); + + FlagEvaluationDetails(std::string flag_key, + const ResolutionDetails& resolution_details); + + FlagEvaluationDetails(const FlagEvaluationDetails&) = default; + FlagEvaluationDetails& operator=(const FlagEvaluationDetails&) = default; + FlagEvaluationDetails(FlagEvaluationDetails&&) noexcept = default; + FlagEvaluationDetails& operator=(FlagEvaluationDetails&&) noexcept = default; + ~FlagEvaluationDetails() = default; + std::string_view GetFlagKey() const; + + private: + std::string flag_key_; +}; + +// Type aliases for common types. +using BoolFlagEvaluationDetails = FlagEvaluationDetails; +using StringFlagEvaluationDetails = FlagEvaluationDetails; +using IntFlagEvaluationDetails = FlagEvaluationDetails; +using DoubleFlagEvaluationDetails = FlagEvaluationDetails; +using ObjectFlagEvaluationDetails = FlagEvaluationDetails; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_FLAG_EVALUATION_DETAILS_H_ diff --git a/openfeature/hook.cpp b/openfeature/hook.cpp new file mode 100644 index 0000000..21af598 --- /dev/null +++ b/openfeature/hook.cpp @@ -0,0 +1,12 @@ +#include "openfeature/hook.h" + +namespace openfeature { + +// Explicit template instantiations for common hook types. +template class Hook; +template class Hook; +template class Hook; +template class Hook; +template class Hook; + +} // namespace openfeature diff --git a/openfeature/hook.h b/openfeature/hook.h new file mode 100644 index 0000000..09774fd --- /dev/null +++ b/openfeature/hook.h @@ -0,0 +1,66 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_HOOK_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_HOOK_H_ + +#include +#include +#include +#include + +#include "openfeature/base_hook.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/flag_evaluation_details.h" +#include "openfeature/hook_context.h" +#include "openfeature/hook_hints.h" +#include "openfeature/value.h" + +namespace openfeature { + +// Hook allows application developers to add arbitrary behavior to the +// flag evaluation lifecycle. Hooks operate similarly to middleware in web +// frameworks. They are executed stack-wise with respect to flag resolution, +// prioritizing increasing specificity (API, Client, Invocation, Provider) +// first, and the order in which they were added second. +/// https://openfeature.dev/specification/sections/hooks +template +class Hook : public BaseHook { + public: + Hook() = default; + Hook(const Hook&) = delete; + Hook& operator=(const Hook&) = delete; + Hook(Hook&&) = delete; + Hook& operator=(Hook&&) = delete; + ~Hook() override = default; + + // Runs before the flag evaluation occurs. + virtual std::optional Before(HookContext& ctx, + const HookHints& hints) { + return std::nullopt; + } + + // Runs immediately after successful flag evaluation occurs. + virtual void After(const HookContext& ctx, + const FlagEvaluationDetails& details, + const HookHints& hints) {} + + // Runs if an error occurs during flag evaluation or in `Before`/`After` + // stages. + virtual void Error(const HookContext& ctx, const std::exception& error, + const HookHints& hints) {} + + // Runs after the flag evaluation occurs, regardless of whether it was + // successful or not. + virtual void Finally(const HookContext& ctx, + const FlagEvaluationDetails& details, + const HookHints& hints) {} +}; + +// Type aliases for common hook specializations. +using BoolHook = Hook; +using StringHook = Hook; +using IntHook = Hook; +using DoubleHook = Hook; +using ObjectHook = Hook; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_HOOK_H_ diff --git a/test/BUILD b/test/BUILD index ca5df9d..a156603 100644 --- a/test/BUILD +++ b/test/BUILD @@ -42,6 +42,15 @@ cc_test( ], ) +cc_test( + name = "flag_evaluation_details_test", + srcs = ["flag_evaluation_details_test.cpp"], + deps = [ + "//openfeature:flag_evaluation_details", + "@googletest//:gtest_main", + ], +) + cc_test( name = "noop_provider_test", srcs = ["noop_provider_test.cpp"], @@ -117,4 +126,20 @@ cc_test( "//openfeature:value", "@googletest//:gtest_main", ], +) + +cc_test( + name = "hook_test", + srcs = ["hook_test.cpp"], + deps = [ + "//openfeature:base_hook", + "//openfeature:evaluation_context", + "//openfeature:flag_evaluation_details", + "//openfeature:flag_metadata", + "//openfeature:hook", + "//openfeature:hook_context", + "//openfeature:hook_hints", + "//openfeature:value", + "@googletest//:gtest_main", + ], ) \ No newline at end of file diff --git a/test/flag_evaluation_details_test.cpp b/test/flag_evaluation_details_test.cpp new file mode 100644 index 0000000..645f5be --- /dev/null +++ b/test/flag_evaluation_details_test.cpp @@ -0,0 +1,224 @@ +#include "openfeature/flag_evaluation_details.h" + +#include + +#include +#include +#include + +#include "openfeature/error_code.h" +#include "openfeature/flag_metadata.h" +#include "openfeature/reason.h" +#include "openfeature/resolution_details.h" +#include "openfeature/value.h" + +namespace openfeature { + +TEST(FlagEvaluationDetailsTest, DirectConstructorAccessesFieldsForBoolean) { + const std::string expected_flag_key = "bool-flag"; + const bool expected_value = true; + const Reason expected_reason = Reason::kTargetingMatch; + const std::optional expected_variant = "on-variant"; + const FlagMetadata expected_flag_metadata{}; + const std::optional expected_error_code = ErrorCode::kParseError; + const std::optional expected_error_message = + "Failed to parse data"; + + BoolFlagEvaluationDetails details( + expected_flag_key, expected_value, expected_reason, expected_variant, + expected_flag_metadata, expected_error_code, expected_error_message); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), expected_error_code); + ASSERT_EQ(details.GetErrorMessage(), expected_error_message); + ASSERT_NO_THROW(details.GetFlagMetadata()); +} + +TEST(FlagEvaluationDetailsTest, ResolutionDetailsConstructorForBoolean) { + const std::string expected_flag_key = "bool-flag-wrapped"; + const bool expected_value = false; + const Reason expected_reason = Reason::kDefault; + const std::optional expected_variant = "off-variant"; + const FlagMetadata expected_flag_metadata{}; + const std::optional expected_error_code = std::nullopt; + const std::optional expected_error_message = std::nullopt; + + BoolResolutionDetails res_details( + expected_value, expected_reason, expected_variant, expected_flag_metadata, + expected_error_code, expected_error_message); + + BoolFlagEvaluationDetails details(expected_flag_key, res_details); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), expected_error_code); + ASSERT_EQ(details.GetErrorMessage(), expected_error_message); + ASSERT_NO_THROW(details.GetFlagMetadata()); +} + +TEST(FlagEvaluationDetailsTest, DirectConstructorAccessesFieldsForString) { + const std::string expected_flag_key = "string-flag"; + const std::string expected_value = "expected-string"; + const Reason expected_reason = Reason::kTargetingMatch; + const std::optional expected_variant = "on-variant"; + const FlagMetadata expected_flag_metadata{}; + const std::optional expected_error_code = ErrorCode::kTypeMismatch; + const std::optional expected_error_message = + "Type mismatch error"; + + StringFlagEvaluationDetails details( + expected_flag_key, expected_value, expected_reason, expected_variant, + expected_flag_metadata, expected_error_code, expected_error_message); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), expected_error_code); + ASSERT_EQ(details.GetErrorMessage(), expected_error_message); + ASSERT_NO_THROW(details.GetFlagMetadata()); +} + +TEST(FlagEvaluationDetailsTest, ResolutionDetailsConstructorForString) { + const std::string expected_flag_key = "string-flag-wrapped"; + const std::string expected_value = "wrapped-string"; + const Reason expected_reason = Reason::kStatic; + const std::optional expected_variant = std::nullopt; + const FlagMetadata expected_flag_metadata{}; + + StringResolutionDetails res_details(expected_value, expected_reason, + expected_variant, expected_flag_metadata); + + StringFlagEvaluationDetails details(expected_flag_key, res_details); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), std::nullopt); + ASSERT_EQ(details.GetErrorMessage(), std::nullopt); +} + +TEST(FlagEvaluationDetailsTest, DirectConstructorAccessesFieldsForInteger) { + const std::string expected_flag_key = "int-flag"; + const int64_t expected_value = 123456789LL; + const Reason expected_reason = Reason::kTargetingMatch; + const std::optional expected_variant = "v1"; + const FlagMetadata expected_flag_metadata{}; + + IntFlagEvaluationDetails details(expected_flag_key, expected_value, + expected_reason, expected_variant, + expected_flag_metadata); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), std::nullopt); + ASSERT_EQ(details.GetErrorMessage(), std::nullopt); +} + +TEST(FlagEvaluationDetailsTest, ResolutionDetailsConstructorForInteger) { + const std::string expected_flag_key = "int-flag-wrapped"; + const int64_t expected_value = 42; + const Reason expected_reason = Reason::kCached; + const std::optional expected_variant = "v2"; + const FlagMetadata expected_flag_metadata{}; + + IntResolutionDetails res_details(expected_value, expected_reason, + expected_variant, expected_flag_metadata); + + IntFlagEvaluationDetails details(expected_flag_key, res_details); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), std::nullopt); + ASSERT_EQ(details.GetErrorMessage(), std::nullopt); +} + +TEST(FlagEvaluationDetailsTest, DirectConstructorAccessesFieldsForDouble) { + const std::string expected_flag_key = "double-flag"; + const double expected_value = 123.456; + const Reason expected_reason = Reason::kTargetingMatch; + const std::optional expected_variant = "on-variant"; + const FlagMetadata expected_flag_metadata{}; + + DoubleFlagEvaluationDetails details(expected_flag_key, expected_value, + expected_reason, expected_variant, + expected_flag_metadata); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_DOUBLE_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), std::nullopt); + ASSERT_EQ(details.GetErrorMessage(), std::nullopt); +} + +TEST(FlagEvaluationDetailsTest, ResolutionDetailsConstructorForDouble) { + const std::string expected_flag_key = "double-flag-wrapped"; + const double expected_value = 3.14159265359; + const Reason expected_reason = Reason::kSplit; + const std::optional expected_variant = "pi-variant"; + const FlagMetadata expected_flag_metadata{}; + + DoubleResolutionDetails res_details(expected_value, expected_reason, + expected_variant, expected_flag_metadata); + + DoubleFlagEvaluationDetails details(expected_flag_key, res_details); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_DOUBLE_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); +} + +TEST(FlagEvaluationDetailsTest, DirectConstructorAccessesFieldsForObject) { + const std::string expected_flag_key = "object-flag"; + const Value expected_value = Value(std::map{ + {"key1", Value("value1")}, {"key2", Value(42)}}); + const Reason expected_reason = Reason::kTargetingMatch; + const std::optional expected_variant = "object-variant"; + const FlagMetadata expected_flag_metadata{}; + + ObjectFlagEvaluationDetails details(expected_flag_key, expected_value, + expected_reason, expected_variant, + expected_flag_metadata); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), std::nullopt); + ASSERT_EQ(details.GetErrorMessage(), std::nullopt); +} + +TEST(FlagEvaluationDetailsTest, ResolutionDetailsConstructorForObject) { + const std::string expected_flag_key = "object-flag-wrapped"; + const Value expected_value = Value(std::map{ + {"nested", Value(true)}, {"score", Value(99.5)}}); + const Reason expected_reason = Reason::kTargetingMatch; + const std::optional expected_variant = "complex-variant"; + const FlagMetadata expected_flag_metadata{}; + + ObjectResolutionDetails res_details(expected_value, expected_reason, + expected_variant, expected_flag_metadata); + + ObjectFlagEvaluationDetails details(expected_flag_key, res_details); + + EXPECT_EQ(details.GetFlagKey(), expected_flag_key); + EXPECT_EQ(details.GetValue(), expected_value); + ASSERT_EQ(details.GetReason(), expected_reason); + ASSERT_EQ(details.GetVariant(), expected_variant); + ASSERT_EQ(details.GetErrorCode(), std::nullopt); + ASSERT_EQ(details.GetErrorMessage(), std::nullopt); +} + +} // namespace openfeature diff --git a/test/hook_test.cpp b/test/hook_test.cpp new file mode 100644 index 0000000..3fd70e9 --- /dev/null +++ b/test/hook_test.cpp @@ -0,0 +1,232 @@ +#include "openfeature/hook.h" + +#include + +#include +#include +#include +#include +#include + +#include "openfeature/base_hook.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/flag_evaluation_details.h" +#include "openfeature/flag_metadata.h" +#include "openfeature/hook_context.h" +#include "openfeature/hook_hints.h" +#include "openfeature/value.h" + +namespace openfeature { +namespace { + +template +class TrackingHook : public Hook { + public: + TrackingHook() = default; + + std::optional Before(HookContext& ctx, + const HookHints& hints) override { + before_called_ = true; + last_flag_key_ = ctx.GetFlagKey(); + if (auto it_hints = hints.find("before_hint"); it_hints != hints.end()) { + last_hint_value_ = std::any_cast(it_hints->second); + } + if (return_context_.has_value()) { + return return_context_; + } + return std::nullopt; + } + + void After(const HookContext& ctx, const FlagEvaluationDetails& details, + const HookHints& hints) override { + after_called_ = true; + last_flag_key_ = ctx.GetFlagKey(); + last_reason_ = details.GetReason(); + if (auto it_hints = hints.find("after_hint"); it_hints != hints.end()) { + last_hint_value_ = std::any_cast(it_hints->second); + } + } + + void Error(const HookContext& ctx, const std::exception& error, + const HookHints& hints) override { + error_called_ = true; + last_flag_key_ = ctx.GetFlagKey(); + last_error_message_ = error.what(); + if (auto it_hints = hints.find("error_hint"); it_hints != hints.end()) { + last_hint_value_ = std::any_cast(it_hints->second); + } + } + + void Finally(const HookContext& ctx, + const FlagEvaluationDetails& details, + const HookHints& hints) override { + finally_called_ = true; + last_flag_key_ = ctx.GetFlagKey(); + last_reason_ = details.GetReason(); + if (auto it_hints = hints.find("finally_hint"); it_hints != hints.end()) { + last_hint_value_ = std::any_cast(it_hints->second); + } + } + + void SetReturnContext(std::optional ctx) { + return_context_ = std::move(ctx); + } + + bool before_called_ = false; + bool after_called_ = false; + bool error_called_ = false; + bool finally_called_ = false; + std::string last_flag_key_; + std::string last_hint_value_; + std::string last_error_message_; + Reason last_reason_ = Reason::kUnknown; + std::optional return_context_; +}; + +class HookTest : public ::testing::Test { + protected: + HookTest() + : initial_ctx_( + EvaluationContext::Builder().WithTargetingKey("user-123").build()), + hook_data_(std::make_shared()) {} + + EvaluationContext initial_ctx_; + Metadata client_metadata_{"client-id"}; + Metadata provider_metadata_{"provider-id"}; + std::shared_ptr hook_data_; +}; + +} // namespace + +TEST_F(HookTest, DefaultBeforeReturnsNulloptForAllSpecializations) { + HookHints hints; + + BoolHook bool_hook; + BoolHookContext bool_ctx("bool-flag", FlagValueType::kBoolean, true, + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); + EXPECT_FALSE(bool_hook.Before(bool_ctx, hints).has_value()); + + StringHook string_hook; + StringHookContext string_ctx("string-flag", FlagValueType::kString, "val", + initial_ctx_, client_metadata_, + provider_metadata_, hook_data_); + EXPECT_FALSE(string_hook.Before(string_ctx, hints).has_value()); + + IntHook int_hook; + IntHookContext int_ctx("int-flag", FlagValueType::kInteger, 100, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + EXPECT_FALSE(int_hook.Before(int_ctx, hints).has_value()); + + DoubleHook double_hook; + DoubleHookContext double_ctx("double-flag", FlagValueType::kDouble, 3.14, + initial_ctx_, client_metadata_, + provider_metadata_, hook_data_); + EXPECT_FALSE(double_hook.Before(double_ctx, hints).has_value()); + + ObjectHook object_hook; + ObjectHookContext object_ctx("object-flag", FlagValueType::kObject, + Value("obj"), initial_ctx_, client_metadata_, + provider_metadata_, hook_data_); + EXPECT_FALSE(object_hook.Before(object_ctx, hints).has_value()); +} + +TEST_F(HookTest, DefaultAfterErrorAndFinallyAreNoOpsWithoutThrowing) { + BoolHook hook; + BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, true, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + BoolFlagEvaluationDetails details("bool-flag", true, Reason::kStatic, + std::nullopt, FlagMetadata()); + HookHints hints; + std::runtime_error error("simulated error"); + + EXPECT_NO_THROW(hook.After(ctx, details, hints)); + EXPECT_NO_THROW(hook.Error(ctx, error, hints)); + EXPECT_NO_THROW(hook.Finally(ctx, details, hints)); +} + +TEST_F(HookTest, OverriddenBeforeCanModifyAndReturnEvaluationContext) { + TrackingHook hook; + BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, true, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + HookHints hints{{"before_hint", std::any(std::string("hint-val"))}}; + + EvaluationContext modified_ctx = + EvaluationContext::Builder() + .WithTargetingKey("mutated-user") + .WithAttribute("region", std::string("us-east")) + .build(); + hook.SetReturnContext(modified_ctx); + + std::optional result = hook.Before(ctx, hints); + + EXPECT_TRUE(hook.before_called_); + EXPECT_EQ(hook.last_flag_key_, "bool-flag"); + EXPECT_EQ(hook.last_hint_value_, "hint-val"); + ASSERT_TRUE(result.has_value()); + ASSERT_TRUE(result->GetTargetingKey().has_value()); + EXPECT_EQ(result->GetTargetingKey().value(), "mutated-user"); +} + +TEST_F(HookTest, OverriddenAfterReceivesContextDetailsAndHints) { + TrackingHook hook; + StringHookContext ctx("string-flag", FlagValueType::kString, "default", + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); + StringFlagEvaluationDetails details("string-flag", "variant-val", + Reason::kTargetingMatch, "v1", + FlagMetadata()); + HookHints hints{{"after_hint", std::any(std::string("after-data"))}}; + + hook.After(ctx, details, hints); + + EXPECT_TRUE(hook.after_called_); + EXPECT_EQ(hook.last_flag_key_, "string-flag"); + EXPECT_EQ(hook.last_reason_, Reason::kTargetingMatch); + EXPECT_EQ(hook.last_hint_value_, "after-data"); +} + +TEST_F(HookTest, OverriddenErrorReceivesExceptionAndHints) { + TrackingHook hook; + IntHookContext ctx("int-flag", FlagValueType::kInteger, 42, initial_ctx_, + client_metadata_, provider_metadata_, hook_data_); + std::runtime_error error("provider timeout error"); + HookHints hints{{"error_hint", std::any(std::string("error-data"))}}; + + hook.Error(ctx, error, hints); + + EXPECT_TRUE(hook.error_called_); + EXPECT_EQ(hook.last_flag_key_, "int-flag"); + EXPECT_EQ(hook.last_error_message_, "provider timeout error"); + EXPECT_EQ(hook.last_hint_value_, "error-data"); +} + +TEST_F(HookTest, OverriddenFinallyReceivesContextDetailsAndHints) { + TrackingHook hook; + DoubleHookContext ctx("double-flag", FlagValueType::kDouble, 1.0, + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); + DoubleFlagEvaluationDetails details("double-flag", 2.718, Reason::kCached, + std::nullopt, FlagMetadata()); + HookHints hints{{"finally_hint", std::any(std::string("finally-data"))}}; + + hook.Finally(ctx, details, hints); + + EXPECT_TRUE(hook.finally_called_); + EXPECT_EQ(hook.last_flag_key_, "double-flag"); + EXPECT_EQ(hook.last_reason_, Reason::kCached); + EXPECT_EQ(hook.last_hint_value_, "finally-data"); +} + +TEST_F(HookTest, PolymorphicDestructionViaBaseHookPointer) { + std::vector> hooks; + hooks.push_back(std::make_unique()); + hooks.push_back(std::make_unique()); + hooks.push_back(std::make_unique()); + hooks.push_back(std::make_unique()); + hooks.push_back(std::make_unique()); + + EXPECT_EQ(hooks.size(), 5); +} + +} // namespace openfeature From a4aa1c20447354452f0a29e9a8bd7ffccc48114e Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 14 Jul 2026 10:05:57 +0000 Subject: [PATCH 15/24] fix linter. Signed-off-by: NeaguGeorgiana23 --- test/hook_test.cpp | 120 +++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 54 deletions(-) diff --git a/test/hook_test.cpp b/test/hook_test.cpp index 3fd70e9..7867872 100644 --- a/test/hook_test.cpp +++ b/test/hook_test.cpp @@ -26,61 +26,61 @@ class TrackingHook : public Hook { std::optional Before(HookContext& ctx, const HookHints& hints) override { - before_called_ = true; - last_flag_key_ = ctx.GetFlagKey(); + before_called = true; + last_flag_key = ctx.GetFlagKey(); if (auto it_hints = hints.find("before_hint"); it_hints != hints.end()) { - last_hint_value_ = std::any_cast(it_hints->second); + last_hint_value = std::any_cast(it_hints->second); } - if (return_context_.has_value()) { - return return_context_; + if (return_context.has_value()) { + return return_context; } return std::nullopt; } void After(const HookContext& ctx, const FlagEvaluationDetails& details, const HookHints& hints) override { - after_called_ = true; - last_flag_key_ = ctx.GetFlagKey(); - last_reason_ = details.GetReason(); + after_called = true; + last_flag_key = ctx.GetFlagKey(); + last_reason = details.GetReason(); if (auto it_hints = hints.find("after_hint"); it_hints != hints.end()) { - last_hint_value_ = std::any_cast(it_hints->second); + last_hint_value = std::any_cast(it_hints->second); } } void Error(const HookContext& ctx, const std::exception& error, const HookHints& hints) override { - error_called_ = true; - last_flag_key_ = ctx.GetFlagKey(); - last_error_message_ = error.what(); + error_called = true; + last_flag_key = ctx.GetFlagKey(); + last_error_message = error.what(); if (auto it_hints = hints.find("error_hint"); it_hints != hints.end()) { - last_hint_value_ = std::any_cast(it_hints->second); + last_hint_value = std::any_cast(it_hints->second); } } void Finally(const HookContext& ctx, const FlagEvaluationDetails& details, const HookHints& hints) override { - finally_called_ = true; - last_flag_key_ = ctx.GetFlagKey(); - last_reason_ = details.GetReason(); + finally_called = true; + last_flag_key = ctx.GetFlagKey(); + last_reason = details.GetReason(); if (auto it_hints = hints.find("finally_hint"); it_hints != hints.end()) { - last_hint_value_ = std::any_cast(it_hints->second); + last_hint_value = std::any_cast(it_hints->second); } } void SetReturnContext(std::optional ctx) { - return_context_ = std::move(ctx); + return_context = std::move(ctx); } - bool before_called_ = false; - bool after_called_ = false; - bool error_called_ = false; - bool finally_called_ = false; - std::string last_flag_key_; - std::string last_hint_value_; - std::string last_error_message_; - Reason last_reason_ = Reason::kUnknown; - std::optional return_context_; + bool before_called = false; + bool after_called = false; + bool error_called = false; + bool finally_called = false; + std::string last_flag_key; + std::string last_hint_value; + std::string last_error_message; + Reason last_reason = Reason::kUnknown; + std::optional return_context; }; class HookTest : public ::testing::Test { @@ -99,43 +99,50 @@ class HookTest : public ::testing::Test { } // namespace TEST_F(HookTest, DefaultBeforeReturnsNulloptForAllSpecializations) { + constexpr bool kBoolValue = true; + constexpr int kIntValue = 100; + constexpr double kDoubleValue = 3.14; + constexpr std::string kStringValue = "val"; + constexpr Value kObjectValue = Value("obj"); HookHints hints; BoolHook bool_hook; - BoolHookContext bool_ctx("bool-flag", FlagValueType::kBoolean, true, + BoolHookContext bool_ctx("bool-flag", FlagValueType::kBoolean, kBoolValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(bool_hook.Before(bool_ctx, hints).has_value()); StringHook string_hook; - StringHookContext string_ctx("string-flag", FlagValueType::kString, "val", + StringHookContext string_ctx("string-flag", FlagValueType::kString, kStringValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(string_hook.Before(string_ctx, hints).has_value()); IntHook int_hook; - IntHookContext int_ctx("int-flag", FlagValueType::kInteger, 100, initial_ctx_, + IntHookContext int_ctx("int-flag", FlagValueType::kInteger, kIntValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(int_hook.Before(int_ctx, hints).has_value()); DoubleHook double_hook; - DoubleHookContext double_ctx("double-flag", FlagValueType::kDouble, 3.14, + DoubleHookContext double_ctx("double-flag", FlagValueType::kDouble, kDoubleValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(double_hook.Before(double_ctx, hints).has_value()); ObjectHook object_hook; ObjectHookContext object_ctx("object-flag", FlagValueType::kObject, - Value("obj"), initial_ctx_, client_metadata_, + kObjectValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(object_hook.Before(object_ctx, hints).has_value()); } TEST_F(HookTest, DefaultAfterErrorAndFinallyAreNoOpsWithoutThrowing) { + constexpr bool kBoolValue = true; BoolHook hook; - BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, true, initial_ctx_, + + BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, kBoolValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); - BoolFlagEvaluationDetails details("bool-flag", true, Reason::kStatic, + BoolFlagEvaluationDetails details("bool-flag", kBoolValue, Reason::kStatic, std::nullopt, FlagMetadata()); HookHints hints; std::runtime_error error("simulated error"); @@ -146,8 +153,9 @@ TEST_F(HookTest, DefaultAfterErrorAndFinallyAreNoOpsWithoutThrowing) { } TEST_F(HookTest, OverriddenBeforeCanModifyAndReturnEvaluationContext) { + constexpr bool kBoolValue = true; TrackingHook hook; - BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, true, initial_ctx_, + BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, kBoolValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); HookHints hints{{"before_hint", std::any(std::string("hint-val"))}}; @@ -160,17 +168,18 @@ TEST_F(HookTest, OverriddenBeforeCanModifyAndReturnEvaluationContext) { std::optional result = hook.Before(ctx, hints); - EXPECT_TRUE(hook.before_called_); - EXPECT_EQ(hook.last_flag_key_, "bool-flag"); - EXPECT_EQ(hook.last_hint_value_, "hint-val"); + EXPECT_TRUE(hook.before_called); + EXPECT_EQ(hook.last_flag_key, "bool-flag"); + EXPECT_EQ(hook.last_hint_value, "hint-val"); ASSERT_TRUE(result.has_value()); ASSERT_TRUE(result->GetTargetingKey().has_value()); EXPECT_EQ(result->GetTargetingKey().value(), "mutated-user"); } TEST_F(HookTest, OverriddenAfterReceivesContextDetailsAndHints) { + constexpr std::string kStringValue = "default"; TrackingHook hook; - StringHookContext ctx("string-flag", FlagValueType::kString, "default", + StringHookContext ctx("string-flag", FlagValueType::kString, kStringValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); StringFlagEvaluationDetails details("string-flag", "variant-val", @@ -180,42 +189,45 @@ TEST_F(HookTest, OverriddenAfterReceivesContextDetailsAndHints) { hook.After(ctx, details, hints); - EXPECT_TRUE(hook.after_called_); - EXPECT_EQ(hook.last_flag_key_, "string-flag"); - EXPECT_EQ(hook.last_reason_, Reason::kTargetingMatch); - EXPECT_EQ(hook.last_hint_value_, "after-data"); + EXPECT_TRUE(hook.after_called); + EXPECT_EQ(hook.last_flag_key, "string-flag"); + EXPECT_EQ(hook.last_reason, Reason::kTargetingMatch); + EXPECT_EQ(hook.last_hint_value, "after-data"); } TEST_F(HookTest, OverriddenErrorReceivesExceptionAndHints) { + constexpr int64_t kIntValue = 42; TrackingHook hook; - IntHookContext ctx("int-flag", FlagValueType::kInteger, 42, initial_ctx_, + IntHookContext ctx("int-flag", FlagValueType::kInteger, kIntValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); std::runtime_error error("provider timeout error"); HookHints hints{{"error_hint", std::any(std::string("error-data"))}}; hook.Error(ctx, error, hints); - EXPECT_TRUE(hook.error_called_); - EXPECT_EQ(hook.last_flag_key_, "int-flag"); - EXPECT_EQ(hook.last_error_message_, "provider timeout error"); - EXPECT_EQ(hook.last_hint_value_, "error-data"); + EXPECT_TRUE(hook.error_called); + EXPECT_EQ(hook.last_flag_key, "int-flag"); + EXPECT_EQ(hook.last_error_message, "provider timeout error"); + EXPECT_EQ(hook.last_hint_value, "error-data"); } TEST_F(HookTest, OverriddenFinallyReceivesContextDetailsAndHints) { + constexpr double kDoubleValue = 1.0; + constexpr double kSecondDoubleValue = 2.718; TrackingHook hook; - DoubleHookContext ctx("double-flag", FlagValueType::kDouble, 1.0, + DoubleHookContext ctx("double-flag", FlagValueType::kDouble, kDoubleValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); - DoubleFlagEvaluationDetails details("double-flag", 2.718, Reason::kCached, + DoubleFlagEvaluationDetails details("double-flag", kSecondDoubleValue, Reason::kCached, std::nullopt, FlagMetadata()); HookHints hints{{"finally_hint", std::any(std::string("finally-data"))}}; hook.Finally(ctx, details, hints); - EXPECT_TRUE(hook.finally_called_); - EXPECT_EQ(hook.last_flag_key_, "double-flag"); - EXPECT_EQ(hook.last_reason_, Reason::kCached); - EXPECT_EQ(hook.last_hint_value_, "finally-data"); + EXPECT_TRUE(hook.finally_called); + EXPECT_EQ(hook.last_flag_key, "double-flag"); + EXPECT_EQ(hook.last_reason, Reason::kCached); + EXPECT_EQ(hook.last_hint_value, "finally-data"); } TEST_F(HookTest, PolymorphicDestructionViaBaseHookPointer) { From d7cd54a72d48668969bfae315d1abccd66226e72 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 14 Jul 2026 10:07:49 +0000 Subject: [PATCH 16/24] fix linter. Signed-off-by: NeaguGeorgiana23 --- test/hook_test.cpp | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/test/hook_test.cpp b/test/hook_test.cpp index 7867872..55a3c54 100644 --- a/test/hook_test.cpp +++ b/test/hook_test.cpp @@ -113,19 +113,20 @@ TEST_F(HookTest, DefaultBeforeReturnsNulloptForAllSpecializations) { EXPECT_FALSE(bool_hook.Before(bool_ctx, hints).has_value()); StringHook string_hook; - StringHookContext string_ctx("string-flag", FlagValueType::kString, kStringValue, - initial_ctx_, client_metadata_, + StringHookContext string_ctx("string-flag", FlagValueType::kString, + kStringValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(string_hook.Before(string_ctx, hints).has_value()); IntHook int_hook; - IntHookContext int_ctx("int-flag", FlagValueType::kInteger, kIntValue, initial_ctx_, - client_metadata_, provider_metadata_, hook_data_); + IntHookContext int_ctx("int-flag", FlagValueType::kInteger, kIntValue, + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); EXPECT_FALSE(int_hook.Before(int_ctx, hints).has_value()); DoubleHook double_hook; - DoubleHookContext double_ctx("double-flag", FlagValueType::kDouble, kDoubleValue, - initial_ctx_, client_metadata_, + DoubleHookContext double_ctx("double-flag", FlagValueType::kDouble, + kDoubleValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(double_hook.Before(double_ctx, hints).has_value()); @@ -140,8 +141,9 @@ TEST_F(HookTest, DefaultAfterErrorAndFinallyAreNoOpsWithoutThrowing) { constexpr bool kBoolValue = true; BoolHook hook; - BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, kBoolValue, initial_ctx_, - client_metadata_, provider_metadata_, hook_data_); + BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, kBoolValue, + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); BoolFlagEvaluationDetails details("bool-flag", kBoolValue, Reason::kStatic, std::nullopt, FlagMetadata()); HookHints hints; @@ -155,8 +157,9 @@ TEST_F(HookTest, DefaultAfterErrorAndFinallyAreNoOpsWithoutThrowing) { TEST_F(HookTest, OverriddenBeforeCanModifyAndReturnEvaluationContext) { constexpr bool kBoolValue = true; TrackingHook hook; - BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, kBoolValue, initial_ctx_, - client_metadata_, provider_metadata_, hook_data_); + BoolHookContext ctx("bool-flag", FlagValueType::kBoolean, kBoolValue, + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); HookHints hints{{"before_hint", std::any(std::string("hint-val"))}}; EvaluationContext modified_ctx = @@ -198,8 +201,9 @@ TEST_F(HookTest, OverriddenAfterReceivesContextDetailsAndHints) { TEST_F(HookTest, OverriddenErrorReceivesExceptionAndHints) { constexpr int64_t kIntValue = 42; TrackingHook hook; - IntHookContext ctx("int-flag", FlagValueType::kInteger, kIntValue, initial_ctx_, - client_metadata_, provider_metadata_, hook_data_); + IntHookContext ctx("int-flag", FlagValueType::kInteger, kIntValue, + initial_ctx_, client_metadata_, provider_metadata_, + hook_data_); std::runtime_error error("provider timeout error"); HookHints hints{{"error_hint", std::any(std::string("error-data"))}}; @@ -218,8 +222,9 @@ TEST_F(HookTest, OverriddenFinallyReceivesContextDetailsAndHints) { DoubleHookContext ctx("double-flag", FlagValueType::kDouble, kDoubleValue, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); - DoubleFlagEvaluationDetails details("double-flag", kSecondDoubleValue, Reason::kCached, - std::nullopt, FlagMetadata()); + DoubleFlagEvaluationDetails details("double-flag", kSecondDoubleValue, + Reason::kCached, std::nullopt, + FlagMetadata()); HookHints hints{{"finally_hint", std::any(std::string("finally-data"))}}; hook.Finally(ctx, details, hints); From d6ef2c67322e2c6a3db0b23728af3e84adfaf63b Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 14 Jul 2026 10:11:34 +0000 Subject: [PATCH 17/24] fix linter. Signed-off-by: NeaguGeorgiana23 --- test/hook_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/hook_test.cpp b/test/hook_test.cpp index 55a3c54..0268c91 100644 --- a/test/hook_test.cpp +++ b/test/hook_test.cpp @@ -102,8 +102,8 @@ TEST_F(HookTest, DefaultBeforeReturnsNulloptForAllSpecializations) { constexpr bool kBoolValue = true; constexpr int kIntValue = 100; constexpr double kDoubleValue = 3.14; - constexpr std::string kStringValue = "val"; - constexpr Value kObjectValue = Value("obj"); + const std::string kStringValue = "val"; + const Value kObjectValue = Value("obj"); HookHints hints; BoolHook bool_hook; @@ -180,7 +180,7 @@ TEST_F(HookTest, OverriddenBeforeCanModifyAndReturnEvaluationContext) { } TEST_F(HookTest, OverriddenAfterReceivesContextDetailsAndHints) { - constexpr std::string kStringValue = "default"; + const std::string kStringValue = "default"; TrackingHook hook; StringHookContext ctx("string-flag", FlagValueType::kString, kStringValue, initial_ctx_, client_metadata_, provider_metadata_, From a2f7fee4fb27e4a3e243b8e7d54df0ac4b82e047 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 14 Jul 2026 10:31:56 +0000 Subject: [PATCH 18/24] fix linter. Signed-off-by: NeaguGeorgiana23 --- test/hook_test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/hook_test.cpp b/test/hook_test.cpp index 0268c91..805e7b2 100644 --- a/test/hook_test.cpp +++ b/test/hook_test.cpp @@ -102,8 +102,8 @@ TEST_F(HookTest, DefaultBeforeReturnsNulloptForAllSpecializations) { constexpr bool kBoolValue = true; constexpr int kIntValue = 100; constexpr double kDoubleValue = 3.14; - const std::string kStringValue = "val"; - const Value kObjectValue = Value("obj"); + const std::string string_value = "val"; + const Value object_value = Value("obj"); HookHints hints; BoolHook bool_hook; @@ -114,7 +114,7 @@ TEST_F(HookTest, DefaultBeforeReturnsNulloptForAllSpecializations) { StringHook string_hook; StringHookContext string_ctx("string-flag", FlagValueType::kString, - kStringValue, initial_ctx_, client_metadata_, + string_value, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(string_hook.Before(string_ctx, hints).has_value()); @@ -132,7 +132,7 @@ TEST_F(HookTest, DefaultBeforeReturnsNulloptForAllSpecializations) { ObjectHook object_hook; ObjectHookContext object_ctx("object-flag", FlagValueType::kObject, - kObjectValue, initial_ctx_, client_metadata_, + object_value, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); EXPECT_FALSE(object_hook.Before(object_ctx, hints).has_value()); } @@ -180,9 +180,9 @@ TEST_F(HookTest, OverriddenBeforeCanModifyAndReturnEvaluationContext) { } TEST_F(HookTest, OverriddenAfterReceivesContextDetailsAndHints) { - const std::string kStringValue = "default"; + const std::string string_value = "default"; TrackingHook hook; - StringHookContext ctx("string-flag", FlagValueType::kString, kStringValue, + StringHookContext ctx("string-flag", FlagValueType::kString, string_value, initial_ctx_, client_metadata_, provider_metadata_, hook_data_); StringFlagEvaluationDetails details("string-flag", "variant-val", From a91afcae5de79761903e4a2317db86ed632edc90 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 14 Jul 2026 10:49:14 +0000 Subject: [PATCH 19/24] appli agent suggestions. Signed-off-by: NeaguGeorgiana23 --- openfeature/base_hook.h | 4 ++-- test/hook_test.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openfeature/base_hook.h b/openfeature/base_hook.h index ef8a505..8264426 100644 --- a/openfeature/base_hook.h +++ b/openfeature/base_hook.h @@ -10,9 +10,9 @@ class BaseHook { public: BaseHook() = default; BaseHook(const BaseHook&) = delete; - BaseHook(BaseHook&&) = default; + BaseHook(BaseHook&&) = delete; BaseHook& operator=(const BaseHook&) = delete; - BaseHook& operator=(BaseHook&&) = default; + BaseHook& operator=(BaseHook&&) = delete; virtual ~BaseHook() = default; }; diff --git a/test/hook_test.cpp b/test/hook_test.cpp index 805e7b2..042b4c1 100644 --- a/test/hook_test.cpp +++ b/test/hook_test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "openfeature/base_hook.h" #include "openfeature/evaluation_context.h" From 06bd51c75ee1ebbcfbf3c868f6898768b563bd6c Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 15 Jul 2026 12:16:24 +0000 Subject: [PATCH 20/24] Add EvaluationOpton struct. Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 10 +++ openfeature/evaluation_options.h | 14 ++++ test/BUILD | 12 ++++ test/evaluation_options_test.cpp | 119 +++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+) create mode 100644 openfeature/evaluation_options.h create mode 100644 test/evaluation_options_test.cpp diff --git a/openfeature/BUILD b/openfeature/BUILD index 0f5803e..b92a765 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -56,6 +56,16 @@ cc_library( include_prefix = "openfeature", ) +cc_library( + name = "evaluation_options", + hdrs = ["evaluation_options.h"], + include_prefix = "openfeature", + deps = [ + ":base_hook", + ":hook_hints", + ], +) + cc_library( name = "feature_provider_status_manager", srcs = ["feature_provider_status_manager.cpp"], diff --git a/openfeature/evaluation_options.h b/openfeature/evaluation_options.h new file mode 100644 index 0000000..647de94 --- /dev/null +++ b/openfeature/evaluation_options.h @@ -0,0 +1,14 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_EVALUATION_OPTIONS_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_EVALUATION_OPTIONS_H_ +#include +#include + +#include "openfeature/base_hook.h" +#include "openfeature/hook_hints.h" +namespace openfeature { +struct EvaluationOptions { + std::vector> hooks; + HookHints hook_hints; +}; +} // namespace openfeature +#endif // CPP_SDK_INCLUDE_OPENFEATURE_EVALUATION_OPTIONS_H_ \ No newline at end of file diff --git a/test/BUILD b/test/BUILD index a156603..8eeb79d 100644 --- a/test/BUILD +++ b/test/BUILD @@ -142,4 +142,16 @@ cc_test( "//openfeature:value", "@googletest//:gtest_main", ], +) + +cc_test( + name = "evaluation_options_test", + srcs = ["evaluation_options_test.cpp"], + deps = [ + "//openfeature:base_hook", + "//openfeature:evaluation_options", + "//openfeature:hook", + "//openfeature:hook_hints", + "@googletest//:gtest_main", + ], ) \ No newline at end of file diff --git a/test/evaluation_options_test.cpp b/test/evaluation_options_test.cpp new file mode 100644 index 0000000..224acb6 --- /dev/null +++ b/test/evaluation_options_test.cpp @@ -0,0 +1,119 @@ +#include "openfeature/evaluation_options.h" + +#include + +#include +#include +#include +#include + +#include "openfeature/base_hook.h" +#include "openfeature/hook.h" +#include "openfeature/hook_hints.h" + +namespace openfeature { + +class CustomTestHook : public BoolHook {}; +class AnotherTestHook : public StringHook {}; + +TEST(EvaluationOptionsTest, DefaultConstructorInitializesEmptyContainers) { + EvaluationOptions options; + EXPECT_TRUE(options.hooks.empty()); + EXPECT_TRUE(options.hook_hints.empty()); +} + +TEST(EvaluationOptionsTest, StoresAndPreservesMultipleHooksInOrder) { + EvaluationOptions options; + + auto hook1 = std::make_shared(); + auto hook2 = std::make_shared(); + auto hook3 = std::make_shared(); + + options.hooks.push_back(hook1); + options.hooks.push_back(hook2); + options.hooks.push_back(hook3); + + ASSERT_EQ(options.hooks.size(), 3); + EXPECT_EQ(options.hooks[0], hook1); + EXPECT_EQ(options.hooks[1], hook2); + EXPECT_EQ(options.hooks[2], hook3); +} + +TEST(EvaluationOptionsTest, CanDowncastBaseHookPointersToConcreteTypes) { + EvaluationOptions options; + + auto bool_hook = std::make_shared(); + auto string_hook = std::make_shared(); + + options.hooks.push_back(bool_hook); + options.hooks.push_back(string_hook); + + ASSERT_EQ(options.hooks.size(), 2); + + auto retrieved_bool_hook = + std::dynamic_pointer_cast(options.hooks[0]); + ASSERT_NE(retrieved_bool_hook, nullptr); + EXPECT_EQ(retrieved_bool_hook, bool_hook); + + auto retrieved_string_hook = + std::dynamic_pointer_cast(options.hooks[1]); + ASSERT_NE(retrieved_string_hook, nullptr); + EXPECT_EQ(retrieved_string_hook, string_hook); + + // Cross-type downcast should return nullptr safely + auto invalid_cast = std::dynamic_pointer_cast(options.hooks[0]); + EXPECT_EQ(invalid_cast, nullptr); +} + +TEST(EvaluationOptionsTest, StoresAndRetrievesHookHints) { + const std::string trace_id_key = "trace_id"; + const std::string timeout_key = "timeout_ms"; + const std::string is_debug_key = "is_debug"; + const std::string trace_id_value = "trace-12345"; + const int timeout_value = 500; + const bool is_debug_value = true; + EvaluationOptions options; + + options.hook_hints[trace_id_key] = std::string(trace_id_value); + options.hook_hints[timeout_key] = timeout_value; + options.hook_hints[is_debug_key] = is_debug_value; + + EXPECT_EQ(options.hook_hints.size(), 3); + + auto it_trace = options.hook_hints.find(trace_id_key); + ASSERT_NE(it_trace, options.hook_hints.end()); + EXPECT_EQ(std::any_cast(it_trace->second), trace_id_value); + + auto it_timeout = options.hook_hints.find(timeout_key); + ASSERT_NE(it_timeout, options.hook_hints.end()); + EXPECT_EQ(std::any_cast(it_timeout->second), timeout_value); + + auto it_debug = options.hook_hints.find(is_debug_key); + ASSERT_NE(it_debug, options.hook_hints.end()); + EXPECT_TRUE(std::any_cast(it_debug->second)); +} + +TEST(EvaluationOptionsTest, SupportsCopyAndMoveSemantics) { + const std::string trace_id_key = "trace_id"; + const std::string trace_id_value = "value"; + EvaluationOptions original; + auto hook = std::make_shared(); + original.hooks.push_back(hook); + original.hook_hints[trace_id_key] = trace_id_value; + + // Copy construction + EvaluationOptions copy_constructed(original); + EXPECT_EQ(copy_constructed.hooks.size(), 1); + EXPECT_EQ(copy_constructed.hooks[0], hook); + EXPECT_EQ(std::any_cast(copy_constructed.hook_hints[trace_id_key]), + trace_id_value); + + // Move construction + EvaluationOptions moved_constructed(std::move(original)); + EXPECT_EQ(moved_constructed.hooks.size(), 1); + EXPECT_EQ(moved_constructed.hooks[0], hook); + EXPECT_EQ(std::any_cast(moved_constructed.hook_hints[trace_id_key]), + trace_id_value); +} + +} // namespace openfeature From b362223393b3c1892d3d73260837998c39c87ae5 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 15 Jul 2026 12:29:41 +0000 Subject: [PATCH 21/24] fix linter. Signed-off-by: NeaguGeorgiana23 --- test/evaluation_options_test.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/evaluation_options_test.cpp b/test/evaluation_options_test.cpp index 224acb6..03fd31f 100644 --- a/test/evaluation_options_test.cpp +++ b/test/evaluation_options_test.cpp @@ -105,15 +105,17 @@ TEST(EvaluationOptionsTest, SupportsCopyAndMoveSemantics) { EvaluationOptions copy_constructed(original); EXPECT_EQ(copy_constructed.hooks.size(), 1); EXPECT_EQ(copy_constructed.hooks[0], hook); - EXPECT_EQ(std::any_cast(copy_constructed.hook_hints[trace_id_key]), - trace_id_value); + EXPECT_EQ( + std::any_cast(copy_constructed.hook_hints[trace_id_key]), + trace_id_value); // Move construction EvaluationOptions moved_constructed(std::move(original)); EXPECT_EQ(moved_constructed.hooks.size(), 1); EXPECT_EQ(moved_constructed.hooks[0], hook); - EXPECT_EQ(std::any_cast(moved_constructed.hook_hints[trace_id_key]), - trace_id_value); + EXPECT_EQ( + std::any_cast(moved_constructed.hook_hints[trace_id_key]), + trace_id_value); } } // namespace openfeature From 6942c4eb0a2bb766311710248a476d003295be8e Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 15 Jul 2026 17:37:15 +0000 Subject: [PATCH 22/24] update Provider class to allow getting hooks. Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 1 + openfeature/provider.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/openfeature/BUILD b/openfeature/BUILD index b92a765..744f57c 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -246,6 +246,7 @@ cc_library( deps = [ "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:statusor", + ":base_hook", ":evaluation_context", ":metadata", ":resolution_details", diff --git a/openfeature/provider.h b/openfeature/provider.h index 50fcdcb..1b458ef 100644 --- a/openfeature/provider.h +++ b/openfeature/provider.h @@ -6,6 +6,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "openfeature/base_hook.h" #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" #include "openfeature/resolution_details.h" @@ -24,6 +25,7 @@ class FeatureProvider { public: virtual ~FeatureProvider() = default; virtual Metadata GetMetadata() const = 0; + virtual std::vector> GetHooks() const = 0; virtual absl::StatusOr> GetBooleanEvaluation(std::string_view flag, bool default_value, const EvaluationContext& ctx) = 0; From 49bdc1759fb74dcc5a6edfd79bf9829952abcef1 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 15 Jul 2026 17:59:44 +0000 Subject: [PATCH 23/24] update all classes that inharit from FeatureProvider. Signed-off-by: NeaguGeorgiana23 --- openfeature/memory_provider/in_memory_provider.cpp | 4 ++++ openfeature/memory_provider/in_memory_provider.h | 2 ++ openfeature/noop_provider.cpp | 4 ++++ openfeature/noop_provider.h | 3 +++ test/mocks/mock_feature_provider.h | 1 + 5 files changed, 14 insertions(+) diff --git a/openfeature/memory_provider/in_memory_provider.cpp b/openfeature/memory_provider/in_memory_provider.cpp index a48df05..c32db4d 100644 --- a/openfeature/memory_provider/in_memory_provider.cpp +++ b/openfeature/memory_provider/in_memory_provider.cpp @@ -21,6 +21,10 @@ Metadata InMemoryProvider::GetMetadata() const { return Metadata{std::string(kName)}; } +std::vector> InMemoryProvider::GetHooks() const { + return {}; +} + absl::Status InMemoryProvider::Init(const EvaluationContext& ctx) { { std::unique_lock lock(mutex_); diff --git a/openfeature/memory_provider/in_memory_provider.h b/openfeature/memory_provider/in_memory_provider.h index 484d0a3..7bd55eb 100644 --- a/openfeature/memory_provider/in_memory_provider.h +++ b/openfeature/memory_provider/in_memory_provider.h @@ -44,6 +44,8 @@ class InMemoryProvider : public FeatureProvider { // will be added to the configuration. void UpdateFlag(std::string key, std::any new_flag); + std::vector> GetHooks() const override; + absl::StatusOr> GetBooleanEvaluation( std::string_view key, bool default_value, const EvaluationContext& ctx) override; diff --git a/openfeature/noop_provider.cpp b/openfeature/noop_provider.cpp index 5b067f0..97fd9f0 100644 --- a/openfeature/noop_provider.cpp +++ b/openfeature/noop_provider.cpp @@ -4,6 +4,10 @@ namespace openfeature { Metadata NoopProvider::GetMetadata() const { return Metadata{name_}; } +std::vector> NoopProvider::GetHooks() const { + return {}; +} + absl::StatusOr> NoopProvider::GetBooleanEvaluation(std::string_view flag, bool default_value, const EvaluationContext& ctx) { diff --git a/openfeature/noop_provider.h b/openfeature/noop_provider.h index a8ebf6b..72de593 100644 --- a/openfeature/noop_provider.h +++ b/openfeature/noop_provider.h @@ -23,6 +23,9 @@ class NoopProvider : public FeatureProvider { // Metadata returns the metadata of the provider. Metadata GetMetadata() const override; + // GetHooks returns an empty vector of hooks. + std::vector> GetHooks() const override; + // BooleanEvaluation returns a boolean flag. absl::StatusOr> GetBooleanEvaluation( std::string_view flag, bool default_value, diff --git a/test/mocks/mock_feature_provider.h b/test/mocks/mock_feature_provider.h index 74e7ad7..3477f08 100644 --- a/test/mocks/mock_feature_provider.h +++ b/test/mocks/mock_feature_provider.h @@ -11,6 +11,7 @@ namespace openfeature { class MockFeatureProvider : public FeatureProvider { public: MOCK_METHOD(Metadata, GetMetadata, (), (const, override)); + MOCK_METHOD(std::vector>, GetHooks, (), (const, override)); MOCK_METHOD(absl::StatusOr>, GetBooleanEvaluation, (std::string_view flag, bool default_value, From dab62a0ab96343444bf700569e46ab862f5a9f4d Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 15 Jul 2026 18:10:52 +0000 Subject: [PATCH 24/24] update BUILD files and imports. Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 1 + openfeature/memory_provider/BUILD | 1 + openfeature/memory_provider/in_memory_provider.cpp | 1 + openfeature/memory_provider/in_memory_provider.h | 1 + openfeature/noop_provider.h | 1 + test/BUILD | 1 + test/mocks/mock_feature_provider.h | 6 +++++- 7 files changed, 11 insertions(+), 1 deletion(-) diff --git a/openfeature/BUILD b/openfeature/BUILD index 744f57c..37d52d3 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -198,6 +198,7 @@ cc_library( hdrs = ["noop_provider.h"], include_prefix = "openfeature", deps = [ + ":base_hook", ":evaluation_context", ":metadata", ":provider", diff --git a/openfeature/memory_provider/BUILD b/openfeature/memory_provider/BUILD index 99dbd0b..7f601b1 100644 --- a/openfeature/memory_provider/BUILD +++ b/openfeature/memory_provider/BUILD @@ -21,6 +21,7 @@ cc_library( hdrs = ["in_memory_provider.h"], include_prefix = "openfeature", deps = [ + "//openfeature:base_hook", "//openfeature:error_code", "//openfeature:evaluation_context", ":flag", diff --git a/openfeature/memory_provider/in_memory_provider.cpp b/openfeature/memory_provider/in_memory_provider.cpp index c32db4d..4e43baa 100644 --- a/openfeature/memory_provider/in_memory_provider.cpp +++ b/openfeature/memory_provider/in_memory_provider.cpp @@ -5,6 +5,7 @@ #include #include "absl/status/statusor.h" +#include "openfeature/base_hook.h" #include "openfeature/error_code.h" #include "openfeature/memory_provider/flag.h" #include "openfeature/reason.h" diff --git a/openfeature/memory_provider/in_memory_provider.h b/openfeature/memory_provider/in_memory_provider.h index 7bd55eb..9394ee8 100644 --- a/openfeature/memory_provider/in_memory_provider.h +++ b/openfeature/memory_provider/in_memory_provider.h @@ -10,6 +10,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "openfeature/base_hook.h" #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" #include "openfeature/provider.h" diff --git a/openfeature/noop_provider.h b/openfeature/noop_provider.h index 72de593..1a1ac63 100644 --- a/openfeature/noop_provider.h +++ b/openfeature/noop_provider.h @@ -6,6 +6,7 @@ #include #include "absl/status/statusor.h" +#include "openfeature/base_hook.h" #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" #include "openfeature/provider.h" diff --git a/test/BUILD b/test/BUILD index 8eeb79d..e6e4248 100644 --- a/test/BUILD +++ b/test/BUILD @@ -8,6 +8,7 @@ cc_library( name = "mock_feature_provider", hdrs = ["mocks/mock_feature_provider.h"], deps = [ + "//openfeature:base_hook", "//openfeature:provider", "@googletest//:gtest", ], diff --git a/test/mocks/mock_feature_provider.h b/test/mocks/mock_feature_provider.h index 3477f08..cfad1e8 100644 --- a/test/mocks/mock_feature_provider.h +++ b/test/mocks/mock_feature_provider.h @@ -3,6 +3,9 @@ #include +#include + +#include "openfeature/base_hook.h" #include "openfeature/provider.h" namespace openfeature { @@ -11,7 +14,8 @@ namespace openfeature { class MockFeatureProvider : public FeatureProvider { public: MOCK_METHOD(Metadata, GetMetadata, (), (const, override)); - MOCK_METHOD(std::vector>, GetHooks, (), (const, override)); + MOCK_METHOD(std::vector>, GetHooks, (), + (const, override)); MOCK_METHOD(absl::StatusOr>, GetBooleanEvaluation, (std::string_view flag, bool default_value,