Files
mcc/include/mcc/mcc_keyvalue.h
2026-05-27 16:18:11 +03:00

574 lines
22 KiB
C++

#pragma once
/****************************************************************************************
* *
* MOUNT CONTROL COMPONENTS LIBRARY *
* *
* *
* IMPLEMENTATION OF KEY-VALUE PAIRS HOLDER *
* *
****************************************************************************************/
#include <unordered_map>
#include <unordered_set>
#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,
ERROR_SERIAL
};
} // namespace mcc::impl
namespace std
{
template <>
class is_error_code_enum<mcc::impl::MccKeyValueHolderErrorCode> : 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 override
{
return "MCC-KEYVALUEHOLDER-ERR-CATEGORY";
}
std::string message(int ec) const override
{
MccKeyValueHolderErrorCode err = static_cast<MccKeyValueHolderErrorCode>(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";
case MccKeyValueHolderErrorCode::ERROR_SERIAL:
return "serialization 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<int>(ec), MccKeyValueHolderCategory::get());
}
// to follow std::variant requirements (not references, not array, not void)
template <typename T>
concept mcc_record_value_c = requires { !std::is_array_v<T> && !std::is_void_v<T> && !std::is_reference_v<T>; };
template <typename T>
concept mcc_keyvalue_record_c = requires(T t) {
requires std::same_as<decltype(t.key), const std::string_view>; // key name is immutable!!!
requires mcc_record_value_c<decltype(t.value)>; // value is mutable
requires mcc_record_value_c<decltype(t.default_value)>; // default value is mutable
requires mcc_serialization_params_c<decltype(t.serial_pars)>; // serialization parameters
};
// must be a std::tuple of mcc_keyvalue_record_c
template <typename T>
concept mcc_keyvalue_desc_c = requires(T t) { []<mcc_keyvalue_record_c... Ts>(std::tuple<Ts...>) {}(t); };
static constexpr char MCC_KV_COMMENT_SEQ_ARR[] = "#";
static constexpr char MCC_KV_KEY_VALUE_DELIM_SEQ_ARR[] = "=";
static constexpr char MCC_KV_COMPOSITE_VALUE_DELIM_SEQ_ARR[] = ",";
template <mcc_keyvalue_desc_c DESCR_T,
const char* COMM_SEQ = MCC_KV_COMMENT_SEQ_ARR, // comment char sequence
const char* KV_DELIM = MCC_KV_KEY_VALUE_DELIM_SEQ_ARR // key-value delimiter
>
class MccKeyValueHolder
{
public:
enum OutputPolicy {
OPOLICY_CHANGED_ONLY, // serialize only changed and previously deserialized (by fromCharRange method) key-value
// pairs
OPOLICY_FULL // serialize entire set of key-value pairs
};
static constexpr std::string_view DEFAULT_RECORD_DELIMITER{"\n"};
static constexpr std::string_view COMMENT_SEQ{COMM_SEQ == nullptr ? MCC_KV_COMMENT_SEQ_ARR
: COMM_SEQ[0] == '\0' ? MCC_KV_COMMENT_SEQ_ARR
: COMM_SEQ};
static constexpr std::string_view KEY_VALUE_DELIM{KV_DELIM == nullptr ? MCC_KV_KEY_VALUE_DELIM_SEQ_ARR
: KV_DELIM[0] == '\0' ? MCC_KV_KEY_VALUE_DELIM_SEQ_ARR
: KV_DELIM};
static constexpr size_t NUMBER_OF_RECORDS = std::tuple_size_v<DESCR_T>;
MccKeyValueHolder(DESCR_T desc)
: _keyValue(desc), _mapHash{[&desc]<size_t... Is>(std::index_sequence<Is...>) {
auto arr = std::array{std::make_pair(utils::FNV1aHash(std::get<Is>(desc).key), Is)...};
std::sort(arr.begin(), arr.end(), [](auto const& p1, auto const& p2) { return p1.first < p2.first; });
return arr;
}(std::make_index_sequence<NUMBER_OF_RECORDS>())}
{
[this]<size_t... I>(std::index_sequence<I...>) {
((_hashes[I] = utils::FNV1aHash(std::get<I>(_keyValue).key)), ...);
}(std::make_index_sequence<NUMBER_OF_RECORDS>());
_changedKey.insert_range(_hashes);
}
constexpr std::array<std::string_view, NUMBER_OF_RECORDS> keys() const
{
return [this]<size_t... Is>(std::index_sequence<Is...>) {
return std::array<std::string_view, NUMBER_OF_RECORDS>{std::get<Is>(_keyValue).key...};
}(std::make_index_sequence<NUMBER_OF_RECORDS>{});
}
void setToDefaults()
{
[this]<size_t... Is>(std::index_sequence<Is...>) {
((std::get<Is>(_keyValue).value = std::get<Is>(_keyValue).default_value), ...);
}(std::make_index_sequence<NUMBER_OF_RECORDS>{});
}
template <typename T, std::ranges::contiguous_range R>
std::expected<T, std::error_code> getValue(R const& key) const
{
return getValue<T>(std::string_view{key});
}
template <typename T>
std::expected<T, std::error_code> getValue(std::string_view key) const
{
// T v;
// auto err = forKey(key, [&v](auto const& rec) -> std::error_code {
// using VT = decltype(rec.value);
// if constexpr (std::convertible_to<VT, T>) {
// v = rec.value;
// } else if constexpr (std::constructible_from<T, VT>) {
// v = T(rec.value);
// } else {
// return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE;
// }
// return MccKeyValueHolderErrorCode::ERROR_OK;
// });
// if (err) {
// return std::unexpected(err);
// } else {
// return v;
// }
return forHash1(utils::FNV1aHash(key), [](auto const& rec) {
using VT = decltype(rec.value);
if constexpr (std::constructible_from<T, VT>) {
return T{rec.value};
} else {
return std::unexpected(MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE);
}
});
}
template <std::ranges::contiguous_range R, typename T>
std::error_code setValue(R const& key, const T& value)
{
return setValue(std::string_view{key}, value);
}
template <typename T>
std::error_code setValue(std::string_view key, const T& value)
{
const size_t hash = utils::FNV1aHash(key);
auto ec = forHash(hash, [&value](auto& rec) -> std::error_code {
using VT = decltype(rec.value);
if constexpr (std::assignable_from<VT&, T>) {
rec.value = value;
} else if constexpr (std::constructible_from<VT, T>) {
rec.value = VT(value);
} else {
return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE;
}
return MccKeyValueHolderErrorCode::ERROR_OK;
});
if (!ec) {
_changedKey.insert(hash);
}
return ec;
}
template <traits::mcc_input_char_range R,
traits::mcc_input_char_range RecDelimT = std::string_view,
mcc_serialization_params_c SerParamsT = mcc_serialization_params_t>
std::error_code fromCharRange(const R& buffer,
size_t skip_records = 0, // number of skipped records (from the beginning)
RecDelimT rec_delim = DEFAULT_RECORD_DELIMITER // records delimiter
)
requires std::ranges::contiguous_range<R>
{
if constexpr (std::is_array_v<std::decay_t<R>>) { // char*, const char*
if constexpr (std::is_array_v<std::decay_t<RecDelimT>>) {
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<std::decay_t<RecDelimT>>) {
return fromCharRange(buffer, std::string_view(rec_delim));
}
}
std::error_code ec{};
std::string_view rec, key, svalue, inline_comm;
std::vector<std::optional<std::string>> head_comm;
size_t key_hash;
_headComment.clear();
_inlineComment.clear();
_changedKey.clear();
// set values to its defaults
setToDefaults();
auto recs = std::views::split(buffer, std::move(rec_delim)) | std::views::drop(skip_records);
for (auto const& el : recs) {
rec = mcc::utils::trimSpaces(el, utils::TrimType::TRIM_LEFT);
inline_comm = {};
if (rec.size()) {
auto found = std::ranges::search(rec, COMMENT_SEQ);
if (found.begin() != rec.end()) { // there was the comment sequence in record
if (found.begin() != rec.begin()) { // inline comment
inline_comm =
std::string_view{found.begin() + COMMENT_SEQ.size(), rec.end()}; // mark inline comment
} else { // head comment
head_comm.push_back(std::string{found.end(), rec.end()});
}
rec = std::string_view(rec.begin(), found.begin());
}
if (rec.size()) {
found = std::ranges::search(rec, KEY_VALUE_DELIM);
if (found.begin() != rec.begin()) {
key = trimSpaces(std::string_view(rec.begin(), found.begin()), utils::TrimType::TRIM_RIGHT);
svalue = std::string_view(found.end(), rec.end());
key_hash = utils::FNV1aHash(key);
ec = forHash(key_hash, [&key_hash, svalue, this]<typename REC_T>(REC_T& v) {
using VT = decltype(v.value);
auto err = MccDeserializer<VT>{}(svalue, v.value, v.serial_pars);
if (err) {
return MccKeyValueHolderErrorCode::ERROR_DESERIAL;
}
_changedKey.insert(key_hash);
return MccKeyValueHolderErrorCode::ERROR_OK;
});
if (ec) { // exit in the case of error
break;
}
// save head comment
_headComment[key_hash] = head_comm;
head_comm.clear();
_inlineComment[key_hash] = inline_comm;
} else { // an empty key
ec = MccKeyValueHolderErrorCode::ERROR_INVALID_KEY;
break;
}
} // just comment string starting from the beginning, just skip it (no error)
} else { // empty record, just skip it (no error)
// head_comm.push_back({el.begin(), el.end()});
head_comm.push_back(std::nullopt);
}
}
return ec;
}
template <OutputPolicy OPOLICY = MccKeyValueHolder::OPOLICY_CHANGED_ONLY,
traits::mcc_output_char_range R,
traits::mcc_input_char_range RecDelimT = std::string_view>
std::error_code toCharRange(R& output_buffer, RecDelimT rec_delim = DEFAULT_RECORD_DELIMITER)
{
if (std::is_pointer_v<std::decay_t<RecDelimT>>) { // char*, const char*, char[], conat char[]
return toCharRange(output_buffer, std::string_view{rec_delim});
}
std::error_code ec{};
auto write_rec = [&output_buffer, &rec_delim, &ec, this]<size_t I = 0>(this auto& self) {
if constexpr (I < NUMBER_OF_RECORDS) {
if constexpr (OPOLICY == MccKeyValueHolder::OPOLICY_CHANGED_ONLY) {
if (_changedKey.count(_hashes[I]) == 0) {
self.template operator()<I + 1>();
return;
}
}
ec = formatRecord<I>(output_buffer, rec_delim);
if (ec) {
return;
}
// using val_t = std::remove_cvref_t<decltype(std::get<I>(_keyValue).value)>;
// auto key = std::get<I>(_keyValue).key;
// std::string buff;
// auto err =
// MccSerializer<val_t>{}(buff, std::get<I>(_keyValue).value, std::get<I>(_keyValue).serial_pars);
// if (err) {
// ec = MccKeyValueHolderErrorCode::ERROR_SERIAL;
// return;
// } else {
// size_t hash = _hashes[I];
// // write head comment
// if (_headComment[hash].size()) {
// for (auto const& comm : _headComment[hash]) {
// if (comm.has_value()) {
// std::format_to(std::back_inserter(output_buffer), "{}{}", COMM_SEQ, comm.value());
// }
// std::ranges::copy(rec_delim, std::back_inserter(output_buffer));
// }
// }
// // key and value
// std::format_to(std::back_inserter(output_buffer), "{}{}{}", key, KEY_VALUE_DELIM, buff);
// // inline comment
// if (_inlineComment[hash].size()) {
// std::format_to(std::back_inserter(output_buffer), " {}{}", COMMENT_SEQ,
// _inlineComment[hash]);
// }
// // record delimiter
// std::ranges::copy(rec_delim, std::back_inserter(output_buffer));
// }
self.template operator()<I + 1>();
}
};
write_rec();
// [&write_rec]<size_t... Is>(std::index_sequence<Is...>) {
// (write_rec.template operator()<Is>(), ...);
// }(std::make_index_sequence<NUMBER_OF_RECORDS>());
return ec;
}
protected:
DESCR_T _keyValue;
std::array<size_t, NUMBER_OF_RECORDS> _hashes{}; // key hashes
std::unordered_map<size_t, std::vector<std::optional<std::string>>>
_headComment{}; // comment string/strings before key
std::unordered_map<size_t, std::string> _inlineComment{}; // inline (after key=value pair) comment
std::unordered_set<size_t> _changedKey{}; // loaded keys (see fromCharRange)
const std::array<std::pair<size_t, size_t>, NUMBER_OF_RECORDS> _mapHash;
//
// 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<decltype(self)>(self).template forHash<0>(utils::FNV1aHash(key),
std::forward<decltype(func)>(func));
}
//
// 'func' signature:
// std::error func(mcc_keyvalue_record_c&&)
// e.g.:
// [](auto const& rec)->std::error_code {}
// [](auto& rec)->std::error_code {}
//
template <size_t I = 0>
std::error_code forHash(this auto&& self, size_t hash, auto&& func)
{
if constexpr (I < NUMBER_OF_RECORDS) {
if (hash == std::forward<decltype(self)>(self)._hashes[I]) {
return std::forward<decltype(func)>(func)(std::get<I>(std::forward<decltype(self)>(self)._keyValue));
} else {
return std::forward<decltype(self)>(self).template forHash<I + 1>(hash,
std::forward<decltype(func)>(func));
}
}
return MccKeyValueHolderErrorCode::ERROR_INVALID_KEY;
}
template <typename FuncT>
auto forHash1(this auto&& self, size_t hash, FuncT&& func)
{
return std::forward<decltype(self)>(self).template forHash1Impl<NUMBER_OF_RECORDS / 2>(
hash, std::forward<FuncT>(func), 0, NUMBER_OF_RECORDS - 1);
}
//
// implements a binary search
//
template <size_t I = NUMBER_OF_RECORDS / 2, typename FuncT>
auto forHash1Impl(this auto&& self,
size_t hash,
FuncT&& func,
size_t start = 0,
size_t stop = NUMBER_OF_RECORDS - 1)
{
using func_t = std::remove_cvref_t<FuncT>;
if constexpr (I < NUMBER_OF_RECORDS) {
if (hash == std::forward<decltype(self)>(self)._mapHash[I].first) {
constexpr auto idx = std::forward<decltype(self)>(self)._mapHash[I].second;
using rec_t = std::tuple_element_t<idx, DESCR_T>;
if constexpr (std::is_void_v<std::invoke_result_t<func_t, rec_t>>) {
std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
} else {
return std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
}
} else if (hash < std::forward<decltype(self)>(self)._mapHash[I].first) {
if (stop - start > 1) {
return std::forward<decltype(self)>(self).template forHash1Impl<I / 2>(
hash, std::forward<FuncT>(func), start, I - 1);
}
const auto idx = self._mapHash[I - 1].second;
using rec_t = std::tuple_element_t<idx, DESCR_T>;
if constexpr (std::is_void_v<std::invoke_result_t<func_t, rec_t>>) {
std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
} else {
return std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
}
} else { // hash > std::forward<decltype(self)>(self)._mapHash[I].first
if (stop - start > 1) {
return std::forward<decltype(self)>(self).template forHash1Impl<I / 2>(
hash, std::forward<FuncT>(func), I + 1, stop);
}
const auto idx = self._mapHash[I + 1].second;
using rec_t = std::tuple_element_t<idx, DESCR_T>;
if constexpr (std::is_void_v<std::invoke_result_t<func_t, rec_t>>) {
std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
} else {
return std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
}
}
}
}
template <size_t I = 0, traits::mcc_output_char_range R, traits::mcc_input_char_range RecDelimT>
std::error_code formatRecord(R& output_buffer, RecDelimT rec_delim, bool is_default = false)
{
if constexpr (I < NUMBER_OF_RECORDS) {
auto key = std::get<I>(_keyValue).key;
using val_t = std::remove_cvref_t<decltype(std::get<I>(_keyValue).value)>;
const val_t& val = is_default ? std::get<I>(_keyValue).default_value : std::get<I>(_keyValue).value;
std::string buff;
// auto err = MccSerializer<val_t>{}(buff, std::get<I>(_keyValue).value,
// std::get<I>(_keyValue).serial_pars);
auto err = MccSerializer<val_t>{}(buff, val, std::get<I>(_keyValue).serial_pars);
if (err) {
return MccKeyValueHolderErrorCode::ERROR_SERIAL;
} else {
size_t hash = _hashes[I];
// write head comment
if (_headComment[hash].size()) {
for (auto const& comm : _headComment[hash]) {
if (comm.has_value()) {
std::format_to(std::back_inserter(output_buffer), "{}{}", COMM_SEQ, comm.value());
}
std::ranges::copy(rec_delim, std::back_inserter(output_buffer));
}
}
// key and value
std::format_to(std::back_inserter(output_buffer), "{}{}{}", key, KEY_VALUE_DELIM, buff);
// inline comment
if (_inlineComment[hash].size()) {
std::format_to(std::back_inserter(output_buffer), " {}{}", COMMENT_SEQ, _inlineComment[hash]);
}
// record delimiter
std::ranges::copy(rec_delim, std::back_inserter(output_buffer));
}
}
return MccKeyValueHolderErrorCode::ERROR_OK;
}
};
template <mcc_record_value_c T>
struct mcc_simple_kv_record_t {
const std::string_view key;
T value, default_value;
mcc_serialization_params_t serial_pars;
};
template <mcc_record_value_c T>
static mcc_simple_kv_record_t<T> mcc_make_simple_kv_record(
std::string_view key,
T const& def_value,
mcc_serialization_params_t const& spars = mcc_serialization_params_t{})
{
return mcc_simple_kv_record_t<T>{key, def_value, def_value, spars};
}
} // namespace mcc::impl