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
35 changes: 35 additions & 0 deletions openfeature/memory_provider/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
load("@rules_cc//cc:defs.bzl", "cc_library")

package(
default_visibility = ["//visibility:public"],
)

cc_library(
name = "flag",
hdrs = ["flag.h"],
include_prefix = "openfeature",
deps = [
"//openfeature:evaluation_context",
"//openfeature:flag_metadata",
"@abseil-cpp//absl/status:statusor",
],
)

cc_library(
name = "in_memory_provider",
srcs = ["in_memory_provider.cpp"],
hdrs = ["in_memory_provider.h"],
include_prefix = "openfeature",
deps = [
"//openfeature:error_code",
"//openfeature:evaluation_context",
":flag",
"//openfeature:metadata",
"//openfeature:provider",
"//openfeature:provider_status",
"//openfeature:reason",
"//openfeature:resolution_details",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
],
)
58 changes: 58 additions & 0 deletions openfeature/memory_provider/flag.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef CPP_SDK_INCLUDE_OPENFEATURE_MEMORY_PROVIDER_FLAG_H_
#define CPP_SDK_INCLUDE_OPENFEATURE_MEMORY_PROVIDER_FLAG_H_

#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>

#include "absl/status/statusor.h"
#include "openfeature/evaluation_context.h"
#include "openfeature/flag_metadata.h"

namespace openfeature {

// This class represents the flag structure used for the InMemoryProvider.
template <typename T>
class Flag {
public:
using ContextEvaluator =
std::function<absl::StatusOr<T>(const Flag&, const EvaluationContext&)>;

Flag(std::unordered_map<std::string, T> variants,
std::optional<std::string> default_variant,
ContextEvaluator context_evaluator, FlagMetadata flag_metadata,
bool disabled = false)
: variants_(std::move(variants)),
default_variant_(std::move(default_variant)),
context_evaluator_(std::move(context_evaluator)),
flag_metadata_(std::move(flag_metadata)),
disabled_(disabled) {}

const std::unordered_map<std::string, T>& GetVariants() const {
return variants_;
}

const std::optional<std::string>& GetDefaultVariant() const {
return default_variant_;
}

const ContextEvaluator& GetContextEvaluator() const {
return context_evaluator_;
}

const FlagMetadata& GetFlagMetadata() const { return flag_metadata_; }

bool IsDisabled() const { return disabled_; }

private:
std::unordered_map<std::string, T> variants_;
std::optional<std::string> default_variant_;
ContextEvaluator context_evaluator_;
FlagMetadata flag_metadata_;
bool disabled_;
};
} // namespace openfeature

#endif // CPP_SDK_INCLUDE_OPENFEATURE_MEMORY_PROVIDER_FLAG_H_
144 changes: 144 additions & 0 deletions openfeature/memory_provider/in_memory_provider.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include "openfeature/memory_provider/in_memory_provider.h"

#include <mutex>
#include <optional>
#include <utility>

#include "absl/status/statusor.h"
#include "openfeature/error_code.h"
#include "openfeature/memory_provider/flag.h"
#include "openfeature/reason.h"

