Skip to content

Commit bdb2b4c

Browse files
Implement in_memory_provider. (#61)
* Implement in_memory_provider. Signed-off-by: NeaguGeorgiana23 <neagugeorgiana@google.com> * Fix comments Signed-off-by: NeaguGeorgiana23 <neagugeorgiana@google.com> * Resolve comments. Signed-off-by: NeaguGeorgiana23 <neagugeorgiana@google.com> --------- Signed-off-by: NeaguGeorgiana23 <neagugeorgiana@google.com>
1 parent 3af9647 commit bdb2b4c

7 files changed

Lines changed: 812 additions & 0 deletions

File tree

openfeature/memory_provider/BUILD

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
load("@rules_cc//cc:defs.bzl", "cc_library")
2+
3+
package(
4+
default_visibility = ["//visibility:public"],
5+
)
6+
7+
cc_library(
8+
name = "flag",
9+
hdrs = ["flag.h"],
10+
include_prefix = "openfeature",
11+
deps = [
12+
"//openfeature:evaluation_context",
13+
"//openfeature:flag_metadata",
14+
"@abseil-cpp//absl/status:statusor",
15+
],
16+
)
17+
18+
cc_library(
19+
name = "in_memory_provider",
20+
srcs = ["in_memory_provider.cpp"],
21+
hdrs = ["in_memory_provider.h"],
22+
include_prefix = "openfeature",
23+
deps = [
24+
"//openfeature:error_code",
25+
"//openfeature:evaluation_context",
26+
":flag",
27+
"//openfeature:metadata",
28+
"//openfeature:provider",
29+
"//openfeature:provider_status",
30+
"//openfeature:reason",
31+
"//openfeature:resolution_details",
32+
"@abseil-cpp//absl/status",
33+
"@abseil-cpp//absl/status:statusor",
34+
],
35+
)

openfeature/memory_provider/flag.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#ifndef CPP_SDK_INCLUDE_OPENFEATURE_MEMORY_PROVIDER_FLAG_H_
2+
#define CPP_SDK_INCLUDE_OPENFEATURE_MEMORY_PROVIDER_FLAG_H_
3+
4+
#include <functional>
5+
#include <optional>
6+
#include <string>
7+
#include <unordered_map>
8+
#include <utility>
9+
10+
#include "absl/status/statusor.h"
11+
#include "openfeature/evaluation_context.h"
12+
#include "openfeature/flag_metadata.h"
13+
14+
namespace openfeature {
15+
16+
// This class represents the flag structure used for the InMemoryProvider.
17+
template <typename T>
18+
class Flag {
19+
public:
20+
using ContextEvaluator =
21+
std::function<absl::StatusOr<T>(const Flag&, const EvaluationContext&)>;
22+
23+
Flag(std::unordered_map<std::string, T> variants,
24+
std::optional<std::string> default_variant,
25+
ContextEvaluator context_evaluator, FlagMetadata flag_metadata,
26+
bool disabled = false)
27+
: variants_(std::move(variants)),
28+
default_variant_(std::move(default_variant)),
29+
context_evaluator_(std::move(context_evaluator)),
30+
flag_metadata_(std::move(flag_metadata)),
31+
disabled_(disabled) {}
32+
33+
const std::unordered_map<std::string, T>& GetVariants() const {
34+
return variants_;
35+
}
36+
37+
const std::optional<std::string>& GetDefaultVariant() const {
38+
return default_variant_;
39+
}
40+
41+
const ContextEvaluator& GetContextEvaluator() const {
42+
return context_evaluator_;
43+
}
44+
45+
const FlagMetadata& GetFlagMetadata() const { return flag_metadata_; }
46+
47+
bool IsDisabled() const { return disabled_; }
48+
49+
private:
50+
std::unordered_map<std::string, T> variants_;
51+
std::optional<std::string> default_variant_;
52+
ContextEvaluator context_evaluator_;
53+
FlagMetadata flag_metadata_;
54+
bool disabled_;
55+
};
56+
} // namespace openfeature
57+
58+
#endif // CPP_SDK_INCLUDE_OPENFEATURE_MEMORY_PROVIDER_FLAG_H_
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#include "openfeature/memory_provider/in_memory_provider.h"
2+
3+
#include <mutex>
4+
#include <optional>
5+
#include <utility>
6+
7+
#include "absl/status/statusor.h"
8+
#include "openfeature/error_code.h"
9+
#include "openfeature/memory_provider/flag.h"
10+
#include "openfeature/reason.h"
11+
12+
namespace openfeature {
13+
14+
static constexpr std::string_view kName = "InMemoryProvider";
15+
16+
InMemoryProvider::InMemoryProvider(
17+
std::unordered_map<std::string, std::any> flags)
18+
: flags_(std::move(flags)), status_(ProviderStatus::kNotReady) {}
19+
20+
Metadata InMemoryProvider::GetMetadata() const {
21+
return Metadata{std::string(kName)};
22+
}
23+
24+
absl::Status InMemoryProvider::Init(const EvaluationContext& ctx) {
25+
{
26+
std::unique_lock lock(mutex_);
27+
status_ = ProviderStatus::kReady;
28+
}
29+
return absl::OkStatus();
30+
}
31+
32+
absl::Status InMemoryProvider::Shutdown() {
33+
{
34+
std::unique_lock lock(mutex_);
35+
status_ = ProviderStatus::kNotReady;
36+
}
37+
return absl::OkStatus();
38+
}
39+
40+
void InMemoryProvider::UpdateFlags(
41+
std::unordered_map<std::string, std::any> new_flags) {
42+
std::unique_lock lock(mutex_);
43+
for (auto& [key, value] : new_flags) {
44+
flags_[key] = value;
45+
}
46+
}
47+
48+
void InMemoryProvider::UpdateFlag(std::string key, std::any new_flag) {
49+
std::unique_lock lock(mutex_);
50+
flags_.insert_or_assign(std::move(key), std::move(new_flag));
51+
}
52+
53+
std::unique_ptr<BoolResolutionDetails> InMemoryProvider::GetBooleanEvaluation(
54+
std::string_view key, bool default_value, const EvaluationContext& ctx) {
55+
return Evaluate<bool>(key, default_value, ctx);
56+
}
57+
58+
template <typename T>
59+
std::unique_ptr<ResolutionDetails<T>> InMemoryProvider::Evaluate(
60+
std::string_view key, T default_value, const EvaluationContext& ctx) {
61+
std::shared_lock lock(mutex_);
62+
63+
if (status_ != ProviderStatus::kReady) {
64+
if (status_ == ProviderStatus::kNotReady) {
65+
return std::make_unique<ResolutionDetails<T>>(
66+
default_value, Reason::kError, std::nullopt, FlagMetadata{},
67+
ErrorCode::kProviderNotReady, "Provider is not ready");
68+
}
69+
if (status_ == ProviderStatus::kFatal) {
70+
return std::make_unique<ResolutionDetails<T>>(
71+
default_value, Reason::kError, std::nullopt, FlagMetadata{},
72+
ErrorCode::kProviderFatal, "Provider is in fatal error state");
73+
}
74+
return std::make_unique<ResolutionDetails<T>>(
75+
default_value, Reason::kError, std::nullopt, FlagMetadata{},
76+
ErrorCode::kGeneral, "Unknown error");
77+
}
78+
79+
std::string key_str{key};
80+
auto it = flags_.find(key_str);
81+
if (it == flags_.end()) {
82+
return std::make_unique<ResolutionDetails<T>>(
83+
default_value, Reason::kError, std::nullopt, FlagMetadata{},
84+
ErrorCode::kFlagNotFound, "Flag " + key_str + " not found");
85+
}
86+
87+
const Flag<T>* flag = std::any_cast<Flag<T>>(&it->second);
88+
89+
if (!flag) {
90+
return std::make_unique<ResolutionDetails<T>>(
91+
default_value, Reason::kError, std::nullopt, FlagMetadata{},
92+
ErrorCode::kTypeMismatch, "Flag type mismatch");
93+
}
94+
95+
if (flag->IsDisabled()) {
96+
return std::make_unique<ResolutionDetails<T>>(
97+
default_value, Reason::kDisabled, std::nullopt,
98+
flag->GetFlagMetadata());
99+
}
100+
101+
T value;
102+
Reason reason = Reason::kStatic;
103+
const std::optional<std::string>& variant_key = flag->GetDefaultVariant();
104+
bool context_eval_success = false;
105+
const auto& evaluator = flag->GetContextEvaluator();
106+
107+
if (evaluator != nullptr) {
108+
absl::StatusOr<T> result = evaluator(*flag, ctx);
109+
110+
if (result.ok()) {
111+
value = *result;
112+
reason = Reason::kTargetingMatch;
113+
context_eval_success = true;
114+
} else {
115+
reason = Reason::kDefault;
116+
}
117+
}
118+
119+
// Fallback to default variant if context evaluation failed.
120+
if (!context_eval_success) {
121+
if (variant_key.has_value()) {
122+
const std::unordered_map<std::string, T>& variants = flag->GetVariants();
123+
auto variant_it = variants.find(*variant_key);
124+
125+
if (variant_it != variants.end()) {
126+
value = variant_it->second;
127+
} else {
128+
return std::make_unique<ResolutionDetails<T>>(
129+
default_value, Reason::kError, variant_key, flag->GetFlagMetadata(),
130+
ErrorCode::kParseError,
131+
"Default variant " + *variant_key + " not found in variants map");
132+
}
133+
} else {
134+
return std::make_unique<ResolutionDetails<T>>(
135+
default_value, Reason::kDefault, std::nullopt,
136+
flag->GetFlagMetadata());
137+
}
138+
}
139+
140+
return std::make_unique<ResolutionDetails<T>>(value, reason, variant_key,
141+
flag->GetFlagMetadata());
142+
}
143+
144+
} // namespace openfeature
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#ifndef CPP_SDK_INCLUDE_OPENFEATURE_IN_MEMORY_PROVIDER_H_
2+
#define CPP_SDK_INCLUDE_OPENFEATURE_IN_MEMORY_PROVIDER_H_
3+
4+
#include <any>
5+
#include <memory>
6+
#include <shared_mutex>
7+
#include <string>
8+
#include <string_view>
9+
#include <unordered_map>
10+
11+
#include "absl/status/status.h"
12+
#include "openfeature/evaluation_context.h"
13+
#include "openfeature/metadata.h"
14+
#include "openfeature/provider.h"
15+
#include "openfeature/provider_status.h"
16+
#include "openfeature/resolution_details.h"
17+
18+
namespace openfeature {
19+
20+
// This class implements the FeatureProvider interface and is intended to be
21+
// used for testing. It stores feature flags in memory and allows for
22+
// evaluation based on the provided EvaluationContext.
23+
class InMemoryProvider : public FeatureProvider {
24+
public:
25+
InMemoryProvider(std::unordered_map<std::string, std::any> flags);
26+
27+
~InMemoryProvider() = default;
28+
29+
Metadata GetMetadata() const override;
30+
31+
absl::Status Init(const EvaluationContext& ctx) override;
32+
absl::Status Shutdown() override;
33+
34+
// Updates the provider flags configuration. All existing flags will be
35+
// replaced with the new ones. If there are any new flags, they will be
36+
// added to the configuration.
37+
void UpdateFlags(std::unordered_map<std::string, std::any> new_flags);
38+
39+
// Updates a single flag in the provider configuration. If the flag already
40+
// exists, it will be replaced with the new one. If it doesn't exist, it
41+
// will be added to the configuration.
42+
void UpdateFlag(std::string key, std::any new_flag);
43+
44+
std::unique_ptr<BoolResolutionDetails> GetBooleanEvaluation(
45+
std::string_view key, bool default_value,
46+
const EvaluationContext& ctx) override;
47+
48+
private:
49+
template <typename T>
50+
std::unique_ptr<ResolutionDetails<T>> Evaluate(std::string_view key,
51+
T default_value,
52+
const EvaluationContext& ctx);
53+
54+
std::unordered_map<std::string, std::any> flags_;
55+
ProviderStatus status_;
56+
mutable std::shared_mutex mutex_;
57+
};
58+
59+
} // namespace openfeature
60+
61+
#endif // CPP_SDK_INCLUDE_OPENFEATURE_IN_MEMORY_PROVIDER_H_

test/memory_provider/BUILD

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
2+
3+
cc_test(
4+
name = "flag_test",
5+
srcs = ["flag_test.cpp"],
6+
deps = [
7+
"//openfeature/memory_provider:flag",
8+
"@googletest//:gtest_main",
9+
],
10+
)
11+
12+
cc_test(
13+
name = "in_memory_provider_test",
14+
srcs = ["in_memory_provider_test.cpp"],
15+
deps = [
16+
"//openfeature/memory_provider:in_memory_provider",
17+
"@googletest//:gtest_main",
18+
],
19+
)

0 commit comments

Comments
 (0)