Skip to content

Commit

Permalink
Add Universal Serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
linuxnyasha committed Dec 27, 2023
1 parent 72724aa commit 82003c1
Show file tree
Hide file tree
Showing 6 changed files with 935 additions and 0 deletions.
97 changes: 97 additions & 0 deletions universal/include/userver/formats/parse/try_parse.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#pragma once
#include <type_traits>
#include <string>
#include <cstdint>
#include <limits>
#include <userver/formats/parse/to.hpp>
#include <userver/utils/meta.hpp>
#include <userver/utils/impl/type_list.hpp>
#include <userver/formats/common/meta.hpp>
#include <userver/formats/parse/common_containers.hpp>


USERVER_NAMESPACE_BEGIN

namespace formats::parse {

namespace impl {

template <typename T, typename Value>
constexpr inline bool Is(Value&& value) {
if constexpr(std::is_same_v<T, bool>) {
return value.IsBool();
} else if constexpr(meta::kIsInteger<T>) {
return (std::is_unsigned_v<T> && sizeof(T) == sizeof(std::uint64_t)) ? value.IsUInt64() : value.IsInt64();
} else if constexpr(std::is_convertible_v<T, std::string>) {
return value.IsString();
} else if constexpr(std::is_convertible_v<T, double>) {
return value.IsDouble();
} else if constexpr(meta::kIsRange<T>) {
return value.IsArray();
} else {
return value.IsObject();
}
}
bool CheckInBounds(const auto& x, const auto& min, const auto& max) {
if (x < min || x > max) {
return false;
};
return true;
};
inline constexpr utils::impl::TypeList<bool, double, std::string> kBaseTypes;

} // namespace impl


template <typename T, typename Value>
constexpr inline std::enable_if_t<
utils::impl::AnyOf(utils::impl::IsSameCarried<T>(), impl::kBaseTypes) ||
meta::kIsInteger<T>, std::optional<T>>
TryParse(Value&& value, userver::formats::parse::To<T>) {
if(!impl::Is<T>(value)) {
return std::nullopt;
}
return value.template As<T>();
}

template <typename T, typename Value>
constexpr inline std::optional<std::optional<T>> TryParse(Value&& value, userver::formats::parse::To<std::optional<T>>) {
return TryParse(std::forward<Value>(value), userver::formats::parse::To<T>{});
}
template <typename Value>
constexpr inline std::optional<float> TryParse(Value&& value, userver::formats::parse::To<float>) {
auto response = value.template As<double>();
if(impl::CheckInBounds(response, std::numeric_limits<float>::lowest(),
std::numeric_limits<float>::max())) {
return static_cast<float>(response);
};
return std::nullopt;
};

template <typename T, typename Value>
constexpr inline std::enable_if_t<meta::kIsRange<T> && !meta::kIsMap<T> &&
!std::is_same_v<T, boost::uuids::uuid> &&
!utils::impl::AnyOf(utils::impl::IsConvertableCarried<T>(), impl::kBaseTypes) &&
!std::is_convertible_v<
T&, utils::impl::strong_typedef::StrongTypedefTag&>,
std::optional<T>>
TryParse(Value&& from, To<T>) {
T response;
auto inserter = std::inserter(response, response.end());
using ValueType = meta::RangeValueType<T>;
for(const auto& item : from) {
auto insert = TryParse(item, userver::formats::parse::To<ValueType>{});
if(!insert) {
return std::nullopt;
};
*inserter = *insert;
++inserter;
};
return response;
}



} // namespace formats::parse

USERVER_NAMESPACE_END
296 changes: 296 additions & 0 deletions universal/include/userver/formats/universal/common_containers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
#pragma once
#include <userver/formats/universal/universal.hpp>
#include <userver/formats/common/items.hpp>
#include <userver/formats/parse/try_parse.hpp>
#include <format>
#include <userver/utils/regex.hpp>

