-
Notifications
You must be signed in to change notification settings - Fork 2
Implement in_memory_provider. #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | ||
| ], | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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_ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| // 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_ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | ||
| ], | ||
| ) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.