namespace openfeature {

static constexpr std::string_view kName = "InMemoryProvider";

InMemoryProvider::InMemoryProvider(
std::unordered_map<std::string, std::any> flags)
: flags_(std::move(flags)), status_(ProviderStatus::kNotReady) {}

Metadata InMemoryProvider::GetMetadata() const {
return Metadata{std::string(kName)};
}

absl::Status InMemoryProvider::Init(const EvaluationContext& ctx) {
{
std::unique_lock lock(mutex_);
status_ = ProviderStatus::kReady;
}
return absl::OkStatus();
}

absl::Status InMemoryProvider::Shutdown() {
{
std::unique_lock lock(mutex_);
status_ = ProviderStatus::kNotReady;
}
return absl::OkStatus();
}

void InMemoryProvider::UpdateFlags(
std::unordered_map<std::string, std::any> new_flags) {
std::unique_lock lock(mutex_);
for (auto& [key, value] : new_flags) {
flags_[key] = value;
}
}

void InMemoryProvider::UpdateFlag(std::string key, std::any new_flag) {
std::unique_lock lock(mutex_);
flags_.insert_or_assign(std::move(key), std::move(new_flag));
}

std::unique_ptr<BoolResolutionDetails> InMemoryProvider::GetBooleanEvaluation(
std::string_view key, bool default_value, const EvaluationContext& ctx) {
return Evaluate<bool>(key, default_value, ctx);
}

template <typename T>
std::unique_ptr<ResolutionDetails<T>> InMemoryProvider::Evaluate(
std::string_view key, T default_value, const EvaluationContext& ctx) {
std::shared_lock lock(mutex_);

if (status_ != ProviderStatus::kReady) {
if (status_ == ProviderStatus::kNotReady) {
return std::make_unique<ResolutionDetails<T>>(
default_value, Reason::kError, std::nullopt, FlagMetadata{},
ErrorCode::kProviderNotReady, "Provider is not ready");
}
if (status_ == ProviderStatus::kFatal) {
return std::make_unique<ResolutionDetails<T>>(
default_value, Reason::kError, std::nullopt, FlagMetadata{},
ErrorCode::kProviderFatal, "Provider is in fatal error state");
}
return std::make_unique<ResolutionDetails<T>>(
default_value, Reason::kError, std::nullopt, FlagMetadata{},
ErrorCode::kGeneral, "Unknown error");
}

std::string key_str{key};
auto it = flags_.find(key_str);
if (it == flags_.end()) {
return std::make_unique<ResolutionDetails<T>>(
default_value, Reason::kError, std::nullopt, FlagMetadata{},
ErrorCode::kFlagNotFound, "Flag " + key_str + " not found");
}

const Flag<T>* flag = std::any_cast<Flag<T>>(&it->second);

if (!flag) {
return std::make_unique<ResolutionDetails<T>>(
default_value, Reason::kError, std::nullopt, FlagMetadata{},
ErrorCode::kTypeMismatch, "Flag type mismatch");
}

if (flag->IsDisabled()) {
return std::make_unique<ResolutionDetails<T>>(
default_value, Reason::kDisabled, std::nullopt,
flag->GetFlagMetadata());
}

T value;
Reason reason = Reason::kStatic;
const std::optional<std::string>& variant_key = flag->GetDefaultVariant();
bool context_eval_success = false;
const auto& evaluator = flag->GetContextEvaluator();

if (evaluator != nullptr) {
absl::StatusOr<T> result = evaluator(*flag, ctx);

if (result.ok()) {
value = *result;
reason = Reason::kTargetingMatch;
context_eval_success = true;
} else {
reason = Reason::kDefault;
}
}

// Fallback to default variant if context evaluation failed.
if (!context_eval_success) {
if (variant_key.has_value()) {
const std::unordered_map<std::string, T>& variants = flag->GetVariants();
auto variant_it = variants.find(*variant_key);

if (variant_it != variants.end()) {
value = variant_it->second;
} else {
return std::make_unique<ResolutionDetails<T>>(
default_value, Reason::kError, variant_key, flag->GetFlagMetadata(),
ErrorCode::kParseError,
"Default variant " + *variant_key + " not found in variants map");
}
} else {
return std::make_unique<ResolutionDetails<T>>(
default_value, Reason::kDefault, std::nullopt,
flag->GetFlagMetadata());
}
}

return std::make_unique<ResolutionDetails<T>>(value, reason, variant_key,
flag->GetFlagMetadata());
}

} // namespace openfeature
61 changes: 61 additions & 0 deletions openfeature/memory_provider/in_memory_provider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#ifndef CPP_SDK_INCLUDE_OPENFEATURE_IN_MEMORY_PROVIDER_H_
#define CPP_SDK_INCLUDE_OPENFEATURE_IN_MEMORY_PROVIDER_H_

#include <any>
#include <memory>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <unordered_map>

#include "absl/status/status.h"
#include "openfeature/evaluation_context.h"
#include "openfeature/metadata.h"
#include "openfeature/provider.h"
#include "openfeature/provider_status.h"
#include "openfeature/resolution_details.h"

namespace openfeature {

// This class implements the FeatureProvider interface and is intended to be
// used for testing. It stores feature flags in memory and allows for
// evaluation based on the provided EvaluationContext.
class InMemoryProvider : public FeatureProvider {
public:
InMemoryProvider(std::unordered_map<std::string, std::any> flags);

~InMemoryProvider() = default;

Metadata GetMetadata() const override;

absl::Status Init(const EvaluationContext& ctx) override;
absl::Status Shutdown() override;

// Updates the provider flags configuration. All existing flags will be
Comment thread
NeaguGeorgiana23 marked this conversation as resolved.
// replaced with the new ones. If there are any new flags, they will be
// added to the configuration.
void UpdateFlags(std::unordered_map<std::string, std::any> new_flags);

// Updates a single flag in the provider configuration. If the flag already
// exists, it will be replaced with the new one. If it doesn't exist, it
// will be added to the configuration.
void UpdateFlag(std::string key, std::any new_flag);

std::unique_ptr<BoolResolutionDetails> GetBooleanEvaluation(
std::string_view key, bool default_value,
const EvaluationContext& ctx) override;

private:
template <typename T>
std::unique_ptr<ResolutionDetails<T>> Evaluate(std::string_view key,
T default_value,
const EvaluationContext& ctx);

std::unordered_map<std::string, std::any> flags_;
ProviderStatus status_;
mutable std::shared_mutex mutex_;
};

} // namespace openfeature

#endif // CPP_SDK_INCLUDE_OPENFEATURE_IN_MEMORY_PROVIDER_H_
19 changes: 19 additions & 0 deletions test/memory_provider/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")

cc_test(
name = "flag_test",
srcs = ["flag_test.cpp"],
deps = [
"//openfeature/memory_provider:flag",
"@googletest//:gtest_main",
],
)

cc_test(
name = "in_memory_provider_test",
srcs = ["in_memory_provider_test.cpp"],
deps = [
"//openfeature/memory_provider:in_memory_provider",
"@googletest//:gtest_main",
],
)
Loading