USERVER_NAMESPACE_BEGIN
namespace formats::universal {
template <typename T>
struct Max : public impl::Param<T> {
inline constexpr Max(const T& value) :
impl::Param<T>(value) {}
inline constexpr std::string Check(const T& value) const {
return value <= this->value ? "" : this->Error(value);
}
template <typename Value>
inline constexpr std::enable_if_t<meta::kIsRange<Value>, std::string>
Check(const Value& value) const {
for(const auto& element : value) {
if(element > this->value) {
return this->Error(element);
}
}
return "";
}
inline constexpr std::string Error(const T& value) const {
return std::format("{} > {}", value, this->value);
}
};

struct MinElements : public impl::Param<std::size_t> {
inline constexpr MinElements(const std::size_t& value) :
impl::Param<std::size_t>(value) {}
template <typename Value>
inline constexpr std::string Check(const Value& value) const {
if(value.size() >= this->value) {
return "";
}
return this->Error(value);
}
template <typename Value>
inline constexpr auto Error(const Value&) const {
return "Error";
}
};

template <typename T>
struct Min : public impl::Param<T> {
inline constexpr Min(const T& value) :
impl::Param<T>(value) {}
inline constexpr std::string Check(const T& value) const {
return value >= this->value ? "" : this->Error(value);
};
template <typename Value>
inline constexpr std::enable_if_t<meta::kIsRange<Value>, std::string>
Check(const Value& value) const {
for(const auto& element : value) {
if(element < this->value) {
return this->Error(element);
}
}
return "";
}
inline constexpr auto Error(const T& value) const {
return std::format("{} < {}", value, this->value);
};
};
template <typename T>
struct Default : public impl::EmptyCheck, public impl::Param<T> {
inline constexpr Default(const T& value) :
impl::Param<T>(value) {}
};
template <utils::ConstexprString Pattern>
static const utils::regex kRegex(Pattern);

struct Pattern : public impl::EmptyCheck, public impl::Param<const utils::regex*> {
constexpr inline Pattern(const utils::regex& regex) :
impl::Param<const utils::regex*>(&regex) {}
constexpr inline std::string Check(std::string_view str) const {
return utils::regex_match(str, *this->value) ? "" : this->Error(str);
}
constexpr inline std::string Error(std::string_view) const {
return "Error";
}
};
struct Additional : public impl::EmptyCheck, public impl::Param<bool> {
constexpr inline Additional(const bool& value) :
impl::Param<bool>(value) {}
};
template <>
struct FieldConfig<int> {
std::optional<Max<int>> Maximum;
std::optional<Min<int>> Minimum;
template <typename MainClass, auto I, typename Value>
constexpr int Read(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
return value[name].template As<int>();
};
template <typename MainClass, auto I, typename Value>
constexpr std::optional<int> TryRead(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
return parse::TryParse(value[name], parse::To<int>{});
};
constexpr auto Write(const int& value, std::string_view fieldName, const auto&, auto& builder) const {
builder[static_cast<std::string>(fieldName)] = value;
};
inline constexpr std::string_view Check(const int&) const {
return "";
}

};
template <>
struct FieldConfig<std::optional<std::string>> {
std::optional<Pattern> Pattern;
std::optional<Default<std::string>> Default;
template <typename MainClass, auto I, typename Value>
constexpr std::optional<std::string> Read(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
if(!value.HasMember(name)) {
return std::nullopt;
};
return value[name].template As<std::string>();
};
template <typename MainClass, auto I, typename Value>
constexpr std::optional<std::optional<std::string>> TryRead(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
auto response = parse::TryParse(value[name], parse::To<std::string>{});
if(response) {
return response;
}
if(this->Default) {
return this->Default->value;
}
return std::nullopt;
}
constexpr auto Write(const std::optional<std::string>& value, std::string_view fieldName, const auto&, auto& builder) const {
if(value) {
builder[static_cast<std::string>(fieldName)] = *value;
};
};
inline constexpr std::string_view Check(const std::string&) const {
return "";
}

};
template <>
struct FieldConfig<std::string> {
std::optional<Pattern> Pattern;
template <typename MainClass, auto I, typename Value>
constexpr std::string Read(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
return value[name].template As<std::string>();
};
template <typename MainClass, auto I, typename Value>
constexpr std::optional<std::string> TryRead(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
return parse::TryParse(value[name], parse::To<std::string>{});
};
constexpr auto Write(std::string_view value, std::string_view fieldName, const auto&, auto& builder) const {
builder[static_cast<std::string>(fieldName)] = value;
};
inline constexpr std::string_view Check(std::string_view) const {
return "";
}

};

template <>
struct FieldConfig<std::optional<int>> {
std::optional<Max<int>> Maximum;
std::optional<Min<int>> Minimum;
std::optional<Default<int>> Default;
template <typename MainClass, auto I, typename Value>
constexpr std::optional<int> Read(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
if(value.HasMember(name)) {
return value[name].template As<int>();
}
if(this->Default) {
return this->Default->value;
}
return std::nullopt;
}
template <typename MainClass, auto I, typename Value>
constexpr std::optional<std::optional<int>> TryRead(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
auto response = parse::TryParse(value[name], parse::To<int>{});
if(response) {
return response;
}
if(this->Default) {
return this->Default->value;
}
return {{}};
}
constexpr auto Write(const std::optional<int>& value, std::string_view fieldName, const auto&, auto& builder) const {
if(value) {
builder[static_cast<std::string>(fieldName)] = *value;
return;
}
if(this->Default) {
builder[static_cast<std::string>(fieldName)] = this->Default->value;
}
}

inline constexpr std::string_view Check(const std::optional<int>&) const {
return "";
}

};
template <typename Value>
struct FieldConfig<std::unordered_map<std::string, Value>> {
std::optional<Additional> Additional;
using kType = std::unordered_map<std::string, Value>;
template <typename MainClass, auto I, typename Value2>
inline constexpr kType Read(Value2&& value) const {
if(!this->Additional) {
throw std::runtime_error("Invalid Flags");
}
kType response;
constexpr auto fields = boost::pfr::names_as_array<MainClass>();
for(const auto& [name, value2] : userver::formats::common::Items(std::forward<Value2>(value))) {
auto it = std::find(fields.begin(), fields.end(), name);
if(it == fields.end()) {
response.emplace(name, value2.template As<Value>());
}
}
return response;
}
template <typename MainClass, auto I, typename Value2>
inline constexpr std::optional<kType> TryRead(Value2&& value) const {
if(!this->Additional) {
throw std::runtime_error("Invalid Flags");
}
kType response;
constexpr auto fields = boost::pfr::names_as_array<MainClass>();
constexpr auto name = boost::pfr::get_name<I, MainClass>();
for(const auto& [name2, value2] : userver::formats::common::Items(std::forward<Value2>(value))) {
if(std::find(fields.begin(), fields.end(), name2) == fields.end()) {
auto New = parse::TryParse(value2, parse::To<Value>{});
if(!New) {
return std::nullopt;
};
response.emplace(name, *New);
}
}
return response;
}
inline constexpr std::string_view Check(const kType&) const {
return "";
}
constexpr auto Write(const kType& value, std::string_view, const auto&, auto& builder) const {
for(const auto& [name, value2] : value) {
builder[name] = value2;
};
};
};
template <typename Element>
struct FieldConfig<std::vector<Element>> {
std::optional<MinElements> MinimalElements;
FieldConfig<Element> Items;
template <typename MainClass, auto I, typename Value>
inline constexpr auto Read(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
std::vector<Element> response;
for(const auto& element : value[name]) {
auto New = element.template As<Element>();
response.push_back(std::move(New));
}
return response;
}
template <typename MainClass, auto I, typename Value>
inline constexpr std::optional<std::vector<Element>> TryRead(Value&& value) const {
std::vector<Element> response;
constexpr auto name = boost::pfr::get_name<I, MainClass>();
for(const auto& element : value[name]) {
auto New = parse::TryParse(element, parse::To<Element>{});
if(!New) {
return std::nullopt;
}
response.push_back(std::move(*New));
}
return response;
}
inline constexpr std::string Check(const std::vector<Element>& obj) const {
std::string error;
for(const auto& element : obj) {
error += impl::UniversalCheckField(element, this->Items);
}
return error;
}

};
} // namespace formats::universal
USERVER_NAMESPACE_END
Loading

0 comments on commit 82003c1

Please sign in to comment.