Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@
# as "No source files found in compile args" when running
# `bazel run @hedron_compile_commands//:refresh_all`.
build --features=-parse_headers

# Fix for "relocation refers to local symbol in discarded section"
build --copt=-fno-asynchronous-unwind-tables
build --linkopt=-Wl,--gc-sections

# Optional: Use a faster, more modern linker
# build --linkopt=-fuse-ld=lld
1 change: 1 addition & 0 deletions openfeature/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ cc_library(
cc_library(
name = "evaluation_context",
hdrs = ["evaluation_context.h"],
srcs = ["evaluation_context.cpp"],
include_prefix = "openfeature",
)

Expand Down
4 changes: 3 additions & 1 deletion openfeature/client_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
namespace openfeature {

ClientAPI::ClientAPI(ProviderRepository& repository, std::string_view domain)
: provider_repository_(repository), domain_(domain) {}
: provider_repository_(repository),
domain_(domain),
evaluation_context_(EvaluationContext::Builder().build()) {}

Metadata ClientAPI::GetMetadata() { return Metadata{domain_}; }

Expand Down
83 changes: 83 additions & 0 deletions openfeature/evaluation_context.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "evaluation_context.h"

namespace openfeature {

EvaluationContext::EvaluationContext(std::optional<std::string> targeting_key,
std::map<std::string, std::any> attributes)
: targeting_key_(std::move(targeting_key)),
attributes_(std::move(attributes)) {}

std::optional<std::string_view> EvaluationContext::GetTargetingKey() const {
if (!targeting_key_.has_value()) {
return std::nullopt;
}
return targeting_key_;
}

const std::any* EvaluationContext::GetValue(std::string_view key) const {
auto it = attributes_.find(std::string(key));
Comment thread
NeaguGeorgiana23 marked this conversation as resolved.
if (it != attributes_.end()) {
return &it->second;
}
return nullptr;
}

const std::map<std::string, std::any>& EvaluationContext::GetAttributes()
const {
return attributes_;
}

EvaluationContext EvaluationContext::Merge(
std::initializer_list<const EvaluationContext*> contexts) {
Builder builder;

// Merge Attributes from all contexts (higher precedence overwrites lower).
for (const EvaluationContext* ctx : contexts) {
if (ctx != nullptr) {
for (const auto& pair : ctx->GetAttributes()) {
builder.WithAttribute(pair.first, pair.second);
}
}
}

// Find the first valid targeting key from highest to lowest precedence.
// We iterate through the list backwards to check the highest precedence
// context first.
std::vector<const EvaluationContext*> reversed(contexts);
std::reverse(reversed.begin(), reversed.end());

for (const EvaluationContext* ctx : reversed) {
if (ctx != nullptr) {
auto key = ctx->GetTargetingKey();
if (key.has_value() && !key->empty()) {
builder.WithTargetingKey(std::string(key.value()));
break;
}
}
}

return builder.build();
}

EvaluationContext::Builder& EvaluationContext::Builder::WithTargetingKey(
std::string key) {
this->targeting_key_ = std::move(key);
return *this;
}

EvaluationContext::Builder& EvaluationContext::Builder::WithAttribute(
std::string key, std::any value) {
this->attributes_.insert_or_assign(std::move(key), std::move(value));
return *this;
}

EvaluationContext::Builder& EvaluationContext::Builder::WithAttribute(
std::string key, const char* value) {
return this->WithAttribute(std::move(key), std::string(value));
}

EvaluationContext EvaluationContext::Builder::build() const {
return EvaluationContext(targeting_key_, attributes_);
}

} // namespace openfeature
53 changes: 51 additions & 2 deletions openfeature/evaluation_context.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,61 @@
#ifndef CPP_SDK_INCLUDE_OPENFEATURE_EVALUATION_CONTEXT_H_
#define CPP_SDK_INCLUDE_OPENFEATURE_EVALUATION_CONTEXT_H_

#include <algorithm>
#include <any>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace openfeature {

// EvaluationContext provides ambient information for the purposes of flag
// evaluation https://openfeature.dev/specification/sections/evaluation-context
// EvaluationContext provides information for the purposes of flag
// evaluation.
class EvaluationContext {
public:
// The Builder class is the only way to construct an EvaluationContext.
class Builder;

EvaluationContext() = delete;

std::optional<std::string_view> GetTargetingKey() const;

// Get a specific attribute value.
// Returns nullptr if key does not exist.
const std::any* GetValue(std::string_view key) const;

// Get all attributes
const std::map<std::string, std::any>& GetAttributes() const;

// It takes a list of pointers to contexts to avoid unnecessary copies.
// The order of contexts in the initializer list determines precedence.
static EvaluationContext Merge(
std::initializer_list<const EvaluationContext*> contexts);

private:
EvaluationContext(std::optional<std::string> targeting_key,
std::map<std::string, std::any> attributes);

std::optional<std::string> targeting_key_;
std::map<std::string, std::any> attributes_;
Comment thread
NeaguGeorgiana23 marked this conversation as resolved.
};

class EvaluationContext::Builder {
public:
// Builder methods return a reference to self to allow for chaining.
Builder& WithTargetingKey(std::string key);
Builder& WithAttribute(std::string key, std::any value);
// Overload for const char* to ensure implicit conversion to std::string
Builder& WithAttribute(std::string key, const char* value);

// The build() method creates the final, immutable EvaluationContext object.
EvaluationContext build() const;

private:
std::optional<std::string> targeting_key_;
std::map<std::string, std::any> attributes_;
};

} // namespace openfeature
Expand Down
3 changes: 3 additions & 0 deletions openfeature/global_context_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace openfeature {

GlobalContextManager::GlobalContextManager()
: global_evaluation_context_(EvaluationContext::Builder().build()) {}

GlobalContextManager& GlobalContextManager::GetInstance() {
static GlobalContextManager instance;
return instance;
Expand Down
2 changes: 1 addition & 1 deletion openfeature/global_context_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class GlobalContextManager {
EvaluationContext GetGlobalEvaluationContext() const;

private:
GlobalContextManager() = default;
GlobalContextManager();
EvaluationContext global_evaluation_context_;
mutable std::shared_mutex context_mutex_;
};
Expand Down
9 changes: 9 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ cc_test(
],
)

cc_test(
name = "evaluation_context_test",
srcs = ["evaluation_context_test.cpp"],
deps = [
"//openfeature:evaluation_context",
"@googletest//:gtest_main",
],
)

cc_test(
name = "openfeature_api_test",
srcs = ["openfeature_api_test.cpp"],
Expand Down
12 changes: 6 additions & 6 deletions test/client_api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ClientAPITest : public ::testing::Test {
void SetUp() override {
// Reset the Global Context to a clean state before each test.
GlobalContextManager::GetInstance().SetGlobalEvaluationContext(
EvaluationContext{});
EvaluationContext::Builder().build());
}
ProviderRepository repo_;
};
Expand All @@ -46,7 +46,7 @@ TEST_F(ClientAPITest, GetProviderStatusDefaultsToReady) {
// Test setting and getting the EvaluationContext.
TEST_F(ClientAPITest, SetAndGetEvaluationContext) {
ClientAPI client(repo_, "test-domain");
EvaluationContext ctx;
EvaluationContext ctx = EvaluationContext::Builder().build();

// Verify we can set the context without error.
EXPECT_NO_THROW(client.SetEvaluationContext(ctx));
Expand All @@ -66,7 +66,7 @@ TEST_F(ClientAPITest, GetBooleanValueReturnsDefaultWithNoopProvider) {
// Test GetBooleanValue with an EvaluationContext passed in.
TEST_F(ClientAPITest, GetBooleanValueWithContextReturnsDefault) {
ClientAPI client(repo_, "test-domain");
EvaluationContext ctx;
EvaluationContext ctx = EvaluationContext::Builder().build();
std::string flag_key = "my-boolean-flag";

EXPECT_TRUE(client.GetBooleanValue(flag_key, true, ctx));
Expand All @@ -75,14 +75,14 @@ TEST_F(ClientAPITest, GetBooleanValueWithContextReturnsDefault) {

// Test context merging logic indirectly.
TEST_F(ClientAPITest, GetBooleanValueSafeWithMergedContexts) {
EvaluationContext global_ctx;
EvaluationContext global_ctx = EvaluationContext::Builder().build();
GlobalContextManager::GetInstance().SetGlobalEvaluationContext(global_ctx);

ClientAPI client(repo_, "test-domain");
EvaluationContext client_ctx;
EvaluationContext client_ctx = EvaluationContext::Builder().build();
client.SetEvaluationContext(client_ctx);

EvaluationContext invocation_ctx;
EvaluationContext invocation_ctx = EvaluationContext::Builder().build();

// This call forces a merge of Global + Client + Invocation contexts.
// We expect the NoopProvider to handle the result gracefully (return
Expand Down
Loading