From f314c6a2c52dfe75dbc4928d620223259161ef24 Mon Sep 17 00:00:00 2001 From: "Timur A. Fatkhullin" Date: Fri, 15 May 2026 18:37:02 +0300 Subject: [PATCH] add mcc_keyvalue.h: rewrite KEY-VALUE HOLDER class --- include/mcc/mcc_keyvalue.h | 270 +++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 include/mcc/mcc_keyvalue.h diff --git a/include/mcc/mcc_keyvalue.h b/include/mcc/mcc_keyvalue.h new file mode 100644 index 0000000..e8cd209 --- /dev/null +++ b/include/mcc/mcc_keyvalue.h @@ -0,0 +1,270 @@ +#pragma once + + +/**************************************************************************************** + * * + * MOUNT CONTROL COMPONENTS LIBRARY * + * * + * * + * IMPLEMENTATION OF KEY-VALUE PAIRS HOLDER * + * * + ****************************************************************************************/ + + +#include "mcc_constants.h" +#include "mcc_deserializer.h" +#include "mcc_serializer.h" +#include "mcc_utils.h" + +namespace mcc::impl +{ + +enum class MccKeyValueHolderErrorCode : int { ERROR_OK, ERROR_INVALID_KEY, ERROR_INCOMPATIBLE_TYPE, ERROR_DESERIAL }; + +} // namespace mcc::impl + + +namespace std +{ + +template <> +class is_error_code_enum : public true_type +{ +}; + +} // namespace std + +namespace mcc::impl +{ + +// error category +struct MccKeyValueHolderCategory : public std::error_category { + MccKeyValueHolderCategory() : std::error_category() {} + + const char* name() const noexcept + { + return "MCC-KEYVALUEHOLDER-ERR-CATEGORY"; + } + + std::string message(int ec) const + { + MccKeyValueHolderErrorCode err = static_cast(ec); + + switch (err) { + case MccKeyValueHolderErrorCode::ERROR_OK: + return "OK"; + case MccKeyValueHolderErrorCode::ERROR_INVALID_KEY: + return "invalid key value"; + case MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE: + return "incompatible type"; + case MccKeyValueHolderErrorCode::ERROR_DESERIAL: + return "deserialization error"; + default: + return "UNKNOWN"; + } + } + + static const MccKeyValueHolderCategory& get() + { + static const MccKeyValueHolderCategory constInst; + return constInst; + } +}; + + +inline std::error_code make_error_code(MccKeyValueHolderErrorCode ec) +{ + return std::error_code(static_cast(ec), MccKeyValueHolderCategory::get()); +} + + +// to follow std::variant requirements (not references, not array, not void) +template +concept mcc_variant_valid_type_c = requires { !std::is_array_v && !std::is_void_v && !std::is_reference_v; }; + + +template +concept mcc_keyvalue_record_c = requires(T t) { + requires std::same_as; + requires mcc_variant_valid_type_c; +}; + + +template +concept mcc_keyvalue_desc_c = requires(T t) { [](std::tuple) {}(t); }; + + +namespace constants +{ + +static constexpr char MCC_KV_COMMENT_SEQ_ARR[] = "#"; +static constexpr char MCC_KV_KEY_VALUE_DELIM_SEQ_ARR[] = "="; +static constexpr char MCC_KV_VALUE_ARRAY_DELIM_SEQ_ARR[] = ","; + +} // namespace constants + + +template +class MccKeyValueHolder +{ +public: + static constexpr std::string_view COMMENT_SEQ{COMM_SEQ == nullptr ? constants::MCC_KV_COMMENT_SEQ_ARR + : COMM_SEQ[0] == '\0' ? constants::MCC_KV_COMMENT_SEQ_ARR + : COMM_SEQ}; + + static constexpr std::string_view KEY_VALUE_DELIM{KV_DELIM == nullptr ? constants::MCC_KV_KEY_VALUE_DELIM_SEQ_ARR + : KV_DELIM[0] == '\0' ? constants::MCC_KV_KEY_VALUE_DELIM_SEQ_ARR + : KV_DELIM}; + + static constexpr std::string_view VALUE_ARRAY_DELIM{ + ARR_DELIM == nullptr ? constants::MCC_KV_VALUE_ARRAY_DELIM_SEQ_ARR + : ARR_DELIM[0] == '\0' ? constants::MCC_KV_VALUE_ARRAY_DELIM_SEQ_ARR + : ARR_DELIM}; + + + MccKeyValueHolder(DESCR_T desc) : _keyValue(desc) + { + [this](std::index_sequence) { + ((_hashes[I] = utils::FNV1aHash(std::get(_keyValue).key)), ...); + }(std::make_index_sequence>()); + } + + + template + std::expected getValue(std::string_view key) const + { + T v; + auto err = forKey(key, [&v](const VT& val) -> std::error_code { + if constexpr (std::convertible_to) { + v = val; + } else if constexpr (std::constructible_from) { + v = T(val); + } else { + return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE; + } + + return MccKeyValueHolderErrorCode::ERROR_OK; + }); + + if (err) { + return std::unexpected(err); + } else { + return v; + } + } + + template + std::error_code setValue(std::string_view key, const T& value) + { + return forKey(key, [value](VT& val) -> std::error_code { + if constexpr (std::convertible_to) { + val = value; + } else if constexpr (std::constructible_from) { + val = VT(value); + } else { + return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE; + } + + return MccKeyValueHolderErrorCode::ERROR_OK; + }); + } + + + template + std::error_code fromCharRange(const R& buffer, + RecDelimT rec_delim = std::string_view("\n"), + SerParamsT const& ser_params = mcc_serialization_params_t{}) + { + if constexpr (std::is_array_v>) { // char*, const char* + if constexpr (std::is_array_v>) { + return fromCharRange(std::string_view{buffer}, std::string_view(rec_delim)); + } else { + return fromCharRange(std::string_view{buffer}, std::move(rec_delim)); + } + } else { + if constexpr (std::is_array_v>) { + return fromCharRange(buffer, std::string_view(rec_delim)); + } + } + + std::error_code ec{}; + std::string_view rec, key, value; + + + auto recs = std::views::split(buffer, std::move(rec_delim)); + for (auto const& el : recs) { + rec = mcc::utils::trimSpaces(el, utils::TrimType::TRIM_LEFT); + + if (rec.size()) { + auto found = std::ranges::search(rec, COMMENT_SEQ); + if (found.begin() != rec.end()) { // there was the comment sequence in record + rec = std::string_view(rec.begin(), found.begin()); + } + + if (rec.size()) { + found = std::ranges::search(rec, KEY_VALUE_DELIM); + if (found.begin() != rec.begin()) { // ignore an empty key + key = trimSpaces(std::string_view(rec.begin(), found.begin()), utils::TrimType::TRIM_RIGHT); + value = std::string_view(found.end(), rec.end()); + + ec = forKey(key, [value, &ser_params](VT& v) { + auto err = MccDeserializer(value, v, ser_params); + if (err) { + return MccKeyValueHolderErrorCode::ERROR_DESERIAL; + } + + return MccKeyValueHolderErrorCode::ERROR_OK; + }); + } + } // just comment string starting from the beginning, just skip it (no error) + + } // empty record, just skip it (no error) + + if (ec) { + break; + } + } + + return ec; + } + +protected: + DESCR_T _keyValue; + + std::array> _hashes; + + // + // NOTE: deduced this is needed here to use "forKey" method in getter and setter (const and non-const contexts)!!! + // + + std::error_code forKey(this auto&& self, std::string_view key, auto&& func) + { + return std::forward(self).template forHash<0>(utils::FNV1aHash(key), + std::forward(func)); + } + + + template + std::error_code forHash(this auto&& self, size_t hash, auto&& func) + { + if constexpr (I < std::tuple_size_v) { + if (hash == std::forward(self)._hashes[I]) { + return std::forward(func)( + std::get(std::forward(self)._keyValue).value); + } else { + return std::forward(self).template forHash(hash, + std::forward(func)); + } + } + + return MccKeyValueHolderErrorCode::ERROR_INVALID_KEY; + } +}; + + +} // namespace mcc::impl \ No newline at end of file