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..cabe909 --- /dev/null +++ b/openfeature/hook_context.cpp @@ -0,0 +1,75 @@ +#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..d4a5b39 100644 --- a/test/BUILD +++ b/test/BUILD @@ -105,4 +105,19 @@ cc_test( "//openfeature:hook_data", "@googletest//:gtest_main", ], +) + +cc_test( + name = "hook_context_test", + srcs = ["hook_context_test.cpp"], + deps = [ + "//openfeature:evaluation_context", + "//openfeature:flag_metadata", + "//openfeature:flag_type_value", + "//openfeature:hook_context", + "//openfeature:hook_data", + "//openfeature:metadata", + "//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..7425433 --- /dev/null +++ b/test/hook_context_test.cpp @@ -0,0 +1,180 @@ +#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()) {} + + EvaluationContext initial_ctx_; + Metadata client_metadata_{"test-client"}; + Metadata provider_metadata_{"test-provider"}; + std::shared_ptr hook_data_{std::make_shared()}; +}; + +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 default_value = "default-string"; + + 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(), default_value); + + 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 default_value(std::string("json-or-structure")); + + ObjectHookContext hook_ctx("object-flag", kType, default_value, 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