-
Notifications
You must be signed in to change notification settings - Fork 300
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
72724aa
commit 82003c1
Showing
6 changed files
with
935 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
296
universal/include/userver/formats/universal/common_containers.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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*>(®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> { | ||
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 |
Oops, something went wrong.