Skip to content
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

Config v6 #23

Merged
merged 55 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
fbf33fd
Update config JSON model to v6
adams85 Feb 25, 2024
dcfc886
Use int32_t instead of int for Int setting values for better portability
adams85 Feb 26, 2024
7d16133
Improve ConfigCatClient public API by making EvaluationDetails generi…
adams85 Feb 26, 2024
3d5e02b
Change user param type to shared_ptr<ConfigCatUser> + revise shared_p…
adams85 Feb 27, 2024
dcc4e54
Handle unexpected errors in evaluation methods (to guard against the …
adams85 Feb 27, 2024
1fd7c87
Remove ConfigCatLogger and SettingResult from public API
adams85 Feb 27, 2024
01eee63
Refactor evaluator and evaluation logging to prepare it for the new f…
adams85 Feb 28, 2024
35cb3f6
Make onConfigChanged hook's argument const to prevent accidental modi…
adams85 Mar 1, 2024
a5194f4
Revise std::move usage
adams85 Mar 4, 2024
88ed141
Implement new comparison operators
adams85 Mar 5, 2024
bcd433a
Implement segment condition evaluation
adams85 Mar 5, 2024
5fba7a9
Implement prerequisite flag condition evaluation
adams85 Mar 5, 2024
f813eb6
Implement SDK key format validation + fix broken tests
adams85 Mar 5, 2024
72d9e47
Improve message of error 1103 + improve error reporting + fix SDK not…
adams85 Mar 5, 2024
1d9dbc3
Improve code formatting consistency
adams85 Mar 5, 2024
9fc5e6c
coding style
kp-cat Mar 13, 2024
cf8f4e7
NamespaceIndentation: None
kp-cat Mar 13, 2024
c877ec7
config V2 rollout integration tests
kp-cat Mar 13, 2024
70ba662
evaluateSensitiveTextSliceEqualsAnyOf fix
kp-cat Mar 13, 2024
46e94f9
Correct mistakes in error messages
adams85 Mar 14, 2024
b7d987a
Handle unexpected errors in forceRefresh (to guard against the SDK br…
adams85 Mar 14, 2024
67b342f
Adjust terminology to docs (eliminate the usage of term 'match' in th…
adams85 Mar 14, 2024
a6adaa0
evaluation log test + evaluation fixes
kp-cat Mar 20, 2024
a248614
test fix
kp-cat Mar 20, 2024
9180ddd
specchar test + sdk validation test
kp-cat Mar 21, 2024
fa8953c
v2 eval fixes + tests
kp-cat Mar 27, 2024
69c74c3
etag fix
kp-cat Mar 27, 2024
4f829cf
We should go to the cache in all polling modes instead of using the i…
kp-cat Mar 27, 2024
908b3e5
test fix
kp-cat Mar 27, 2024
1da0ed8
windows regex fix
kp-cat Mar 27, 2024
7843c4a
ConfigCatUser::create
kp-cat Mar 28, 2024
177b779
ConfigV2EvaluationTest -> EvaluationTest
kp-cat Mar 28, 2024
aaf31b3
timeutils
kp-cat Mar 28, 2024
1413fae
OverrideValueTypeMismatchShouldBeHandledCorrectly_SimplifiedConfig
kp-cat Mar 28, 2024
107ad72
remove "this->"s + warning fixes
kp-cat Mar 28, 2024
d59f55f
Revert "OverrideValueTypeMismatchShouldBeHandledCorrectly_SimplifiedC…
kp-cat Mar 28, 2024
01cbbb7
ValueAndDefaultValueTypeCompatibilityTest
kp-cat Mar 28, 2024
6a88de3
keep just datetime_to_isostring and make_datetime on public interface
kp-cat Mar 28, 2024
9660a44
make_datetime fix
kp-cat Apr 1, 2024
89a35b8
version 4.0.0
kp-cat Apr 2, 2024
e0daf4d
make_datetime millisec 0 default value
kp-cat Apr 2, 2024
5028c45
Change the return type of ConfigCatClient.get to shared_ptr<ConfigCat…
adams85 Apr 2, 2024
4e58407
Fix clang error
adams85 Apr 2, 2024
942cec8
Add noexcept to move constructors/operators
adams85 Apr 2, 2024
b650be7
Minor corrections, maintain naming convention
adams85 Apr 2, 2024
9958ed7
Add a few addition tests for user attribute value conversion edge cases
adams85 Apr 2, 2024
c8f458d
Update samples in README.md
kp-cat Apr 2, 2024
fcc4cf5
Add guard against nullptr dereference in ConfigCatClient::close
adams85 Apr 2, 2024
25e5383
Call days_from_civil with 32-bit integer
adams85 Apr 2, 2024
681a9a0
Manual polling in EnsureCloseWorks on windows
kp-cat Apr 2, 2024
ab1a1e1
curl close fix
kp-cat Apr 2, 2024
f1de4dc
Add missing synchronization to ConfigCatClient::instanceCount
adams85 Apr 3, 2024
f151bc6
github CI: ctest -j4
kp-cat Apr 3, 2024
c678716
Improve assertions in EnsureCloseWorks test
adams85 Apr 3, 2024
b5e7da3
ResponseErrorCode + cancelled request handling
kp-cat Apr 3, 2024
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
6 changes: 6 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BasedOnStyle: Chromium
IndentWidth: 4
ColumnLimit: 160
NamespaceIndentation: None
# The number of spaces before trailing line comments (// - comments).
SpacesBeforeTrailingComments: 1
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ if(CONFIGCAT_BUILD_TESTS)
)
target_include_directories(google_tests PRIVATE ${CONFIGCAT_INCLUDE_PATHS})
# $<TARGET_PROPERTY:configcat,LINK_LIBRARIES> explicitly propagates private dependencies
target_link_libraries(google_tests configcat gtest_main $<TARGET_PROPERTY:configcat,LINK_LIBRARIES>)
target_link_libraries(google_tests configcat gmock_main $<TARGET_PROPERTY:configcat,LINK_LIBRARIES>)

gtest_discover_tests(google_tests)
endif()
Expand Down
498 changes: 344 additions & 154 deletions include/configcat/config.h

Large diffs are not rendered by default.

64 changes: 36 additions & 28 deletions include/configcat/configcatclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "keyvalue.h"
#include "configcatoptions.h"
#include "refreshresult.h"
#include "settingresult.h"
#include "evaluationdetails.h"


Expand All @@ -20,7 +19,7 @@ class ConfigFetcher;
class RolloutEvaluator;
class FlagOverrides;
class ConfigService;

struct SettingResult;

class ConfigCatClient {
public:
Expand All @@ -43,17 +42,17 @@ class ConfigCatClient {
* Parameter [defaultValue]: in case of any failure, this value will be returned.
* Parameter [user]: the user object to identify the caller.
*/
bool getValue(const std::string& key, bool defaultValue, const ConfigCatUser* user = nullptr) const;
int getValue(const std::string& key, int defaultValue, const ConfigCatUser* user = nullptr) const;
double getValue(const std::string& key, double defaultValue, const ConfigCatUser* user = nullptr) const;
std::string getValue(const std::string& key, const char* defaultValue, const ConfigCatUser* user = nullptr) const;
std::string getValue(const std::string& key, const std::string& defaultValue, const ConfigCatUser* user = nullptr) const;
bool getValue(const std::string& key, bool defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;
int32_t getValue(const std::string& key, int32_t defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;
double getValue(const std::string& key, double defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;
std::string getValue(const std::string& key, const char* defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;
std::string getValue(const std::string& key, const std::string& defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;

/**
* Gets the value of a feature flag or setting as std::shared_ptr<Value> identified by the given [key].
* In case of any failure, nullptr will be returned. The [user] param identifies the caller.
* Gets the value of a feature flag or setting as std::optional<Value> identified by the given [key].
* In case of any failure, std::nullopt will be returned. The [user] param identifies the caller.
*/
std::shared_ptr<Value> getValue(const std::string& key, const ConfigCatUser* user = nullptr) const;
std::optional<Value> getValue(const std::string& key, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;

/**
* Gets the value and evaluation details of a feature flag or setting identified by the given `key`.
Expand All @@ -62,34 +61,40 @@ class ConfigCatClient {
* Parameter [defaultValue]: in case of any failure, this value will be returned.
* Parameter [user]: the user object to identify the caller.
*/
EvaluationDetails getValueDetails(const std::string& key, bool defaultValue, const ConfigCatUser* user = nullptr) const;
EvaluationDetails getValueDetails(const std::string& key, int defaultValue, const ConfigCatUser* user = nullptr) const;
EvaluationDetails getValueDetails(const std::string& key, double defaultValue, const ConfigCatUser* user = nullptr) const;
EvaluationDetails getValueDetails(const std::string& key, const std::string& defaultValue, const ConfigCatUser* user = nullptr) const;
EvaluationDetails getValueDetails(const std::string& key, const char* defaultValue, const ConfigCatUser* user = nullptr) const;
EvaluationDetails<bool> getValueDetails(const std::string& key, bool defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;
EvaluationDetails<int32_t> getValueDetails(const std::string& key, int32_t defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;
EvaluationDetails<double> getValueDetails(const std::string& key, double defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;
EvaluationDetails<std::string> getValueDetails(const std::string& key, const std::string& defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;
EvaluationDetails<std::string> getValueDetails(const std::string& key, const char* defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;

/**
* Gets the value and evaluation details of a feature flag or setting identified by the given [key].
* In case of any failure, the [value] field of the returned EvaluationDetails struct will be set to std::nullopt. The [user] param identifies the caller.
*/
EvaluationDetails<std::optional<Value>> getValueDetails(const std::string& key, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;

// Gets all the setting keys.
std::vector<std::string> getAllKeys() const;

// Gets the key of a setting and it's value identified by the given Variation ID (analytics)
std::shared_ptr<KeyValue> getKeyAndValue(const std::string& variationId) const;
std::optional<KeyValue> getKeyAndValue(const std::string& variationId) const;

// Gets the values of all feature flags or settings.
std::unordered_map<std::string, Value> getAllValues(const ConfigCatUser* user = nullptr) const;
std::unordered_map<std::string, Value> getAllValues(const std::shared_ptr<ConfigCatUser>& user = nullptr) const;

// Gets the values along with evaluation details of all feature flags and settings.
std::vector<EvaluationDetails> getAllValueDetails(const ConfigCatUser* user = nullptr) const;
std::vector<EvaluationDetails<Value>> getAllValueDetails(const std::shared_ptr<ConfigCatUser>& user = nullptr) const;

// Initiates a force refresh synchronously on the cached configuration.
void forceRefresh();
RefreshResult forceRefresh();

// Sets the default user.
void setDefaultUser(std::shared_ptr<ConfigCatUser> user) {
inline void setDefaultUser(const std::shared_ptr<ConfigCatUser>& user) {
defaultUser = user;
}

// Sets the default user to nullptr.
void clearDefaultUser() {
inline void clearDefaultUser() {
defaultUser.reset();
}

Expand All @@ -103,23 +108,26 @@ class ConfigCatClient {
bool isOffline() const;

// Gets the Hooks object for subscribing events.
std::shared_ptr<Hooks> getHooks() { return hooks; }
inline std::shared_ptr<Hooks> getHooks() { return hooks; }

private:
ConfigCatClient(const std::string& sdkKey, const ConfigCatOptions& options);

template<typename ValueType>
ValueType _getValue(const std::string& key, const ValueType& defaultValue, const ConfigCatUser* user = nullptr) const;
ValueType _getValue(const std::string& key, const ValueType& defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;

template<typename ValueType>
EvaluationDetails _getValueDetails(const std::string& key, ValueType defaultValue, const ConfigCatUser* user = nullptr) const;
EvaluationDetails<ValueType> _getValueDetails(const std::string& key, const ValueType& defaultValue, const std::shared_ptr<ConfigCatUser>& user = nullptr) const;

SettingResult getSettings() const;

EvaluationDetails evaluate(const std::string& key,
const ConfigCatUser* user,
const Setting& setting,
double fetchTime) const;
template<typename ValueType>
EvaluationDetails<ValueType> evaluate(const std::string& key,
const std::optional<Value>& defaultValue,
const std::shared_ptr<ConfigCatUser>& effectiveUser,
const Setting& setting,
const std::shared_ptr<Settings>& settings,
double fetchTime) const;

std::shared_ptr<Hooks> hooks;
std::shared_ptr<ConfigCatLogger> logger;
Expand Down
28 changes: 15 additions & 13 deletions include/configcat/configcatoptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <functional>
#include <vector>
#include <mutex>
#include <exception>
#include <optional>
#include "datagovernance.h"
#include "pollingmode.h"
#include "configcache.h"
Expand All @@ -21,9 +23,9 @@ namespace configcat {
class Hooks {
public:
explicit Hooks(const std::function<void()>& onClientReady = nullptr,
const std::function<void(std::shared_ptr<Settings>)>& onConfigChanged = nullptr,
const std::function<void(const EvaluationDetails&)>& onFlagEvaluated = nullptr,
const std::function<void(const std::string&)>& onError = nullptr) {
const std::function<void(std::shared_ptr<const Settings>)>& onConfigChanged = nullptr,
const std::function<void(const EvaluationDetailsBase&)>& onFlagEvaluated = nullptr,
const std::function<void(const std::string&, const std::exception_ptr&)>& onError = nullptr) {
if (onClientReady) {
onClientReadyCallbacks.push_back(onClientReady);
}
Expand All @@ -43,17 +45,17 @@ class Hooks {
onClientReadyCallbacks.push_back(callback);
}

void addOnConfigChanged(const std::function<void(std::shared_ptr<Settings>)>& callback) {
void addOnConfigChanged(const std::function<void(std::shared_ptr<const Settings>)>& callback) {
std::lock_guard<std::mutex> lock(mutex);
onConfigChangedCallbacks.push_back(callback);
}

void addOnFlagEvaluated(const std::function<void(const EvaluationDetails&)>& callback) {
void addOnFlagEvaluated(const std::function<void(const EvaluationDetailsBase&)>& callback) {
std::lock_guard<std::mutex> lock(mutex);
onFlagEvaluatedCallbacks.push_back(callback);
}

void addOnError(const std::function<void(const std::string&)>& callback) {
void addOnError(const std::function<void(const std::string&, const std::exception_ptr&)>& callback) {
std::lock_guard<std::mutex> lock(mutex);
onErrorCallbacks.push_back(callback);
}
Expand All @@ -65,24 +67,24 @@ class Hooks {
}
}

void invokeOnConfigChanged(std::shared_ptr<Settings> config) {
void invokeOnConfigChanged(const std::shared_ptr<Settings>& config) {
std::lock_guard<std::mutex> lock(mutex);
for (auto& callback : onConfigChangedCallbacks) {
callback(config);
}
}

void invokeOnFlagEvaluated(const EvaluationDetails& details) {
void invokeOnFlagEvaluated(const EvaluationDetailsBase& details) {
std::lock_guard<std::mutex> lock(mutex);
for (auto& callback : onFlagEvaluatedCallbacks) {
callback(details);
}
}

void invokeOnError(const std::string& error) {
void invokeOnError(const std::string& message, const std::exception_ptr& exception) {
std::lock_guard<std::mutex> lock(mutex);
for (auto& callback : onErrorCallbacks) {
callback(error);
callback(message, exception);
}
}

Expand All @@ -97,9 +99,9 @@ class Hooks {
private:
std::mutex mutex;
std::vector<std::function<void()>> onClientReadyCallbacks;
std::vector<std::function<void(std::shared_ptr<Settings>)>> onConfigChangedCallbacks;
std::vector<std::function<void(const EvaluationDetails&)>> onFlagEvaluatedCallbacks;
std::vector<std::function<void(const std::string&)>> onErrorCallbacks;
std::vector<std::function<void(std::shared_ptr<const Settings>)>> onConfigChangedCallbacks;
std::vector<std::function<void(const EvaluationDetailsBase&)>> onFlagEvaluatedCallbacks;
std::vector<std::function<void(const std::string&, const std::exception_ptr&)>> onErrorCallbacks;
};

// Configuration options for ConfigCatClient.
Expand Down
84 changes: 76 additions & 8 deletions include/configcat/configcatuser.h
Original file line number Diff line number Diff line change
@@ -1,26 +1,94 @@
#pragma once

#include <chrono>
#include <string>
#include <optional>
#include <unordered_map>
#include <variant>
#include <vector>

namespace configcat {

using date_time_t = std::chrono::system_clock::time_point;

// An object containing attributes to properly identify a given user for rollout evaluation.
class ConfigCatUser {
public:
struct AttributeValue : public std::variant<std::string, double, date_time_t, std::vector<std::string>> {
private:
using _Base = std::variant<std::string, double, date_time_t, std::vector<std::string>>;
public:
AttributeValue(const char* v) : _Base(std::string(v)) {}
// CLang number type conversion to variant<double> fix
AttributeValue(double value) : _Base(value) {}

// Disable the implicit conversion from pointer to bool: https://stackoverflow.com/a/59372958/8656352
template<typename T>
AttributeValue(T*) = delete;

using _Base::_Base;
using _Base::operator=;
};

static constexpr char kIdentifierAttribute[] = "Identifier";
static constexpr char kEmailAttribute[] = "Email";
static constexpr char kCountryAttribute[] = "Country";

/**
* Creates a new instance of the [ConfigCatUser] class.
*
* Parameter [id]: the unique identifier of the user or session (e.g. email address, primary key, session ID, etc.)
* Parameter [email]: email address of the user.
* Parameter [country]: country of the user.
* Parameter [custom]: custom attributes of the user for advanced targeting rule definitions (e.g. user role, subscription type, etc.)
*
* All comparators support `std::string` values as User Object attribute (in some cases they need to be provided in a specific format though, see below),
* but some of them also support other types of values. It depends on the comparator how the values will be handled. The following rules apply:
*
* **Text-based comparators** (EQUALS, IS ONE OF, etc.)
* * accept `std::string` values,
* * all other values are automatically converted to `std::string` (a warning will be logged but evaluation will continue as normal).
*
* **SemVer-based comparators** (IS ONE OF, &lt;, &gt;=, etc.)
* * accept `std::string` values containing a properly formatted, valid semver value,
* * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped).
*
* **Number-based comparators** (=, &lt;, &gt;=, etc.)
* * accept `double` values,
* * accept `std::string` values containing a properly formatted, valid `double` value,
* * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped).
*
* **Date time-based comparators** (BEFORE / AFTER)
* * accept `configcat::date_time_t` (`std::chrono::system_clock::time_point`) values,
which are automatically converted to a second-based Unix timestamp,
* * accept `double` values representing a second-based Unix timestamp,
* * accept `std::string` values containing a properly formatted, valid `double` value,
* * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped).
*
* **String array-based comparators** (ARRAY CONTAINS ANY OF / ARRAY NOT CONTAINS ANY OF)
* * accept lists of `std::string` (i.e. `std::vector<std::string>`),
* * accept `std::string` values containing a valid JSON string which can be deserialized to a list of `std::string`,
* * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped).
*/
ConfigCatUser(const std::string& id,
const std::string& email = {},
const std::string& country = {},
const std::unordered_map<std::string, std::string>& custom = {});
const std::optional<std::string>& email = std::nullopt,
const std::optional<std::string>& country = std::nullopt,
const std::unordered_map<std::string, ConfigCatUser::AttributeValue>& custom = {})
: identifier(id)
, email(email)
, country(country)
, custom(custom) {}

const std::string* getAttribute(const std::string& key) const;
inline const std::string& getIdentifier() const { return std::get<std::string>(this->identifier); }
inline const ConfigCatUser::AttributeValue& getIdentifierAttribute() const { return this->identifier; }
const ConfigCatUser::AttributeValue* getAttribute(const std::string& key) const;
std::string toJson() const;

private:
std::unordered_map<std::string, std::string> attributes;

public:
const std::string& identifier;
ConfigCatUser::AttributeValue identifier;
std::optional<ConfigCatUser::AttributeValue> email;
std::optional<ConfigCatUser::AttributeValue> country;
std::unordered_map<std::string, AttributeValue> custom;
};

} // namespace configcat
3 changes: 2 additions & 1 deletion include/configcat/consolelogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ class ConsoleLogger : public ILogger {
public:
ConsoleLogger(LogLevel logLevel = LOG_LEVEL_WARNING): ILogger(logLevel) {}

void log(LogLevel level, const std::string& message) override {
void log(LogLevel level, const std::string& message, const std::exception_ptr& exception = nullptr) override {
printf("[%s]: %s\n", logLevelAsString(level), message.c_str());
if (exception) printf("Exception details: %s\n", unwrap_exception_message(exception).c_str());
}
};

Expand Down
Loading
Loading