-
Notifications
You must be signed in to change notification settings - Fork 350
feat formats: automatic serialization/deserialization based on Boost.PFR #472
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
base: develop
Are you sure you want to change the base?
Changes from 1 commit
82003c1
b49d32c
f1260f2
cf62dc4
ebb0d3e
d3a97d4
aee757f
8986d53
8e942ae
30b890b
c3323b9
1af8915
fc62f92
30a7b78
4c2bd65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; | ||
|
Comment on lines
+19
to
+22
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can simplified. The other changes may (or may not) render this comment obsolete, though
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }; | ||
| 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>) { | ||
linuxnyasha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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>{}); | ||
linuxnyasha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| 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; | ||
Anton3 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ++inserter; | ||
| }; | ||
| return response; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| } // namespace formats::parse | ||
|
|
||
| USERVER_NAMESPACE_END | ||
| 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> | ||
|
|
||
linuxnyasha marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 { | ||
Anton3 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 { | ||
Anton3 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for(const auto& element : value) { | ||
| if(element > this->value) { | ||
| return this->Error(element); | ||
| } | ||
| } | ||
| return ""; | ||
| } | ||
| inline constexpr std::string Error(const T& value) const { | ||
Anton3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return std::format("{} > {}", value, this->value); | ||
| } | ||
| }; | ||
|
|
||
| struct MinElements : public impl::Param<std::size_t> { | ||
| inline constexpr MinElements(const std::size_t& value) : | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Parameters of guaranteed |
||
| impl::Param<std::size_t>(value) {} | ||
| template <typename Value> | ||
| inline constexpr std::string Check(const Value& value) const { | ||
| if(value.size() >= this->value) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
| 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) : | ||
Anton3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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*>(®ex) {} | ||
| 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> { | ||
linuxnyasha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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>(); | ||
linuxnyasha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
| 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) { | ||
linuxnyasha marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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; | ||
Anton3 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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>; | ||
linuxnyasha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| template <typename MainClass, auto I, typename Value2> | ||
| inline constexpr kType Read(Value2&& value) const { | ||
| if(!this->Additional) { | ||
| throw std::runtime_error("Invalid Flags"); | ||
linuxnyasha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| 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 ""; | ||
linuxnyasha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| 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 | ||
linuxnyasha marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| USERVER_NAMESPACE_END | ||
Uh oh!
There was an error while loading. Please reload this page.