Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
42a776d
base data structures fot hook implementation.
NeaguGeorgiana23 Jul 7, 2026
e968609
fix linter
NeaguGeorgiana23 Jul 7, 2026
334ec2a
fix linter
NeaguGeorgiana23 Jul 7, 2026
f110e5c
Merge branch 'main' into define-hook-data-structures
NeaguGeorgiana23 Jul 7, 2026
5cc878b
Update BUILD file.
NeaguGeorgiana23 Jul 10, 2026
bd66430
Correct include guards.
NeaguGeorgiana23 Jul 10, 2026
5332610
feat: Add HookData class.
NeaguGeorgiana23 Jul 10, 2026
c8fc92d
fix linter
NeaguGeorgiana23 Jul 10, 2026
3f5f4de
fix linter
NeaguGeorgiana23 Jul 10, 2026
b0f3a15
fix linter
NeaguGeorgiana23 Jul 10, 2026
e9e8852
fix linter
NeaguGeorgiana23 Jul 10, 2026
e333df0
add HookContext structure.
NeaguGeorgiana23 Jul 13, 2026
c59de28
fix linter
NeaguGeorgiana23 Jul 13, 2026
45ca64a
fix linter
NeaguGeorgiana23 Jul 13, 2026
3b93d77
add Hooks class.
NeaguGeorgiana23 Jul 14, 2026
a4aa1c2
fix linter.
NeaguGeorgiana23 Jul 14, 2026
d7cd54a
fix linter.
NeaguGeorgiana23 Jul 14, 2026
d6ef2c6
fix linter.
NeaguGeorgiana23 Jul 14, 2026
a2f7fee
fix linter.
NeaguGeorgiana23 Jul 14, 2026
a91afca
appli agent suggestions.
NeaguGeorgiana23 Jul 14, 2026
c0482f7
Merge branch 'main' into hooks
NeaguGeorgiana23 Jul 14, 2026
a0ca155
Merge branch 'main' into hooks
NeaguGeorgiana23 Jul 14, 2026
06bd51c
Add EvaluationOpton struct.
NeaguGeorgiana23 Jul 15, 2026
b362223
fix linter.
NeaguGeorgiana23 Jul 15, 2026
12c6b9a
Merge branch 'main' into evaluation_options
NeaguGeorgiana23 Aug 6, 2026
0626872
Merge branch 'main' of https://github.com/open-feature/cpp-sdk into e…
NeaguGeorgiana23 Aug 6, 2026
41b851d
merge brench with changes from main.
NeaguGeorgiana23 Aug 6, 2026
f65bede
fix linter
NeaguGeorgiana23 Aug 6, 2026
38c66e5
fix linter
NeaguGeorgiana23 Aug 6, 2026
7b27e3b
fix linter
NeaguGeorgiana23 Aug 6, 2026
6baa254
class base_hook was transformed to class general_hook in previouse PR
NeaguGeorgiana23 Aug 6, 2026
5bc9b5a
fix linter
NeaguGeorgiana23 Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions openfeature/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ cc_library(
include_prefix = "openfeature",
)

cc_library(
name = "evaluation_options",
hdrs = ["evaluation_options.h"],
include_prefix = "openfeature",
deps = [
":general_hook",
":hook_hints",
],
)

cc_library(
name = "feature_provider_status_manager",
srcs = ["feature_provider_status_manager.cpp"],
Expand Down
14 changes: 14 additions & 0 deletions openfeature/evaluation_options.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef CPP_SDK_INCLUDE_OPENFEATURE_EVALUATION_OPTIONS_H_
#define CPP_SDK_INCLUDE_OPENFEATURE_EVALUATION_OPTIONS_H_
#include <memory>
#include <vector>

#include "openfeature/general_hook.h"
#include "openfeature/hook_hints.h"
namespace openfeature {
struct EvaluationOptions {
std::vector<std::shared_ptr<GeneralHook>> hooks;
HookHints hook_hints;
};
} // namespace openfeature
#endif // CPP_SDK_INCLUDE_OPENFEATURE_EVALUATION_OPTIONS_H_
12 changes: 12 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,16 @@ cc_test(
"//openfeature:value",
"@googletest//:gtest_main",
],
)

