diff --git a/src/settings_manager.hpp b/src/settings_manager.hpp index 989da29..d24165c 100644 --- a/src/settings_manager.hpp +++ b/src/settings_manager.hpp @@ -1,10 +1,6 @@ #pragma once #include "file_io.hpp" -#include "exceptions.hpp" -#include "string/conversion.hpp" -#include "string/utility.hpp" -#include "concepts.hpp" #include #include @@ -13,65 +9,12 @@ #include #include #include -#include -#include + +#include "settings_manager/common.hpp" +#include "settings_manager/restricted_value.hpp" namespace gz { - template - concept Number = std::integral || std::floating_point; - - template - concept NotNumber = !Number; - - /* template */ - /* concept IntegralInPack = std::integral && util::IsInPack; */ - - /* template */ - /* concept FloatingPointInPack = std::floating_point && util::IsInPack; */ - - template - concept NumberInPack = Number && util::IsInPack; - - template - concept NotNumberInPack = NotNumber && util::IsInPack; - - - - enum SettingsManagerAllowedValueTypes { - SM_RANGE, SM_LIST, - }; - - /** - * @brief Information about the allowed values - * @details - * If type is - * - SM_RANGE -> allowedValues must contain two numbers [min, max] - * - SM_LIST -> allowedValues must contain strings, which are the allowed values - */ - template - struct SettingsManagerAllowedValues { - SettingsManagerAllowedValueTypes type; - std::variant, std::vector... > allowedValues; - }; - /** - * @brief Check if struct is valid. - * @throws InvalidType if: - * - the type contained in the variant allowedValues is not T - * - type is SM_RANGE but T is not an integral or floating point type - * - * @throws InvalidArgument if: - * - vector contained in allowedValues is empty - * - type is SM_RANGE but allowedValues is not of size 2 - * - type is SM_RANGE but allowedValues[0] >= allowedValues[1] - */ - template - void hasCorrectFormat(SettingsManagerAllowedValues& ab); - template - void hasCorrectFormat(SettingsManagerAllowedValues& av); - /* template */ - /* void hasCorrectFormat(SettingsManagerAllowedValues& ab); */ - /** * @brief Creation info for SettingsManager */ @@ -102,22 +45,6 @@ namespace gz { * @brief Wether to write the values to filepath when destroying the SettingsManager */ bool writeFileOnExit = false; - /* - * @brief A map containing SettingsMangerAllowedValues to restrict values - * @details - * If allowedValues contains key, its value must be allowed by SettingsMangerAllowedValues struct. - * If it is not allowed, any operation that tries to set an invalid value will throw InvalidArgument or be ignored: - * - in constructor: always ignored - * - in SettingsManager::readFileOnCreation: always ignored - * - in SettingsManager::setAllowedValues: always ignored - * - in SettingsManager::set: depends on throwExceptionWhenNewValueNotAllowed - * @see sm_validity - */ - util::unordered_string_map> allowedValues; - /** - * @brief Wether to throw an exception when trying to set an invalid value - */ - bool throwExceptionWhenNewValueNotAllowed = true; }; using SettingsCallbackFunction = std::function; @@ -129,11 +56,6 @@ namespace gz { * The SettingsManager is basically a map with extra features. * It stores key-value pairs in a map. Both key and value are strings, but other types can also be stored. * - * @section sm_validity Restricting values - * SettingsManager can restrict the values for a given keys to a range of numbers, or a list of possible strings. - * This can be done by providing @ref SettingsMangerAllowedValues::allowedValues "a map containing information on the allowed values" in the create info. - * The settings manager will then never return an invalid value, but it might still throw errors if there is no value set. - * * @section sm_cache Storing other types * While all values are internally strings, SettingsManager can also store different types. * The types must satisfy the concept StringConvertible = ConstructibleFromString and ConvertibleToString. @@ -293,11 +215,11 @@ namespace gz { * @ingroup sm_allowed_values * @brief Check if a value is allowed for key */ - template T> + template T> bool isValueAllowed(const std::string& key, const T& value) const noexcept; /* template T> */ /* bool isValueAllowed(const std::string& key, const T& value) const noexcept; */ - template T> + template T> bool isValueAllowed(const std::string& key, const T& value) const noexcept; /** @@ -317,7 +239,7 @@ namespace gz { template T> void setAllowedValues(const std::string& key, std::vector& allowedValues, SettingsManagerAllowedValueTypes type=SM_LIST); template T> - void setAllowedValues(const std::string& key, std::vector&& allowedValues, SettingsManagerAllowedValueTypes type=SM_LIST) { setAllowedValues(key, allowedValues, type); }; + void setAllowedValues(const std::string& key, std::vector&& allowedValues, SettingsManagerAllowedValueTypes type=SM_LIST) { setAllowedValues(key, allowedValues, type); } /** * @ingroup sm_allowed_values @@ -352,7 +274,7 @@ namespace gz { * */ template Void> - void initCache() {}; + void initCache() {} std::set cacheTypes; util::unordered_string_map>> settingsCache; /* template */ @@ -360,23 +282,6 @@ namespace gz { /** * @} */ - /** - * @name Restricting values - * @see sm_validity - * @{ - */ - // Allowed Values - /** - * @brief Make sure the allowedValues map is valid - * @throws InvalidArgument if any of the initial allowedValues structs is invalid - */ - void initAllowedValues(); - util::unordered_string_map> allowedValues; - bool throwExceptionWhenNewValueNotAllowed; - /** - * @} - */ - util::unordered_string_map settings; util::unordered_string_map settingsCallbackFunctions; @@ -391,94 +296,6 @@ namespace gz { } namespace gz { - /* template T> */ - /* template */ - /* void hasCorrectFormat(SettingsManagerAllowedValues& av) { */ - /* const std::vector* v = nullptr; */ - /* try { */ - /* v = &std::get>(av.allowedValues); */ - /* } */ - /* catch (std::bad_variant_access& e) { */ - /* throw InvalidType("allowedValues variant does not contain type T", "SettingsManagerAllowedValues::hasCorrectFormat"); */ - /* } */ - /* switch (av.type) { */ - /* case SM_LIST: */ - /* if (v->empty()) { */ - /* throw InvalidArgument("Allowed value vector needs to have at least one element when AllowedValueType is SM_LIST, but is empty.", "SettingsManagerAllowedValues::hasCorrectFormat"); */ - /* } */ - /* break; */ - /* case SM_RANGE: */ - /* static_assert(std::floating_point || std::integral, "Type must be integral or floating point when using SM_RANGE."); */ - /* std::size_t size = std::get>(av.allowedValues).size(); */ - /* if (size == 2) { */ - /* v->push_back(static_cast(1)); */ - /* } */ - /* else if (size != 3) { */ - /* throw InvalidArgument("AllowedValueType is SM_RANGE with integral type but allowedValues does not have size 2 or 3.", "SettingsManagerAllowedValues::hasCorrectFormat"); */ - /* } */ - /* // check min < max */ - /* if (v[0] >= v[1]) { */ - /* throw InvalidArgument("AllowedValueType is SM_RANGE but allowedValues[0] is larger than allowedValues[1].", "SettingsManagerAllowedValues::hasCorrectFormat"); */ - /* } */ - /* break; */ - /* } // switch */ - /* } */ - - template - void hasCorrectFormat(SettingsManagerAllowedValues& av) { - static_assert(util::IsInPack, "T must be be in pack CacheTypes"); - const std::vector* v = nullptr; - try { - v = &std::get>(av.allowedValues); - } - catch (std::bad_variant_access& e) { - throw InvalidType("allowedValues variant does not contain type T", "SettingsManagerAllowedValues::hasCorrectFormat"); - } - switch (av.type) { - case SM_LIST: - if (v->empty()) { - throw InvalidArgument("Allowed value vector needs to have at least one element when AllowedValueType is SM_LIST, but is empty.", "SettingsManagerAllowedValues::hasCorrectFormat"); - } - break; - case SM_RANGE: - static_assert(std::floating_point || std::integral, "Type must be integral or floating point when using SM_RANGE."); - std::size_t size = v->size(); - if (size != 2) { - throw InvalidArgument("AllowedValueType is SM_RANGE with floating point type but allowedValues does not have size 2.", "SettingsManagerAllowedValues::hasCorrectFormat"); - } - // check min <= max - if (v->at(0) > v->at(1)) { - /* throw InvalidArgument("AllowedValueType is SM_RANGE but allowedValues[0] >= allowedValues[1].", "SettingsManagerAllowedValues::hasCorrectFormat"); */ - throw InvalidArgument("AllowedValueType is SM_RANGE but allowedValues[0]=" + toString(v->at(0)) + " > " + toString(v->at(1)) + "=allowedValues[1].", "SettingsManagerAllowedValues::hasCorrectFormat"); - } - break; - } // switch - } - - /* template T> */ - template - void hasCorrectFormat(SettingsManagerAllowedValues& av) { - static_assert(util::IsInPack, "T must be be in pack CacheTypes"); - const std::vector* v = nullptr; - try { - v = &std::get>(av.allowedValues); - } - catch (std::bad_variant_access& e) { - throw InvalidType("allowedValues variant does not contain type T", "SettingsManagerAllowedValues::hasCorrectFormat"); - } - switch (av.type) { - case SM_LIST: - if (v->empty()) { - throw InvalidArgument("Allowed value vector needs to have at least one element when AllowedValueType is SM_LIST, but is empty.", "SettingsManagerAllowedValues::hasCorrectFormat"); - } - break; - case SM_RANGE: - throw InvalidArgument("Type must be integral or floating point when using SM_RANGE, but is <" + std::string(typeid(T).name()) + ">"); - break; - } // switch - } - - // // SETTINGS MANAGER // @@ -486,9 +303,6 @@ namespace gz { SettingsManager::SettingsManager(SettingsManagerCreateInfo& createInfo) { insertFallbacks = createInfo.insertFallbacks; writeFileOnExit = createInfo.writeFileOnExit; - throwExceptionWhenNewValueNotAllowed = createInfo.throwExceptionWhenNewValueNotAllowed; - - allowedValues = std::move(createInfo.allowedValues); settings = std::move(createInfo.initialValues); filepath = createInfo.filepath; @@ -496,13 +310,6 @@ namespace gz { readFromFile(false); } - // erase invalid initalValues - for (auto it = settings.begin(); it != settings.end(); it++) { - if (!isValueAllowed(it->first, it->second)) { - it = settings.erase(it); - } - } - initCache(); } @@ -620,15 +427,6 @@ namespace gz { // template void SettingsManager::set(const std::string& key, const std::string& value) { - // check if new value is allowed - if (!isValueAllowed(key, value)) { - if (throwExceptionWhenNewValueNotAllowed) { - throw InvalidArgument("Value '" + value + "' is not allowed. Key: '" + key + "'", "SettingsManager::set"); - } - else { - return; - } - } // remove old value from cache for (auto& [type, cache] : settingsCache) { if (cache.contains(key)) { @@ -658,21 +456,13 @@ namespace gz { try { s = gz::toString(value); } + // TODO why cathes? catch (std::exception& e) { throw InvalidArgument("Could not convert value to string, an exception occured: '" + std::string(e.what()) + "'. Key: '" + key + "'", "SettingsManager::set<" + std::string(typeid(T).name()) + ">"); } catch (...) { throw InvalidArgument("Could not convert value to string, an exception occured. Key: '" + key + "'", "SettingsManager::set<" + std::string(typeid(T).name()) + ">"); } - // check if new value is allowed - if (!isValueAllowed(key, value)) { - if (throwExceptionWhenNewValueNotAllowed) { - throw InvalidArgument("Value '" + s + "' is not allowed. Key: '" + key + "'", "SettingsManager::set<" + std::string(typeid(T).name()) + ">"); - } - else { - return; - } - } // remove old value from cache for (auto& [type, cache] : settingsCache) { if (cache.contains(key)) { @@ -697,108 +487,6 @@ namespace gz { } // -// ALLOWED VALUES -// - template - template T> - bool SettingsManager::isValueAllowed(const std::string& key, const T& value) const noexcept { - if (!allowedValues.contains(key)) { - return true; - } - const std::vector* av = nullptr; - try { - av = &std::get>(allowedValues.at(key).allowedValues); - } - catch (std::bad_variant_access&) { - return false; - } - - switch (allowedValues.at(key).type) { - case SM_LIST: { - for (auto it = av->begin(); it != av->end(); it++) { - if (*it == value) { - return true; - } - } - } - break; - case SM_RANGE: { - bool valid = true; - // value >= lowest - valid &= value >= av->at(0); - // value <= highest - valid &= value <= av->at(1); - return valid; - } - break; - } // switch - return false; - } - - template - template T> - bool SettingsManager::isValueAllowed(const std::string& key, const T& value) const noexcept { - if (!allowedValues.contains(key)) { - return true; - } - const std::vector* av = nullptr; - try { - av = &std::get>(allowedValues.at(key).allowedValues); - } - catch (std::bad_variant_access&) { - return false; - } - switch (allowedValues.at(key).type) { - case SM_LIST: { - for (auto it = av->begin(); it != av->end(); it++) { - if (*it == value) { - return true; - } - } - } - break; - case SM_RANGE: { - return false; - } - break; - } // switch - return false; - } - - template - template T> - void SettingsManager::setAllowedValues(const std::string& key, std::vector& allowed_vector, SettingsManagerAllowedValueTypes type) { - /* std::cout << "setAllowedValues: " << typeid(std::vector).name() << " - " << typeid(T).name() << "\n"; */ - SettingsManagerAllowedValues av; - av.type = type; - av.allowedValues = std::variant, std::vector...>(std::move(allowed_vector)); - hasCorrectFormat(av); - allowedValues[key] = std::move(av); - // erase the current value if it is no longer allowed - if (settingsCache[typeid(T).name()].contains(key)) { - /* std::cout << "setAllowedValues <" << typeid(T).name() << ">\n"; */ - if (!isValueAllowed(key, std::get(settingsCache[typeid(T).name()].at(key)))) { - settings.erase(key); - settingsCache[typeid(T).name()].erase(key); - } - } - else if (settings.contains(key)) { - try { - if (!isValueAllowed(key, fromString(settings.at(key)))) { - settings.erase(key); - } - } - catch (...) { - settings.erase(key); - } - } - } - - template - void SettingsManager::removeAllowedValues(const std::string& key) noexcept { - allowedValues.erase(key); - } -// // CALLBACKS // template diff --git a/src/settings_manager/restricted_value.hpp b/src/settings_manager/restricted_value.hpp new file mode 100644 index 0000000..a158886 --- /dev/null +++ b/src/settings_manager/restricted_value.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "value_restriction.hpp" + +namespace gz { + template + class RestrictedValue { + public: + /** + * @brief Create a value that is restricted to the restriction + * @throws InvalidArgument if initial does not satisfy the restriction + */ + RestrictedValue(T&& initial, ValueRestriction&& restriction); + + /** + * @brief Get the value. + * @details + * This function will never return a value that does not satisfy the restriction + */ + inline const T& get() const { return val; } + /** + * @brief Set a new value + * @throws InvalidArgument if initial does not satisfy the restriction + */ + void operator=(const T& v); + void operator=(T&& v); + void operator=(const std::string& s) const requires (ConstructibleFromString && !std::same_as); + inline const ValueRestriction& getRestriction() const { return restriction; } + + private: + T val; + ValueRestriction restriction; + }; +} + +namespace gz { + template + RestrictedValue::RestrictedValue(T&& initial, ValueRestriction&& restriction) { + + } + + template + void RestrictedValue::operator=(T&& v) { + if (restriction.isValueAllowed(v)) { + val = std::move(v); + } + else { + throw InvalidArgument("Trying to assign invalid value", "RestrictedValue::operator="); + } + } + template + void RestrictedValue::operator=(const T& v) { + if (restriction.isValueAllowed(v)) { + val = v; + } + else { + throw InvalidArgument("Trying to assign invalid value", "RestrictedValue::operator="); + } + } + template + void RestrictedValue::operator=(const std::string& s) const requires (ConstructibleFromString && !std::same_as) { + T v; + try { + v = fromString(s); + } + catch (...) { + throw InvalidArgument(std::string("Can not convert string to type <") + typeid(T).name() + ">", "RestrictedValue::operator="); + } + *this = std::move(v); + } + + +} // namespace gz diff --git a/src/settings_manager/value_restriction.hpp b/src/settings_manager/value_restriction.hpp new file mode 100644 index 0000000..622bb78 --- /dev/null +++ b/src/settings_manager/value_restriction.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include "common.hpp" + +#include +#include + +namespace gz { + enum SettingsManagerAllowedValueTypes { + SM_RANGE, SM_LIST, + }; + + /** + * @brief A value restriction can check if a variable satisfies the restriction. + * @details + * The restriction can be: + * - SM_RANGE -> value needs to be in [min, max] + * - SM_LIST -> value must be one of + */ + template + class ValueRestriction { + public: + /** + * @todo allow any class that has <= and >= + * @brief Restriction is a range [lower, upper] + */ + ValueRestriction(const T& lower, const T& upper) requires util::Number; + /** + * @brief Restriction is a list of allowedValues + */ + ValueRestriction(std::vector&& allowedValues) noexcept; + + /** + * @brief Check if a value is allowed according to SettingsManagerAllowedValues + */ + bool isValueAllowed(const T& value) const noexcept; + + /** + * @brief Construct a value from string and check if it is allowed according to SettingsManagerAllowedValues + * @throws Exceptions that occur while s is converted to T (using gz::fromString()) + */ + bool isValueAllowed(const std::string& s) const requires (ConstructibleFromString && !std::same_as); + + inline const SettingsManagerAllowedValueTypes& getType() const { return type; } + private: + const SettingsManagerAllowedValueTypes type; + std::vector allowedValues; // using vector and not set to also store lower, upper in there + }; + + +} // namespace gz + +namespace gz { + // + // VALUERESTICTION IMPLEMENTATION + // + template + ValueRestriction::ValueRestriction(const T& lower, const T& upper) requires util::Number + : type(SettingsManagerAllowedValueTypes::SM_RANGE) { + if (lower > upper) { + throw InvalidArgument("ValueRestriction: lower(=\"" + gz::toString(lower) + "\") is larger than upper(=\"" + gz::toString(upper) + "\")"); + } + allowedValues.push_back(lower); + allowedValues.push_back(upper); + } + + template + ValueRestriction::ValueRestriction(std::vector&& allowedValues) noexcept + : type(SettingsManagerAllowedValueTypes::SM_LIST) { + this->allowedValues = std::move(allowedValues); + } + + template + bool ValueRestriction::isValueAllowed(const std::string& s) const requires (ConstructibleFromString && !std::same_as) { + T val = fromString(s); + return isValue(val); + } + + template + bool ValueRestriction::isValueAllowed(const T& value) const noexcept { + switch (this->type) { + case SM_LIST: { + for (auto it = this->allowedValues.begin(); it != this->allowedValues.end(); it++) { + if (*it == value) { return true; } + } + } + break; + case SM_RANGE: { + bool valid = true; + // value >= lowest + valid &= value >= allowedValues.at(0); + // value <= highest + valid &= value <= allowedValues.at(1); + return valid; + } + break; + } // switch + return false; + } +} // namespace gz + + +#ifdef GZ_UTIL_TESTING +// TODO +TEST_CASE("") + + CHECK_THROWS() + + +#endif