cc_test(
name = "evaluation_options_test",
srcs = ["evaluation_options_test.cpp"],
deps = [
"//openfeature:general_hook",
"//openfeature:evaluation_options",
"//openfeature:hook",
"//openfeature:hook_hints",
"@googletest//:gtest_main",
],
)
121 changes: 121 additions & 0 deletions test/evaluation_options_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include "openfeature/evaluation_options.h"

#include <gtest/gtest.h>

#include <any>
#include <memory>
#include <string>
#include <vector>

#include "openfeature/general_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<CustomTestHook>();
auto hook2 = std::make_shared<AnotherTestHook>();
auto hook3 = std::make_shared<IntHook>();

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<CustomTestHook>();
auto string_hook = std::make_shared<AnotherTestHook>();

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<BoolHook>(options.hooks[0]);
ASSERT_NE(retrieved_bool_hook, nullptr);
EXPECT_EQ(retrieved_bool_hook, bool_hook);

auto retrieved_string_hook =
std::dynamic_pointer_cast<StringHook>(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<IntHook>(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<std::string>(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<int>(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<bool>(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<CustomTestHook>();
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<std::string>(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<std::string>(moved_constructed.hook_hints[trace_id_key]),
trace_id_value);
}

} // namespace openfeature
2 changes: 1 addition & 1 deletion test/hook_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,4 @@ TEST_F(HookTest, TypedHookIgnoresMismatchedFlagTypes) {
EXPECT_FALSE(bool_hook.after_called);
}

} // namespace openfeature
} // namespace openfeature
47 changes: 25 additions & 22 deletions test/openfeature_api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,22 @@
#include "mocks/mock_feature_provider.h"
#include "openfeature/noop_provider.h"

using namespace openfeature;
using ::testing::_;
using ::testing::Return;

namespace openfeature {

class OpenFeatureAPITest : public ::testing::Test {
protected:
// To ensure test isolation for the singleton, we shut it down before and
// after each test, to reset it to its default state.
void SetUp() override {}
void TearDown() override {
api.Shutdown();
api.SetEvaluationContext(EvaluationContext ::Builder().build());
api_.Shutdown();
api_.SetEvaluationContext(EvaluationContext::Builder().build());
}

OpenFeatureAPI& api = OpenFeatureAPI::GetInstance();
OpenFeatureAPI& api_ = OpenFeatureAPI::GetInstance();
};

// Test that GetInstance always returns the same singleton instance.
Expand All @@ -35,7 +36,7 @@ TEST_F(OpenFeatureAPITest, GetInstanceReturnsSameInstance) {

// Test that the API is initialized with a NoopProvider by default.
TEST_F(OpenFeatureAPITest, InitialStateHasNoopProvider) {
std::shared_ptr<FeatureProvider> provider = api.GetProvider();
std::shared_ptr<FeatureProvider> provider = api_.GetProvider();
ASSERT_NE(provider, nullptr);
EXPECT_NE(dynamic_cast<NoopProvider*>(provider.get()), nullptr);
}
Expand All @@ -46,9 +47,9 @@ TEST_F(OpenFeatureAPITest, SetAndGetDefaultProviderAndWait) {
std::make_shared<MockFeatureProvider>();
EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus()));

api.SetProviderAndWait(mock_provider);
api_.SetProviderAndWait(mock_provider);

EXPECT_EQ(api.GetProvider(), mock_provider);
EXPECT_EQ(api_.GetProvider(), mock_provider);
}

// Test setting a named provider and waiting for its initialization.
Expand All @@ -58,24 +59,24 @@ TEST_F(OpenFeatureAPITest, SetAndGetNamedProviderAndWait) {
std::string domain = "test-domain";
EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus()));

api.SetProviderAndWait(domain, mock_provider);
api_.SetProviderAndWait(domain, mock_provider);

EXPECT_EQ(api.GetProvider(domain), mock_provider);
EXPECT_NE(dynamic_cast<NoopProvider*>(api.GetProvider().get()), nullptr);
EXPECT_EQ(api_.GetProvider(domain), mock_provider);
EXPECT_NE(dynamic_cast<NoopProvider*>(api_.GetProvider().get()), nullptr);
}

// Test that getting a provider for a non-existent domain falls back to the
// default.
TEST_F(OpenFeatureAPITest, GetProviderFallsBackToDefault) {
std::shared_ptr<FeatureProvider> default_provider = api.GetProvider();
std::shared_ptr<FeatureProvider> default_provider = api_.GetProvider();
std::shared_ptr<FeatureProvider> unknown_domain_provider =
api.GetProvider("unknown-domain");
api_.GetProvider("unknown-domain");
EXPECT_EQ(default_provider, unknown_domain_provider);
}

// Test getting metadata from the default provider.
TEST_F(OpenFeatureAPITest, GetProviderMetadataForDefault) {
Metadata metadata = api.GetProviderMetadata();
Metadata metadata = api_.GetProviderMetadata();
EXPECT_EQ(metadata.name, "Noop Provider");
}

Expand All @@ -91,8 +92,8 @@ TEST_F(OpenFeatureAPITest, GetProviderMetadataForNamed) {
.WillOnce(Return(expected_metadata));
EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus()));

api.SetProviderAndWait(domain, mock_provider);
Metadata actual_metadata = api.GetProviderMetadata(domain);
api_.SetProviderAndWait(domain, mock_provider);
Metadata actual_metadata = api_.GetProviderMetadata(domain);

EXPECT_EQ(actual_metadata.name, expected_metadata.name);
}
Expand All @@ -109,15 +110,15 @@ TEST_F(OpenFeatureAPITest, ShutdownCallsProviderShutdown) {
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*mock_named_provider, Init(_)).WillOnce(Return(absl::OkStatus()));

api.SetProviderAndWait(mock_default_provider);
api.SetProviderAndWait(domain, mock_named_provider);
api_.SetProviderAndWait(mock_default_provider);
api_.SetProviderAndWait(domain, mock_named_provider);

EXPECT_CALL(*mock_default_provider, Shutdown())
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*mock_named_provider, Shutdown())
.WillOnce(Return(absl::OkStatus()));

api.Shutdown();
api_.Shutdown();

testing::Mock::VerifyAndClearExpectations(mock_default_provider.get());
testing::Mock::VerifyAndClearExpectations(mock_named_provider.get());
Expand All @@ -141,7 +142,7 @@ TEST_F(OpenFeatureAPITest, SetProviderAsyncDoesNotBlock) {
});

EXPECT_CALL(*mock_provider, Shutdown()).WillOnce(Return(absl::OkStatus()));
api.SetProvider(mock_provider);
api_.SetProvider(mock_provider);

// Confirm the background task has started.
auto status = init_started_future.wait_for(std::chrono::seconds(1));
Expand Down Expand Up @@ -173,7 +174,7 @@ TEST_F(OpenFeatureAPITest, SetNamedProviderAsyncDoesNotBlock) {
;
});
EXPECT_CALL(*mock_provider, Shutdown()).WillOnce(Return(absl::OkStatus()));
api.SetProvider(domain, mock_provider);
api_.SetProvider(domain, mock_provider);

// Confirm the background task has started.
auto status = init_started_future.wait_for(std::chrono::seconds(1));
Expand All @@ -187,18 +188,20 @@ TEST_F(OpenFeatureAPITest, SetNamedProviderAsyncDoesNotBlock) {

// Test that GetClient returns a valid default ClientAPI instance.
TEST_F(OpenFeatureAPITest, GetDefaultClient) {
std::shared_ptr<Client> client = api.GetClient();
std::shared_ptr<Client> client = api_.GetClient();
EXPECT_NE(client, nullptr) << "GetClient() should return a valid ptr";
EXPECT_EQ(client->GetMetadata().name, "");
}

// Test that GetClient returns a valid named ClientAPI instance.
TEST_F(OpenFeatureAPITest, GetNamedClient) {
std::shared_ptr<Client> named_client = api.GetClient("some-domain");
std::shared_ptr<Client> named_client = api_.GetClient("some-domain");
EXPECT_NE(named_client, nullptr)
<< "GetClient(domain) should return a valid ptr";
EXPECT_EQ(named_client->GetMetadata().name, "some-domain");
}

// TODO: Add tests for "GetEvaluationContext" and "SetEvaluationContext" once.
// EvaluationContext logic is implemented.

} // namespace openfeature